Compare commits

..

4 Commits

Author SHA1 Message Date
Mikayla Maki
0d705bdab5 Notes.... 2025-02-23 18:20:35 -08:00
Mikayla Maki
a94b13ddf4 WIP: Implement push 2025-02-23 18:20:32 -08:00
Mikayla Maki
a3b08aa8da Make uncommit prompt if replacing the commit message
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-02-20 09:54:53 -08:00
Mikayla Maki
0c4224a3e0 Add the push button to the UI
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-02-20 09:44:17 -08:00
467 changed files with 8101 additions and 19773 deletions

View File

@@ -10,39 +10,16 @@ body:
value: | value: |
<!-- Please insert a one line summary of the issue below --> <!-- Please insert a one line summary of the issue below -->
SUMMARY_SENTENCE_HERE <!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
<!-- Be verbose: Include all steps necessary to reproduce from a clean Zed installation. -->
<!-- Code snippets are better than images, a repository link that reproduces the issue is ideal. -->
Steps to trigger the problem: Steps to trigger the problem:
1. 1.
2. 2.
3. 3.
4.
Actual Behavior: Actual Behavior:
Expected Behavior: Expected Behavior:
<!--
Is there anything additional necessary to reproduce this issue?
- settings.json, keymap.json, .editorconfig etc?
- Does it happen intermittently or only with specific projects / file types?
- Have you found a workaround?
Did you check your Zed.log to see if there is any relevant details there?
- When including large items (videos, screenshots, logs, configs) please wrap with:
<details><summary>See inside for XXXXYYY</summary>
```shell
code
```
</details>
-->
validations: validations:
required: true required: true

View File

@@ -109,16 +109,8 @@ jobs:
- name: cargo clippy - name: cargo clippy
run: ./script/clippy run: ./script/clippy
- name: Install cargo-machete
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
command: install
args: cargo-machete@0.7.0
- name: Check unused dependencies - name: Check unused dependencies
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2 uses: bnjbvr/cargo-machete@main
with:
command: machete
- name: Check licenses - name: Check licenses
run: | run: |
@@ -240,7 +232,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
name: (Windows) Run Clippy and tests name: (Windows) Run Clippy and tests
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-2 runs-on: hosted-windows-1
steps: steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020 # more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git - name: Enable longer pathnames for git
@@ -273,7 +265,8 @@ jobs:
- name: cargo clippy - name: cargo clippy
working-directory: ${{ env.ZED_WORKSPACE }} 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 - name: Run tests
uses: ./.github/actions/run_tests_windows uses: ./.github/actions/run_tests_windows
@@ -305,9 +298,8 @@ jobs:
env: env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }} APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }} ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
@@ -358,14 +350,14 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg 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 - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-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 - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -416,7 +408,7 @@ jobs:
run: script/bundle-linux run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -464,7 +456,7 @@ jobs:
run: script/bundle-linux run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label - 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') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz

View File

@@ -1,25 +0,0 @@
name: Update All Top Ranking Issues
on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository == 'zed-industries/zed'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true
cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
- name: Install Python 3.13
run: uv python install 3.13
- name: Install dependencies
run: uv sync --project script/update_top_ranking_issues -p 3.13
- name: Run script
run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 5393

View File

@@ -1,25 +0,0 @@
name: Update Weekly Top Ranking Issues
on:
schedule:
- cron: "0 15 * * *"
workflow_dispatch:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository == 'zed-industries/zed'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true
cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
- name: Install Python 3.13
run: uv python install 3.13
- name: Install dependencies
run: uv sync --project script/update_top_ranking_issues -p 3.13
- name: Run script
run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 6952 --query-day-interval 7

View File

@@ -65,7 +65,7 @@ jobs:
command: deploy .cloudflare/docs-proxy/src/worker.js command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Preserve Wrangler logs - name: Preserve Wrangler logs
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
if: always() if: always()
with: with:
name: wrangler_logs name: wrangler_logs

View File

@@ -62,9 +62,8 @@ jobs:
env: env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }} APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}

View File

@@ -21,8 +21,6 @@ Andrei Zvonimir Crnković <andrei@0x7f.dev>
Andrei Zvonimir Crnković <andrei@0x7f.dev> <andreicek@0x7f.dev> Andrei Zvonimir Crnković <andrei@0x7f.dev> <andreicek@0x7f.dev>
Antonio Scandurra <me@as-cii.com> Antonio Scandurra <me@as-cii.com>
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev> Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
Ben Kunkle <ben@zed.dev>
Ben Kunkle <ben@zed.dev> <ben.kunkle@gmail.com>
Bennet Bo Fenner <bennet@zed.dev> Bennet Bo Fenner <bennet@zed.dev>
Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com> Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com>
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de> Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
@@ -114,8 +112,6 @@ Sebastijan Kelnerič <sebastijan.kelneric@sebba.dev> <sebastijan.kelneric@vichav
Sergey Onufrienko <sergey@onufrienko.com> Sergey Onufrienko <sergey@onufrienko.com>
Shish <webmaster@shishnet.org> Shish <webmaster@shishnet.org>
Shish <webmaster@shishnet.org> <shish@shishnet.org> Shish <webmaster@shishnet.org> <shish@shishnet.org>
Smit Barmase <0xtimsb@gmail.com>
Smit Barmase <0xtimsb@gmail.com> <smit@zed.dev>
Thorben Kröger <dev@thorben.net> Thorben Kröger <dev@thorben.net>
Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com> Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com>
Thorsten Ball <thorsten@zed.dev> Thorsten Ball <thorsten@zed.dev>

View File

@@ -14,12 +14,12 @@
}, },
"JSON": { "JSON": {
"tab_size": 2, "tab_size": 2,
"preferred_line_length": 120, "preferred_line_length": 100,
"formatter": "prettier" "formatter": "prettier"
}, },
"JSONC": { "JSONC": {
"tab_size": 2, "tab_size": 2,
"preferred_line_length": 120, "preferred_line_length": 100,
"formatter": "prettier" "formatter": "prettier"
}, },
"JavaScript": { "JavaScript": {

351
Cargo.lock generated
View File

@@ -83,8 +83,9 @@ dependencies = [
[[package]] [[package]]
name = "alacritty_terminal" name = "alacritty_terminal"
version = "0.25.1-dev" version = "0.25.0"
source = "git+https://github.com/zed-industries/alacritty.git?rev=03c2907b44b4189aac5fdeaea331f5aab5c7072e#03c2907b44b4189aac5fdeaea331f5aab5c7072e" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bccc2e60c2112dc8e8a722d6d30f2bb1a6a7b5d0e65fa695e09e57415dca7f7"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bitflags 2.8.0", "bitflags 2.8.0",
@@ -257,9 +258,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.96" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]] [[package]]
name = "approx" name = "approx"
@@ -395,6 +396,7 @@ dependencies = [
"language", "language",
"language_model", "language_model",
"language_model_selector", "language_model_selector",
"language_models",
"languages", "languages",
"log", "log",
"lsp", "lsp",
@@ -462,6 +464,7 @@ dependencies = [
"language", "language",
"language_model", "language_model",
"language_model_selector", "language_model_selector",
"language_models",
"log", "log",
"lsp", "lsp",
"markdown", "markdown",
@@ -490,7 +493,6 @@ dependencies = [
"ui", "ui",
"util", "util",
"uuid", "uuid",
"vim_mode_setting",
"workspace", "workspace",
"zed_actions", "zed_actions",
] ]
@@ -517,6 +519,7 @@ dependencies = [
"language", "language",
"language_model", "language_model",
"language_model_selector", "language_model_selector",
"language_models",
"languages", "languages",
"log", "log",
"multi_buffer", "multi_buffer",
@@ -1176,9 +1179,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-config" name = "aws-config"
version = "1.5.17" version = "1.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd" checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1267,35 +1270,11 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "aws-sdk-bedrockruntime"
version = "1.75.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ddf7475b6f50a1a5be8edb1bcdf6e4ae00feed5b890d14a3f1f0e14d76f5a16"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes 1.10.0",
"fastrand 2.3.0",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]] [[package]]
name = "aws-sdk-kinesis" name = "aws-sdk-kinesis"
version = "1.62.0" version = "1.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31622345afd0c35d33c1cbba73ccf9fb88e09857413d8963dea2c493e00704d" checksum = "89f2163d8704e8fdcd51ec6c2e0441c418471e422ee9690451b17a1c46344e1a"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1315,9 +1294,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-s3" name = "aws-sdk-s3"
version = "1.77.0" version = "1.76.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34e87342432a3de0e94e82c99a7cbd9042f99de029ae1f4e368160f9e9929264" checksum = "66e83401ad7287ad15244d557e35502c2a94105ca5b41d656c391f1a4fc04ca2"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1349,9 +1328,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sso" name = "aws-sdk-sso"
version = "1.60.0" version = "1.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56" checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1371,9 +1350,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-ssooidc" name = "aws-sdk-ssooidc"
version = "1.61.0" version = "1.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef" checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1393,9 +1372,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sts" name = "aws-sdk-sts"
version = "1.61.0" version = "1.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156" checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1456,9 +1435,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-checksums" name = "aws-smithy-checksums"
version = "0.63.0" version = "0.62.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c" checksum = "f2f45a1c384d7a393026bc5f5c177105aa9fa68e4749653b985707ac27d77295"
dependencies = [ dependencies = [
"aws-smithy-http", "aws-smithy-http",
"aws-smithy-types", "aws-smithy-types",
@@ -1620,17 +1599,6 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "aws_http_client"
version = "0.1.0"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"futures 0.3.31",
"http_client",
"tokio",
]
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.6.20" version = "0.6.20"
@@ -1760,22 +1728,6 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bedrock"
version = "0.1.0"
dependencies = [
"anyhow",
"aws-sdk-bedrockruntime",
"aws-smithy-types",
"futures 0.3.31",
"schemars",
"serde",
"serde_json",
"strum",
"thiserror 1.0.69",
"tokio",
]
[[package]] [[package]]
name = "bigdecimal" name = "bigdecimal"
version = "0.4.7" version = "0.4.7"
@@ -1808,7 +1760,7 @@ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.10.5", "itertools 0.12.1",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"log", "log",
@@ -1831,7 +1783,7 @@ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.10.5", "itertools 0.12.1",
"log", "log",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@@ -1966,16 +1918,15 @@ dependencies = [
[[package]] [[package]]
name = "blake3" name = "blake3"
version = "1.6.0" version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"arrayvec", "arrayvec",
"cc", "cc",
"cfg-if", "cfg-if",
"constant_time_eq 0.3.1", "constant_time_eq 0.3.1",
"memmap2",
] ]
[[package]] [[package]]
@@ -2401,6 +2352,25 @@ dependencies = [
"cipher", "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]] [[package]]
name = "cbindgen" name = "cbindgen"
version = "0.28.0" version = "0.28.0"
@@ -2498,9 +2468,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.40" version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -2508,7 +2478,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen", "wasm-bindgen",
"windows-link", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@@ -2568,9 +2538,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.31" version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -2578,9 +2548,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.31" version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -2635,7 +2605,6 @@ dependencies = [
"serde", "serde",
"tempfile", "tempfile",
"util", "util",
"windows 0.58.0",
] ]
[[package]] [[package]]
@@ -2650,7 +2619,6 @@ dependencies = [
"clock", "clock",
"cocoa 0.26.0", "cocoa 0.26.0",
"collections", "collections",
"credentials_provider",
"feature_flags", "feature_flags",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
@@ -2816,7 +2784,6 @@ dependencies = [
"futures 0.3.31", "futures 0.3.31",
"git", "git",
"git_hosting_providers", "git_hosting_providers",
"git_ui",
"google_ai", "google_ai",
"gpui", "gpui",
"hex", "hex",
@@ -3003,6 +2970,7 @@ dependencies = [
"collections", "collections",
"gpui", "gpui",
"linkme", "linkme",
"once_cell",
"parking_lot", "parking_lot",
"theme", "theme",
] ]
@@ -3083,7 +3051,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assistant_tool", "assistant_tool",
"async-trait",
"collections", "collections",
"command_palette_hooks", "command_palette_hooks",
"context_server_settings", "context_server_settings",
@@ -3124,9 +3091,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.8.0" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@@ -3144,9 +3111,7 @@ dependencies = [
"clock", "clock",
"collections", "collections",
"command_palette_hooks", "command_palette_hooks",
"ctor",
"editor", "editor",
"env_logger 0.11.6",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
@@ -3154,7 +3119,6 @@ dependencies = [
"indoc", "indoc",
"inline_completion", "inline_completion",
"language", "language",
"log",
"lsp", "lsp",
"menu", "menu",
"node_runtime", "node_runtime",
@@ -3505,26 +3469,14 @@ dependencies = [
[[package]] [[package]]
name = "crc64fast-nvme" name = "crc64fast-nvme"
version = "1.2.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3" checksum = "d5e2ee08013e3f228d6d2394116c4549a6df77708442c62d887d83f68ef2ee37"
dependencies = [ dependencies = [
"cbindgen 0.27.0",
"crc", "crc",
] ]
[[package]]
name = "credentials_provider"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.31",
"gpui",
"paths",
"release_channel",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.5.1" version = "0.5.1"
@@ -3645,19 +3597,9 @@ dependencies = [
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.4.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7747ac3a66a06f4ee6718686c8ea976d2d05fb30ada93ebd76b3f9aef97257c" checksum = "bba74424191d99c617a172ef126d429f62fe54aba1002c4d36e49403ce4b00a2"
dependencies = [
"ctor-proc-macro",
"dtor",
]
[[package]]
name = "ctor-proc-macro"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
@@ -3895,6 +3837,7 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"project", "project",
"rand 0.8.5", "rand 0.8.5",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"settings", "settings",
@@ -4033,21 +3976,6 @@ dependencies = [
"phf", "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]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@@ -4102,7 +4030,7 @@ dependencies = [
"client", "client",
"clock", "clock",
"collections", "collections",
"convert_case 0.8.0", "convert_case 0.7.1",
"ctor", "ctor",
"db", "db",
"emojis", "emojis",
@@ -5345,12 +5273,10 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"regex", "regex",
"rope", "rope",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"smol", "smol",
"sum_tree", "sum_tree",
"tempfile",
"text", "text",
"time", "time",
"unindent", "unindent",
@@ -5420,7 +5346,6 @@ dependencies = [
"theme", "theme",
"time", "time",
"ui", "ui",
"unindent",
"util", "util",
"windows 0.58.0", "windows 0.58.0",
"workspace", "workspace",
@@ -5555,7 +5480,7 @@ dependencies = [
"bytemuck", "bytemuck",
"calloop", "calloop",
"calloop-wayland-source", "calloop-wayland-source",
"cbindgen", "cbindgen 0.28.0",
"cocoa 0.26.0", "cocoa 0.26.0",
"collections", "collections",
"core-foundation 0.9.4", "core-foundation 0.9.4",
@@ -7012,14 +6937,17 @@ dependencies = [
"anthropic", "anthropic",
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
"client",
"collections", "collections",
"deepseek",
"futures 0.3.31", "futures 0.3.31",
"google_ai", "google_ai",
"gpui", "gpui",
"http_client", "http_client",
"image", "image",
"lmstudio",
"log", "log",
"mistral",
"ollama",
"open_ai", "open_ai",
"parking_lot", "parking_lot",
"proto", "proto",
@@ -7028,7 +6956,6 @@ dependencies = [
"serde_json", "serde_json",
"smol", "smol",
"strum", "strum",
"telemetry_events",
"thiserror 1.0.69", "thiserror 1.0.69",
"ui", "ui",
"util", "util",
@@ -7055,14 +6982,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anthropic", "anthropic",
"anyhow", "anyhow",
"aws-config",
"aws-credential-types",
"aws_http_client",
"bedrock",
"client", "client",
"collections", "collections",
"copilot", "copilot",
"credentials_provider",
"deepseek", "deepseek",
"editor", "editor",
"feature_flags", "feature_flags",
@@ -7070,7 +6992,6 @@ dependencies = [
"futures 0.3.31", "futures 0.3.31",
"google_ai", "google_ai",
"gpui", "gpui",
"gpui_tokio",
"http_client", "http_client",
"language_model", "language_model",
"lmstudio", "lmstudio",
@@ -7086,9 +7007,10 @@ dependencies = [
"settings", "settings",
"smol", "smol",
"strum", "strum",
"telemetry_events",
"theme", "theme",
"thiserror 1.0.69",
"tiktoken-rs", "tiktoken-rs",
"tokio",
"ui", "ui",
"util", "util",
] ]
@@ -7228,9 +7150,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "libdbus-sys" name = "libdbus-sys"
@@ -7270,7 +7192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.52.6", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@@ -7574,9 +7496,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
dependencies = [ dependencies = [
"serde", "serde",
"value-bag", "value-bag",
@@ -7947,7 +7869,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"convert_case 0.8.0", "convert_case 0.7.1",
"log", "log",
"pretty_assertions", "pretty_assertions",
"streaming-iterator", "streaming-iterator",
@@ -9326,7 +9248,7 @@ name = "perplexity"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"serde", "serde",
"zed_extension_api 0.3.0", "zed_extension_api 0.2.0",
] ]
[[package]] [[package]]
@@ -9757,15 +9679,6 @@ dependencies = [
"indexmap", "indexmap",
] ]
[[package]]
name = "pgvector"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e8871b6d7ca78348c6cd29b911b94851f3429f0cd403130ca17f26c1fb91a6"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.2" version = "0.11.2"
@@ -10389,8 +10302,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [ dependencies = [
"bytes 1.10.0", "bytes 1.10.0",
"heck 0.5.0", "heck 0.4.1",
"itertools 0.10.5", "itertools 0.12.1",
"log", "log",
"multimap 0.10.0", "multimap 0.10.0",
"once_cell", "once_cell",
@@ -10423,7 +10336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.10.5", "itertools 0.12.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.90",
@@ -11451,9 +11364,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed" name = "rust-embed"
version = "8.6.0" version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f" checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
dependencies = [ dependencies = [
"rust-embed-impl", "rust-embed-impl",
"rust-embed-utils", "rust-embed-utils",
@@ -11462,9 +11375,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-impl" name = "rust-embed-impl"
version = "8.6.0" version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae" checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -11475,9 +11388,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-utils" name = "rust-embed-utils"
version = "8.6.0" version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a" checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
dependencies = [ dependencies = [
"globset", "globset",
"sha2", "sha2",
@@ -11752,9 +11665,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"indexmap", "indexmap",
@@ -11765,9 +11678,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.22" version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -11830,18 +11743,17 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm" name = "sea-orm"
version = "1.1.6" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13fba7b2c749b2d0a00303d5cb13e6761e39a4172554bdf930852cac4e7aeabd" checksum = "1a93194430b419da0801f404baf3b986399d6a2a4f43bc79bc96dea83f92ca43"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"bigdecimal", "bigdecimal",
"chrono", "chrono",
"futures-util", "futures 0.3.31",
"log", "log",
"ouroboros", "ouroboros",
"pgvector",
"rust_decimal", "rust_decimal",
"sea-orm-macros", "sea-orm-macros",
"sea-query", "sea-query",
@@ -11859,9 +11771,9 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm-macros" name = "sea-orm-macros"
version = "1.1.6" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2568cff8d35d5150b4276cc0dd766192a587f64b6ece60ae3706e0872c4eb209" checksum = "d19e8f22fb474a8a622eb516c46885a080535d8d559386188f525977eaad32b3"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
@@ -11934,7 +11846,6 @@ dependencies = [
"unindent", "unindent",
"util", "util",
"workspace", "workspace",
"zed_actions",
] ]
[[package]] [[package]]
@@ -12054,18 +11965,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.218" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.218" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -12094,9 +12005,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.139" version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"itoa", "itoa",
@@ -12445,9 +12356,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.14.0" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -13421,9 +13332,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.17.1" version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand 2.3.0", "fastrand 2.3.0",
@@ -14578,7 +14489,7 @@ dependencies = [
name = "ui_macros" name = "ui_macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"convert_case 0.8.0", "convert_case 0.7.1",
"linkme", "linkme",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -14781,9 +14692,9 @@ dependencies = [
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.15.1" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
dependencies = [ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
"serde", "serde",
@@ -14950,16 +14861,26 @@ dependencies = [
[[package]] [[package]]
name = "vte" name = "vte"
version = "0.15.0" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" checksum = "9a0b683b20ef64071ff03745b14391751f6beab06a54347885459b77a3f2caa5"
dependencies = [ dependencies = [
"arrayvec",
"bitflags 2.8.0", "bitflags 2.8.0",
"cursor-icon", "cursor-icon",
"log", "log",
"memchr",
"serde", "serde",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
dependencies = [
"proc-macro2",
"quote",
] ]
[[package]] [[package]]
@@ -15761,7 +15682,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -15906,12 +15827,6 @@ dependencies = [
"syn 2.0.90", "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]] [[package]]
name = "windows-registry" name = "windows-registry"
version = "0.2.0" version = "0.2.0"
@@ -16747,7 +16662,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.177.0" version = "0.176.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"anyhow", "anyhow",
@@ -16851,7 +16766,6 @@ dependencies = [
"tasks_ui", "tasks_ui",
"telemetry", "telemetry",
"telemetry_events", "telemetry_events",
"tempfile",
"terminal_view", "terminal_view",
"theme", "theme",
"theme_extension", "theme_extension",
@@ -16868,7 +16782,6 @@ dependencies = [
"vim", "vim",
"vim_mode_setting", "vim_mode_setting",
"welcome", "welcome",
"which 6.0.3",
"windows 0.58.0", "windows 0.58.0",
"winresource", "winresource",
"workspace", "workspace",
@@ -16894,7 +16807,7 @@ dependencies = [
[[package]] [[package]]
name = "zed_deno" name = "zed_deno"
version = "0.1.0" version = "0.0.2"
dependencies = [ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]
@@ -16903,7 +16816,7 @@ dependencies = [
name = "zed_elixir" name = "zed_elixir"
version = "0.1.4" version = "0.1.4"
dependencies = [ dependencies = [
"zed_extension_api 0.2.0", "zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@@ -16934,8 +16847,6 @@ dependencies = [
[[package]] [[package]]
name = "zed_extension_api" name = "zed_extension_api"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@@ -16944,7 +16855,9 @@ dependencies = [
[[package]] [[package]]
name = "zed_extension_api" name = "zed_extension_api"
version = "0.3.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@@ -16967,7 +16880,7 @@ dependencies = [
[[package]] [[package]]
name = "zed_html" name = "zed_html"
version = "0.1.6" version = "0.1.5"
dependencies = [ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]
@@ -17030,12 +16943,12 @@ dependencies = [
name = "zed_test_extension" name = "zed_test_extension"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"zed_extension_api 0.3.0", "zed_extension_api 0.2.0",
] ]
[[package]] [[package]]
name = "zed_toml" name = "zed_toml"
version = "0.1.3" version = "0.1.2"
dependencies = [ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]
@@ -17212,7 +17125,7 @@ dependencies = [
"indoc", "indoc",
"inline_completion", "inline_completion",
"language", "language",
"language_model", "language_models",
"log", "log",
"menu", "menu",
"migrator", "migrator",

View File

@@ -15,10 +15,7 @@ members = [
"crates/audio", "crates/audio",
"crates/auto_update", "crates/auto_update",
"crates/auto_update_ui", "crates/auto_update_ui",
"crates/aws_http_client",
"crates/bedrock",
"crates/breadcrumbs", "crates/breadcrumbs",
"crates/buffer_diff",
"crates/call", "crates/call",
"crates/channel", "crates/channel",
"crates/cli", "crates/cli",
@@ -34,10 +31,10 @@ members = [
"crates/context_server", "crates/context_server",
"crates/context_server_settings", "crates/context_server_settings",
"crates/copilot", "crates/copilot",
"crates/credentials_provider",
"crates/db", "crates/db",
"crates/deepseek", "crates/deepseek",
"crates/diagnostics", "crates/diagnostics",
"crates/buffer_diff",
"crates/docs_preprocessor", "crates/docs_preprocessor",
"crates/editor", "crates/editor",
"crates/evals", "crates/evals",
@@ -220,8 +217,6 @@ assistant_tools = { path = "crates/assistant_tools" }
audio = { path = "crates/audio" } audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" } auto_update = { path = "crates/auto_update" }
auto_update_ui = { path = "crates/auto_update_ui" } auto_update_ui = { path = "crates/auto_update_ui" }
aws_http_client = { path = "crates/aws_http_client" }
bedrock = { path = "crates/bedrock" }
breadcrumbs = { path = "crates/breadcrumbs" } breadcrumbs = { path = "crates/breadcrumbs" }
call = { path = "crates/call" } call = { path = "crates/call" }
channel = { path = "crates/channel" } channel = { path = "crates/channel" }
@@ -238,7 +233,6 @@ component_preview = { path = "crates/component_preview" }
context_server = { path = "crates/context_server" } context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" } context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" } copilot = { path = "crates/copilot" }
credentials_provider = { path = "crates/credentials_provider" }
db = { path = "crates/db" } db = { path = "crates/db" }
deepseek = { path = "crates/deepseek" } deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" } diagnostics = { path = "crates/diagnostics" }
@@ -370,7 +364,7 @@ zeta = { path = "crates/zeta" }
# #
aho-corasick = "1.1" aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" } alacritty_terminal = "0.25"
any_vec = "0.14" any_vec = "0.14"
anyhow = "1.0.86" anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] }
@@ -386,11 +380,6 @@ async-trait = "0.1"
async-tungstenite = "0.28" async-tungstenite = "0.28"
async-watch = "0.3.1" async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
aws-config = { version = "1.5.16", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] }
aws-sdk-bedrockruntime = { version = "1.73.0", features = ["behavior-version-latest"] }
aws-smithy-runtime-api = { version = "1.7.3", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.2.13", features = ["http-body-1-x"] }
base64 = "0.22" base64 = "0.22"
bitflags = "2.6.0" bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" } blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
@@ -405,10 +394,10 @@ chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26" cocoa = "0.26"
cocoa-foundation = "0.2.0" cocoa-foundation = "0.2.0"
convert_case = "0.8.0" convert_case = "0.7.0"
core-foundation = "0.9.3" core-foundation = "0.9.3"
core-foundation-sys = "0.8.6" core-foundation-sys = "0.8.6"
ctor = "0.4.0" ctor = "0.3.0"
dashmap = "6.0" dashmap = "6.0"
derive_more = "0.99.17" derive_more = "0.99.17"
dirs = "4.0" dirs = "4.0"
@@ -455,6 +444,7 @@ nanoid = "0.4"
nbformat = { version = "0.10.0" } nbformat = { version = "0.10.0" }
nix = "0.29" nix = "0.29"
num-format = "0.4.4" num-format = "0.4.4"
once_cell = "1.20"
ordered-float = "2.1.1" ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] } palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"
@@ -543,7 +533,7 @@ tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23" tree-sitter-css = "0.23"
tree-sitter-elixir = "0.3" tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0" 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 = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" } 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" } tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
@@ -618,7 +608,6 @@ features = [
"Win32_Storage_FileSystem", "Win32_Storage_FileSystem",
"Win32_System_Com", "Win32_System_Com",
"Win32_System_Com_StructuredStorage", "Win32_System_Com_StructuredStorage",
"Win32_System_Console",
"Win32_System_DataExchange", "Win32_System_DataExchange",
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
"Win32_System_Memory", "Win32_System_Memory",
@@ -639,7 +628,7 @@ features = [
# TODO livekit https://github.com/RustAudio/cpal/pull/891 # TODO livekit https://github.com/RustAudio/cpal/pull/891
[patch.crates-io] [patch.crates-io]
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" } 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] [profile.dev]
split-debuginfo = "unpacked" split-debuginfo = "unpacked"
@@ -709,9 +698,6 @@ debug = "full"
lto = false lto = false
codegen-units = 16 codegen-units = 16
[workspace.lints.rust]
unexpected_cfgs = { level = "allow" }
[workspace.lints.clippy] [workspace.lints.clippy]
dbg_macro = "deny" dbg_macro = "deny"
todo = "deny" todo = "deny"

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="283.6413 127.3453 56 55.9999" width="16px" height="16px">
<path d="M 808.592 158.131 C 807.489 158.131 806.592 157.234 806.592 156.131 C 806.592 155.028 807.489 154.131 808.592 154.131 C 809.695 154.131 810.592 155.028 810.592 156.131 C 810.592 157.234 809.695 158.131 808.592 158.131 Z M 776.705 185.039 L 773.457 183.145 L 780.122 178.979 L 779.062 177.283 L 771.505 182.006 L 765.592 178.557 L 765.592 169.666 L 771.147 165.963 L 770.037 164.299 L 764.551 167.956 L 758.592 164.551 L 758.592 159.711 L 765.088 155.999 L 764.096 154.263 L 758.592 157.408 L 758.592 153.711 L 764.592 150.283 L 770.592 153.711 L 770.592 157.565 L 766.077 160.274 L 767.107 161.988 L 771.592 159.297 L 776.077 161.988 L 777.107 160.274 L 772.592 157.565 L 772.592 153.666 L 778.147 149.963 C 778.425 149.777 778.592 149.465 778.592 149.131 L 778.592 142.131 L 776.592 142.131 L 776.592 148.596 L 771.551 151.956 L 765.592 148.551 L 765.592 139.705 L 770.592 136.789 L 770.592 145.131 L 772.592 145.131 L 772.592 135.622 L 776.705 133.223 L 784.592 135.852 L 784.592 164.565 L 770.077 173.274 L 771.107 174.988 L 784.592 166.897 L 784.592 182.41 L 776.705 185.039 Z M 806.592 169.131 C 806.592 170.234 805.695 171.131 804.592 171.131 C 803.489 171.131 802.592 170.234 802.592 169.131 C 802.592 168.028 803.489 167.131 804.592 167.131 C 805.695 167.131 806.592 168.028 806.592 169.131 Z M 796.592 179.131 C 796.592 180.234 795.695 181.131 794.592 181.131 C 793.489 181.131 792.592 180.234 792.592 179.131 C 792.592 178.028 793.489 177.131 794.592 177.131 C 795.695 177.131 796.592 178.028 796.592 179.131 Z M 795.592 139.131 C 795.592 138.028 796.489 137.131 797.592 137.131 C 798.695 137.131 799.592 138.028 799.592 139.131 C 799.592 140.234 798.695 141.131 797.592 141.131 C 796.489 141.131 795.592 140.234 795.592 139.131 Z M 808.592 152.131 C 806.733 152.131 805.181 153.411 804.734 155.131 L 786.592 155.131 L 786.592 150.131 L 797.592 150.131 C 798.145 150.131 798.592 149.683 798.592 149.131 L 798.592 142.989 C 800.312 142.542 801.592 140.989 801.592 139.131 C 801.592 136.925 799.798 135.131 797.592 135.131 C 795.386 135.131 793.592 136.925 793.592 139.131 C 793.592 140.989 794.872 142.542 796.592 142.989 L 796.592 148.131 L 786.592 148.131 L 786.592 135.131 C 786.592 134.7 786.317 134.319 785.908 134.182 L 776.908 131.182 C 776.634 131.092 776.336 131.122 776.088 131.267 L 764.088 138.267 C 763.78 138.446 763.592 138.776 763.592 139.131 L 763.592 148.551 L 757.096 152.263 C 756.784 152.441 756.592 152.772 756.592 153.131 L 756.592 165.131 C 756.592 165.49 756.784 165.821 757.096 165.999 L 763.592 169.711 L 763.592 179.131 C 763.592 179.486 763.78 179.816 764.088 179.995 L 776.088 186.995 C 776.242 187.085 776.417 187.131 776.592 187.131 C 776.698 187.131 776.805 187.114 776.908 187.08 L 785.908 184.08 C 786.317 183.943 786.592 183.562 786.592 183.131 L 786.592 171.131 L 793.592 171.131 L 793.592 175.273 C 791.872 175.72 790.592 177.273 790.592 179.131 C 790.592 181.337 792.386 183.131 794.592 183.131 C 796.798 183.131 798.592 181.337 798.592 179.131 C 798.592 177.273 797.312 175.72 795.592 175.273 L 795.592 170.131 C 795.592 169.579 795.145 169.131 794.592 169.131 L 786.592 169.131 L 786.592 164.131 L 799.092 164.131 L 801.23 166.981 C 800.831 167.603 800.592 168.338 800.592 169.131 C 800.592 171.337 802.386 173.131 804.592 173.131 C 806.798 173.131 808.592 171.337 808.592 169.131 C 808.592 166.925 806.798 165.131 804.592 165.131 C 803.908 165.131 803.274 165.319 802.711 165.623 L 800.392 162.531 C 800.203 162.279 799.906 162.131 799.592 162.131 L 786.592 162.131 L 786.592 157.131 L 804.734 157.131 C 805.181 158.851 806.733 160.131 808.592 160.131 C 810.798 160.131 812.592 158.337 812.592 156.131 C 812.592 153.925 810.798 152.131 808.592 152.131 Z" fill-rule="evenodd" fill-opacity="1" style="" id="object-0" transform="matrix(1, 0, 0, 1, -472.9506530761719, -3.7858259677886963)"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -84,12 +84,12 @@
"pageup": "editor::MovePageUp", "pageup": "editor::MovePageUp",
"alt-pageup": "editor::PageUp", "alt-pageup": "editor::PageUp",
"shift-pageup": "editor::SelectPageUp", "shift-pageup": "editor::SelectPageUp",
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown", "down": "editor::MoveDown",
"pagedown": "editor::MovePageDown", "pagedown": "editor::MovePageDown",
"alt-pagedown": "editor::PageDown", "alt-pagedown": "editor::PageDown",
"shift-pagedown": "editor::SelectPageDown", "shift-pagedown": "editor::SelectPageDown",
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }], "end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft", "left": "editor::MoveLeft",
"right": "editor::MoveRight", "right": "editor::MoveRight",
"ctrl-left": "editor::MoveToPreviousWordStart", "ctrl-left": "editor::MoveToPreviousWordStart",
@@ -107,9 +107,9 @@
"ctrl-a": "editor::SelectAll", "ctrl-a": "editor::SelectAll",
"ctrl-l": "editor::SelectLine", "ctrl-l": "editor::SelectLine",
"ctrl-shift-i": "editor::Format", "ctrl-shift-i": "editor::Format",
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }], // "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true }],
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], // "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], // "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], // "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
@@ -118,7 +118,7 @@
"ctrl-;": "editor::ToggleLineNumbers", "ctrl-;": "editor::ToggleLineNumbers",
"ctrl-k ctrl-r": "git::Restore", "ctrl-k ctrl-r": "git::Restore",
"ctrl-'": "editor::ToggleSelectedDiffHunks", "ctrl-'": "editor::ToggleSelectedDiffHunks",
"ctrl-\"": "editor::ExpandAllDiffHunks", "ctrl-\"": "editor::ExpandAllHunkDiffs",
"ctrl-i": "editor::ShowSignatureHelp", "ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame", "alt-g b": "editor::ToggleGitBlame",
"menu": "editor::OpenContextMenu", "menu": "editor::OpenContextMenu",
@@ -184,9 +184,8 @@
"ctrl-alt-/": "assistant::ToggleModelSelector", "ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-k h": "assistant::DeployHistory", "ctrl-k h": "assistant::DeployHistory",
"ctrl-k l": "assistant::DeployPromptLibrary", "ctrl-k l": "assistant::DeployPromptLibrary",
"new": "assistant::NewChat", "new": "assistant::NewContext",
"ctrl-t": "assistant::NewChat", "ctrl-n": "assistant::NewContext"
"ctrl-n": "assistant::NewChat"
} }
}, },
{ {
@@ -344,9 +343,9 @@
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit", "alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences", "alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket", "ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-|": "editor::MoveToEnclosingBracket", "ctrl-shift-\\": "editor::MoveToEnclosingBracket",
"ctrl-{": "editor::Fold", "ctrl-shift-[": "editor::Fold",
"ctrl-}": "editor::UnfoldLines", "ctrl-shift-]": "editor::UnfoldLines",
"ctrl-k ctrl-l": "editor::ToggleFold", "ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive", "ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive", "ctrl-k ctrl-]": "editor::UnfoldRecursive",
@@ -368,12 +367,7 @@
"ctrl-\\": "pane::SplitRight", "ctrl-\\": "pane::SplitRight",
"ctrl-k v": "markdown::OpenPreviewToTheSide", "ctrl-k v": "markdown::OpenPreviewToTheSide",
"ctrl-shift-v": "markdown::OpenPreview", "ctrl-shift-v": "markdown::OpenPreview",
"ctrl-alt-shift-c": "editor::DisplayCursorNames", "ctrl-alt-shift-c": "editor::DisplayCursorNames"
"ctrl-alt-y": "git::ToggleStaged",
"alt-y": "git::StageAndNext",
"alt-shift-y": "git::UnstageAndNext",
"alt-.": "editor::GoToHunk",
"alt-,": "editor::GoToPrevHunk"
} }
}, },
{ {
@@ -606,7 +600,7 @@
"ctrl-n": "assistant2::NewThread", "ctrl-n": "assistant2::NewThread",
"new": "assistant2::NewThread", "new": "assistant2::NewThread",
"ctrl-shift-h": "assistant2::OpenHistory", "ctrl-shift-h": "assistant2::OpenHistory",
"ctrl-alt-/": "assistant::ToggleModelSelector", "ctrl-alt-/": "assistant2::ToggleModelSelector",
"ctrl-shift-a": "assistant2::ToggleContextPicker", "ctrl-shift-a": "assistant2::ToggleContextPicker",
"ctrl-e": "assistant2::ChatMode", "ctrl-e": "assistant2::ChatMode",
"ctrl-alt-e": "assistant2::RemoveAllContext" "ctrl-alt-e": "assistant2::RemoveAllContext"
@@ -710,6 +704,12 @@
"space": "project_panel::Open" "space": "project_panel::Open"
} }
}, },
{
"context": "GitPanel && !CommitEditor",
"bindings": {
"escape": "git_panel::Close"
}
},
{ {
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"bindings": { "bindings": {
@@ -721,42 +721,19 @@
"ctrl-shift-space": "git::UnstageAll", "ctrl-shift-space": "git::UnstageAll",
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus"
"ctrl-enter": "git::Commit",
"alt-enter": "menu::SecondaryConfirm"
}
},
{
"context": "GitCommit > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter": "git::Commit"
}
},
{
"context": "GitDiff > Editor",
"bindings": {
"ctrl-enter": "git::Commit"
} }
}, },
{ {
"context": "GitPanel > Editor", "context": "GitPanel > Editor",
"bindings": { "bindings": {
"escape": "git_panel::FocusChanges", "escape": "git_panel::FocusChanges",
"ctrl-enter": "git::Commit",
"tab": "git_panel::FocusChanges", "tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges", "shift-tab": "git_panel::FocusChanges",
"ctrl-enter": "git::Commit",
"alt-up": "git_panel::FocusChanges" "alt-up": "git_panel::FocusChanges"
} }
}, },
{
"context": "GitCommit > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
"ctrl-enter": "git::Commit"
}
},
{ {
"context": "CollabPanel && not_editing", "context": "CollabPanel && not_editing",
"bindings": { "bindings": {
@@ -835,7 +812,6 @@
"pagedown": ["terminal::SendKeystroke", "pagedown"], "pagedown": ["terminal::SendKeystroke", "pagedown"],
"escape": ["terminal::SendKeystroke", "escape"], "escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"], "enter": ["terminal::SendKeystroke", "enter"],
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"], "ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"shift-pageup": "terminal::ScrollPageUp", "shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown", "shift-pagedown": "terminal::ScrollPageDown",

View File

@@ -91,16 +91,14 @@
"ctrl-l": "editor::ScrollCursorCenter", "ctrl-l": "editor::ScrollCursorCenter",
"alt-left": "editor::MoveToPreviousWordStart", "alt-left": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd", "alt-right": "editor::MoveToNextWordEnd",
"cmd-left": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "cmd-left": "editor::MoveToBeginningOfLine",
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], "ctrl-a": "editor::MoveToBeginningOfLine",
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "home": "editor::MoveToBeginningOfLine",
"cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }], "cmd-right": "editor::MoveToEndOfLine",
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], "ctrl-e": "editor::MoveToEndOfLine",
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }], "end": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToStartOfExcerpt", "cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEndOfExcerpt", "cmd-down": "editor::MoveToEnd",
"cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left`
"cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right`
"shift-up": "editor::SelectUp", "shift-up": "editor::SelectUp",
"ctrl-shift-p": "editor::SelectUp", "ctrl-shift-p": "editor::SelectUp",
"shift-down": "editor::SelectDown", "shift-down": "editor::SelectDown",
@@ -113,14 +111,14 @@
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect "alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"ctrl-shift-up": "editor::SelectToStartOfParagraph", "ctrl-shift-up": "editor::SelectToStartOfParagraph",
"ctrl-shift-down": "editor::SelectToEndOfParagraph", "ctrl-shift-down": "editor::SelectToEndOfParagraph",
"cmd-shift-up": "editor::SelectToStartOfExcerpt", "cmd-shift-up": "editor::SelectToBeginning",
"cmd-shift-down": "editor::SelectToEndOfExcerpt", "cmd-shift-down": "editor::SelectToEnd",
"cmd-a": "editor::SelectAll", "cmd-a": "editor::SelectAll",
"cmd-l": "editor::SelectLine", "cmd-l": "editor::SelectLine",
"cmd-shift-i": "editor::Format", "cmd-shift-i": "editor::Format",
"cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
@@ -133,7 +131,7 @@
"cmd-y": "git::StageAndNext", "cmd-y": "git::StageAndNext",
"cmd-shift-y": "git::UnstageAndNext", "cmd-shift-y": "git::UnstageAndNext",
"cmd-'": "editor::ToggleSelectedDiffHunks", "cmd-'": "editor::ToggleSelectedDiffHunks",
"cmd-\"": "editor::ExpandAllDiffHunks", "cmd-\"": "editor::ExpandAllHunkDiffs",
"cmd-alt-g b": "editor::ToggleGitBlame", "cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp", "cmd-i": "editor::ShowSignatureHelp",
"ctrl-f12": "editor::GoToDeclaration", "ctrl-f12": "editor::GoToDeclaration",
@@ -211,8 +209,7 @@
"cmd-alt-/": "assistant::ToggleModelSelector", "cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-k h": "assistant::DeployHistory", "cmd-k h": "assistant::DeployHistory",
"cmd-k l": "assistant::DeployPromptLibrary", "cmd-k l": "assistant::DeployPromptLibrary",
"cmd-t": "assistant::NewChat", "cmd-n": "assistant::NewContext"
"cmd-n": "assistant::NewChat"
} }
}, },
{ {
@@ -238,7 +235,7 @@
"cmd-n": "assistant2::NewThread", "cmd-n": "assistant2::NewThread",
"cmd-alt-p": "assistant2::NewPromptEditor", "cmd-alt-p": "assistant2::NewPromptEditor",
"cmd-shift-h": "assistant2::OpenHistory", "cmd-shift-h": "assistant2::OpenHistory",
"cmd-alt-/": "assistant::ToggleModelSelector", "cmd-alt-/": "assistant2::ToggleModelSelector",
"cmd-shift-a": "assistant2::ToggleContextPicker", "cmd-shift-a": "assistant2::ToggleContextPicker",
"cmd-e": "assistant2::ChatMode", "cmd-e": "assistant2::ChatMode",
"cmd-alt-e": "assistant2::RemoveAllContext" "cmd-alt-e": "assistant2::RemoveAllContext"
@@ -289,8 +286,7 @@
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
"cmd-f": "search::FocusSearch", "cmd-f": "search::FocusSearch",
"cmd-alt-f": "search::ToggleReplace", "cmd-alt-f": "search::ToggleReplace",
"cmd-alt-l": "search::ToggleSelection", "cmd-alt-l": "search::ToggleSelection"
"cmd-shift-o": "outline::Toggle"
} }
}, },
{ {
@@ -468,7 +464,7 @@
"ctrl-9": ["pane::ActivateItem", 8], "ctrl-9": ["pane::ActivateItem", 8],
"ctrl-0": "pane::ActivateLastItem", "ctrl-0": "pane::ActivateLastItem",
"ctrl--": "pane::GoBack", "ctrl--": "pane::GoBack",
"ctrl-_": "pane::GoForward", "ctrl-shift--": "pane::GoForward",
"cmd-shift-f": "pane::DeploySearch" "cmd-shift-f": "pane::DeploySearch"
} }
}, },
@@ -658,7 +654,7 @@
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-shift-a": "assistant2::ToggleContextPicker", "cmd-shift-a": "assistant2::ToggleContextPicker",
"cmd-alt-/": "assistant::ToggleModelSelector", "cmd-alt-/": "assistant2::ToggleModelSelector",
"cmd-alt-e": "assistant2::RemoveAllContext", "cmd-alt-e": "assistant2::RemoveAllContext",
"ctrl-[": "assistant::CyclePreviousInlineAssist", "ctrl-[": "assistant::CyclePreviousInlineAssist",
"ctrl-]": "assistant::CycleNextInlineAssist" "ctrl-]": "assistant::CycleNextInlineAssist"
@@ -739,15 +735,7 @@
"alt-down": "git_panel::FocusEditor", "alt-down": "git_panel::FocusEditor",
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus"
"cmd-enter": "git::Commit"
}
},
{
"context": "GitDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "git::Commit"
} }
}, },
{ {
@@ -761,14 +749,6 @@
"alt-up": "git_panel::FocusChanges" "alt-up": "git_panel::FocusChanges"
} }
}, },
{
"context": "GitCommit > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
"cmd-enter": "git::Commit"
}
},
{ {
"context": "CollabPanel && not_editing", "context": "CollabPanel && not_editing",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -839,7 +819,6 @@
"cmd-k": "terminal::Clear", "cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal", "cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist", "ctrl-enter": "assistant::InlineAssist",
"ctrl-_": null, // emacs undo
// Some nice conveniences // Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"], "cmd-backspace": ["terminal::SendText", "\u0015"],
"cmd-right": ["terminal::SendText", "\u0005"], "cmd-right": ["terminal::SendText", "\u0005"],

View File

@@ -28,7 +28,6 @@
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line "ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word "alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word "alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word "alt-u": "editor::ConvertToUpperCase", // upcase-word
@@ -49,8 +48,6 @@
"ctrl-_": "editor::Undo", // undo "ctrl-_": "editor::Undo", // undo
"ctrl-/": "editor::Undo", // undo "ctrl-/": "editor::Undo", // undo
"ctrl-x u": "editor::Undo", // undo "ctrl-x u": "editor::Undo", // undo
"alt-{": "editor::MoveToStartOfParagraph", // backward-paragraph
"alt-}": "editor::MoveToEndOfParagraph", // forward-paragraph
"ctrl-v": "editor::MovePageDown", // scroll-up "ctrl-v": "editor::MovePageDown", // scroll-up
"alt-v": "editor::MovePageUp", // scroll-down "alt-v": "editor::MovePageUp", // scroll-down
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer "ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer

View File

@@ -2,8 +2,8 @@
{ {
"bindings": { "bindings": {
"ctrl-alt-s": "zed::OpenSettings", "ctrl-alt-s": "zed::OpenSettings",
"ctrl-{": "pane::ActivatePrevItem", "ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem" "ctrl-shift-]": "pane::ActivateNextItem"
} }
}, },
{ {

View File

@@ -1,8 +1,8 @@
[ [
{ {
"bindings": { "bindings": {
"ctrl-{": "pane::ActivatePrevItem", "ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem", "ctrl-shift-]": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-1": ["workspace::ActivatePane", 0], "ctrl-1": ["workspace::ActivatePane", 0],
@@ -14,15 +14,15 @@
"ctrl-7": ["workspace::ActivatePane", 6], "ctrl-7": ["workspace::ActivatePane", 6],
"ctrl-8": ["workspace::ActivatePane", 7], "ctrl-8": ["workspace::ActivatePane", 7],
"ctrl-9": ["workspace::ActivatePane", 8], "ctrl-9": ["workspace::ActivatePane", 8],
"ctrl-!": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }], "ctrl-shift-1": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
"ctrl-@": ["workspace::MoveItemToPane", { "destination": 1 }], "ctrl-shift-2": ["workspace::MoveItemToPane", { "destination": 1 }],
"ctrl-#": ["workspace::MoveItemToPane", { "destination": 2 }], "ctrl-shift-3": ["workspace::MoveItemToPane", { "destination": 2 }],
"ctrl-$": ["workspace::MoveItemToPane", { "destination": 3 }], "ctrl-shift-4": ["workspace::MoveItemToPane", { "destination": 3 }],
"ctrl-%": ["workspace::MoveItemToPane", { "destination": 4 }], "ctrl-shift-5": ["workspace::MoveItemToPane", { "destination": 4 }],
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }], "ctrl-shift-6": ["workspace::MoveItemToPane", { "destination": 5 }],
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }], "ctrl-shift-7": ["workspace::MoveItemToPane", { "destination": 6 }],
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }], "ctrl-shift-8": ["workspace::MoveItemToPane", { "destination": 7 }],
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }] "ctrl-shift-9": ["workspace::MoveItemToPane", { "destination": 8 }]
} }
}, },
{ {

View File

@@ -28,7 +28,6 @@
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line "ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word "alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word "alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word "alt-u": "editor::ConvertToUpperCase", // upcase-word
@@ -49,8 +48,6 @@
"ctrl-_": "editor::Undo", // undo "ctrl-_": "editor::Undo", // undo
"ctrl-/": "editor::Undo", // undo "ctrl-/": "editor::Undo", // undo
"ctrl-x u": "editor::Undo", // undo "ctrl-x u": "editor::Undo", // undo
"alt-{": "editor::MoveToStartOfParagraph", // backward-paragraph
"alt-}": "editor::MoveToEndOfParagraph", // forward-paragraph
"ctrl-v": "editor::MovePageDown", // scroll-up "ctrl-v": "editor::MovePageDown", // scroll-up
"alt-v": "editor::MovePageUp", // scroll-down "alt-v": "editor::MovePageUp", // scroll-down
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer "ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer

View File

@@ -1,8 +1,8 @@
[ [
{ {
"bindings": { "bindings": {
"cmd-{": "pane::ActivatePrevItem", "cmd-shift-[": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem" "cmd-shift-]": "pane::ActivateNextItem"
} }
}, },
{ {

View File

@@ -1,8 +1,8 @@
[ [
{ {
"bindings": { "bindings": {
"cmd-{": "pane::ActivatePrevItem", "cmd-shift-[": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem", "cmd-shift-]": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-1": ["workspace::ActivatePane", 0], "ctrl-1": ["workspace::ActivatePane", 0],
@@ -14,15 +14,15 @@
"ctrl-7": ["workspace::ActivatePane", 6], "ctrl-7": ["workspace::ActivatePane", 6],
"ctrl-8": ["workspace::ActivatePane", 7], "ctrl-8": ["workspace::ActivatePane", 7],
"ctrl-9": ["workspace::ActivatePane", 8], "ctrl-9": ["workspace::ActivatePane", 8],
"ctrl-!": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }], "ctrl-shift-1": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
"ctrl-@": ["workspace::MoveItemToPane", { "destination": 1 }], "ctrl-shift-2": ["workspace::MoveItemToPane", { "destination": 1 }],
"ctrl-#": ["workspace::MoveItemToPane", { "destination": 2 }], "ctrl-shift-3": ["workspace::MoveItemToPane", { "destination": 2 }],
"ctrl-$": ["workspace::MoveItemToPane", { "destination": 3 }], "ctrl-shift-4": ["workspace::MoveItemToPane", { "destination": 3 }],
"ctrl-%": ["workspace::MoveItemToPane", { "destination": 4 }], "ctrl-shift-5": ["workspace::MoveItemToPane", { "destination": 4 }],
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }], "ctrl-shift-6": ["workspace::MoveItemToPane", { "destination": 5 }],
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }], "ctrl-shift-7": ["workspace::MoveItemToPane", { "destination": 6 }],
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }], "ctrl-shift-8": ["workspace::MoveItemToPane", { "destination": 7 }],
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }] "ctrl-shift-9": ["workspace::MoveItemToPane", { "destination": 8 }]
} }
}, },
{ {

View File

@@ -293,7 +293,6 @@
"!": "vim::ShellCommand", "!": "vim::ShellCommand",
"i": ["vim::PushObject", { "around": false }], "i": ["vim::PushObject", { "around": false }],
"a": ["vim::PushObject", { "around": true }], "a": ["vim::PushObject", { "around": true }],
"g r": ["vim::Paste", { "preserve_clipboard": true }],
"g c": "vim::ToggleComments", "g c": "vim::ToggleComments",
"g q": "vim::Rewrap", "g q": "vim::Rewrap",
"\"": "vim::PushRegister", "\"": "vim::PushRegister",
@@ -437,7 +436,6 @@
"context": "vim_operator == c", "context": "vim_operator == c",
"bindings": { "bindings": {
"c": "vim::CurrentLine", "c": "vim::CurrentLine",
"x": "vim::Exchange",
"d": "editor::Rename", // zed specific "d": "editor::Rename", // zed specific
"s": ["vim::PushChangeSurrounds", {}] "s": ["vim::PushChangeSurrounds", {}]
} }
@@ -524,19 +522,6 @@
"c": "vim::CurrentLine" "c": "vim::CurrentLine"
} }
}, },
{
"context": "vim_operator == gr",
"bindings": {
"r": "vim::CurrentLine"
}
},
{
"context": "vim_operator == cx",
"bindings": {
"x": "vim::CurrentLine",
"c": "vim::ClearExchange"
}
},
{ {
"context": "vim_mode == literal", "context": "vim_mode == literal",
"bindings": { "bindings": {

View File

@@ -176,8 +176,8 @@
"show_completion_documentation": true, "show_completion_documentation": true,
// Show method signatures in the editor, when inside parentheses. // Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false, "auto_signature_help": false,
// Whether to show the signature help after completion or a bracket pair inserted. /// 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. /// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false, "show_signature_help_after_edits": false,
// Whether to show wrap guides (vertical rulers) in the editor. // Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value // 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 // - "information": show only errors, warnings, and information
// - "all" or true: show all diagnostics // - "all" or true: show all diagnostics
"diagnostics": "all", "diagnostics": "all",
// Forcefully enable or disable the scrollbar for each axis /// Forcefully enable or disable the scrollbar for each axis
"axes": { "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, "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 "vertical": true
} }
}, },
@@ -328,24 +328,24 @@
"folds": true "folds": true
}, },
"indent_guides": { "indent_guides": {
// Whether to show indent guides in the editor. /// Whether to show indent guides in the editor.
"enabled": true, "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, "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, "active_line_width": 1,
// Determines how indent guides are colored. /// Determines how indent guides are colored.
// This setting can take the following three values: /// This setting can take the following three values:
/// ///
// 1. "disabled" /// 1. "disabled"
// 2. "fixed" /// 2. "fixed"
// 3. "indent_aware" /// 3. "indent_aware"
"coloring": "fixed", "coloring": "fixed",
// Determines how indent guide backgrounds are colored. /// Determines how indent guide backgrounds are colored.
// This setting can take the following two values: /// This setting can take the following two values:
/// ///
// 1. "disabled" /// 1. "disabled"
// 2. "indent_aware" /// 2. "indent_aware"
"background_coloring": "disabled" "background_coloring": "disabled"
}, },
// Whether the editor will scroll beyond the last line. // Whether the editor will scroll beyond the last line.
@@ -379,9 +379,6 @@
// 3. Never populate the search query // 3. Never populate the search query
// "never" // "never"
"seed_search_query_from_cursor": "always", "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, "use_smartcase_search": false,
// Inlay hint related settings // Inlay hint related settings
"inlay_hints": { "inlay_hints": {
@@ -401,16 +398,7 @@
"edit_debounce_ms": 700, "edit_debounce_ms": 700,
// Time to wait after scrolling the buffer, before requesting the hints, // Time to wait after scrolling the buffer, before requesting the hints,
// set to 0 to disable debouncing. // set to 0 to disable debouncing.
"scroll_debounce_ms": 50, "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
}
}, },
"project_panel": { "project_panel": {
// Whether to show the project panel button in the status bar // Whether to show the project panel button in the status bar
@@ -436,32 +424,32 @@
// Whether to fold directories automatically and show compact folders // Whether to fold directories automatically and show compact folders
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside. // (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
"auto_fold_dirs": true, "auto_fold_dirs": true,
// Scrollbar-related settings /// Scrollbar-related settings
"scrollbar": { "scrollbar": {
// When to show the scrollbar in the project panel. /// When to show the scrollbar in the project panel.
// This setting can take five values: /// This setting can take five values:
/// ///
// 1. null (default): Inherit editor settings /// 1. null (default): Inherit editor settings
// 2. Show the scrollbar if there's important information or /// 2. Show the scrollbar if there's important information or
// follow the system's configured behavior (default): /// follow the system's configured behavior (default):
// "auto" /// "auto"
// 3. Match the system's configured behavior: /// 3. Match the system's configured behavior:
// "system" /// "system"
// 4. Always show the scrollbar: /// 4. Always show the scrollbar:
// "always" /// "always"
// 5. Never show the scrollbar: /// 5. Never show the scrollbar:
// "never" /// "never"
"show": null "show": null
}, },
// Which files containing diagnostic errors/warnings to mark in the project panel. /// Which files containing diagnostic errors/warnings to mark in the project panel.
// This setting can take the following three values: /// This setting can take the following three values:
/// ///
// 1. Do not mark any files: /// 1. Do not mark any files:
// "off" /// "off"
// 2. Only mark files with errors: /// 2. Only mark files with errors:
// "errors" /// "errors"
// 3. Mark files with errors and warnings: /// 3. Mark files with errors and warnings:
// "all" /// "all"
"show_diagnostics": "all", "show_diagnostics": "all",
// Settings related to indent guides in the project panel. // Settings related to indent guides in the project panel.
"indent_guides": { "indent_guides": {
@@ -494,8 +482,8 @@
// when a corresponding outline entry becomes active. // when a corresponding outline entry becomes active.
// Gitignored entries are never auto revealed. // Gitignored entries are never auto revealed.
"auto_reveal_entries": true, "auto_reveal_entries": true,
// Whether to fold directories automatically /// Whether to fold directories automatically
// when a directory has only one directory inside. /// when a directory has only one directory inside.
"auto_fold_dirs": true, "auto_fold_dirs": true,
// Settings related to indent guides in the outline panel. // Settings related to indent guides in the outline panel.
"indent_guides": { "indent_guides": {
@@ -508,21 +496,21 @@
// "never" // "never"
"show": "always" "show": "always"
}, },
// Scrollbar-related settings /// Scrollbar-related settings
"scrollbar": { "scrollbar": {
// When to show the scrollbar in the project panel. /// When to show the scrollbar in the project panel.
// This setting can take five values: /// This setting can take five values:
/// ///
// 1. null (default): Inherit editor settings /// 1. null (default): Inherit editor settings
// 2. Show the scrollbar if there's important information or /// 2. Show the scrollbar if there's important information or
// follow the system's configured behavior (default): /// follow the system's configured behavior (default):
// "auto" /// "auto"
// 3. Match the system's configured behavior: /// 3. Match the system's configured behavior:
// "system" /// "system"
// 4. Always show the scrollbar: /// 4. Always show the scrollbar:
// "always" /// "always"
// 5. Never show the scrollbar: /// 5. Never show the scrollbar:
// "never" /// "never"
"show": null "show": null
} }
}, },
@@ -593,7 +581,7 @@
// The provider to use. // The provider to use.
"provider": "zed.dev", "provider": "zed.dev",
// The model to use. // The model to use.
"model": "claude-3-5-sonnet-latest" "model": "claude-3-5-sonnet"
} }
}, },
// The settings for slash commands. // The settings for slash commands.
@@ -640,7 +628,7 @@
"show": true, "show": true,
// Whether or not to show the navigation history buttons. // Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true, "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 "show_tab_bar_buttons": true
}, },
// Settings related to the editor's tabs // Settings related to the editor's tabs
@@ -662,16 +650,16 @@
// 3. Activate the left neighbour tab if present // 3. Activate the left neighbour tab if present
// "left_neighbour" // "left_neighbour"
"activate_on_close": "history", "activate_on_close": "history",
// Which files containing diagnostic errors/warnings to mark in the tabs. /// Which files containing diagnostic errors/warnings to mark in the tabs.
// Diagnostics are only shown when file icons are also active. /// Diagnostics are only shown when file icons are also active.
// This setting only works when can take the following three values: /// This setting only works when can take the following three values:
/// ///
// 1. Do not mark any files: /// 1. Do not mark any files:
// "off" /// "off"
// 2. Only mark files with errors: /// 2. Only mark files with errors:
// "errors" /// "errors"
// 3. Mark files with errors and warnings: /// 3. Mark files with errors and warnings:
// "all" /// "all"
"show_diagnostics": "off" "show_diagnostics": "off"
}, },
// Settings related to preview tabs. // Settings related to preview tabs.
@@ -770,25 +758,7 @@
// Diagnostics configuration. // Diagnostics configuration.
"diagnostics": { "diagnostics": {
// Whether to show warnings or not by default. // Whether to show warnings or not by default.
"include_warnings": true, "include_warnings": true
// Settings for inline diagnostics
"inline": {
// Whether to show diagnostics inline or not
"enabled": false,
// The delay in milliseconds to show inline diagnostics after the
// last diagnostic update.
"update_debounce_ms": 150,
// The amount of padding between the end of the source line and the start
// of the inline diagnostic in units of em widths.
"padding": 4,
// The minimum column to display inline diagnostics. This setting can be
// used to horizontally align inline diagnostics at some column. Lines
// longer than this value will still push diagnostics further to the right.
"min_column": 0,
// The minimum severity of the diagnostics to show inline.
// Shows all diagnostics when not specified.
"max_severity": null
}
}, },
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file // Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
// scans, file searches, and not be displayed in the project file tree. Takes precedence over `file_scan_inclusions`. // scans, file searches, and not be displayed in the project file tree. Takes precedence over `file_scan_inclusions`.
@@ -841,8 +811,6 @@
// A list of globs representing files that edit predictions should be disabled for. // A list of globs representing files that edit predictions should be disabled for.
// There's a sensible default list of globs already included. // There's a sensible default list of globs already included.
// Any addition to this list will be merged with the default list. // 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": [ "disabled_globs": [
"**/.env*", "**/.env*",
"**/*.pem", "**/*.pem",
@@ -854,14 +822,11 @@
], ],
// When to show edit predictions previews in buffer. // When to show edit predictions previews in buffer.
// This setting takes two possible values: // This setting takes two possible values:
// 1. Display predictions inline when there are no language server completions available. // 1. Display inline when there are no language server completions available.
// "mode": "eager" // "mode": "eager_preview"
// 2. Display predictions inline only when holding a modifier key (alt by default). // 2. Display inline when holding modifier key (alt by default).
// "mode": "subtle" // "mode": "auto"
"mode": "eager", "mode": "eager_preview"
// Whether edit predictions are enabled in the assistant panel.
// This setting has no effect if globally disabled.
"enabled_in_assistant": true
}, },
// Settings specific to journaling // Settings specific to journaling
"journal": { "journal": {
@@ -997,21 +962,21 @@
// Example: `echo -e "\e]2;New Title\007";` // Example: `echo -e "\e]2;New Title\007";`
"breadcrumbs": true "breadcrumbs": true
}, },
// Scrollbar-related settings /// Scrollbar-related settings
"scrollbar": { "scrollbar": {
// When to show the scrollbar in the terminal. /// When to show the scrollbar in the terminal.
// This setting can take five values: /// This setting can take five values:
/// ///
// 1. null (default): Inherit editor settings /// 1. null (default): Inherit editor settings
// 2. Show the scrollbar if there's important information or /// 2. Show the scrollbar if there's important information or
// follow the system's configured behavior (default): /// follow the system's configured behavior (default):
// "auto" /// "auto"
// 3. Match the system's configured behavior: /// 3. Match the system's configured behavior:
// "system" /// "system"
// 4. Always show the scrollbar: /// 4. Always show the scrollbar:
// "always" /// "always"
// 5. Never show the scrollbar: /// 5. Never show the scrollbar:
// "never" /// "never"
"show": null "show": null
} }
// Set the terminal's font size. If this option is not included, // Set the terminal's font size. If this option is not included,
@@ -1030,7 +995,7 @@
// "max_scroll_history_lines": 10000, // "max_scroll_history_lines": 10000,
}, },
"code_actions_on_format": {}, "code_actions_on_format": {},
// Settings related to running tasks. /// Settings related to running tasks.
"tasks": { "tasks": {
"variables": {} "variables": {}
}, },
@@ -1051,20 +1016,20 @@
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"], "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"Shell Script": [".env.*"] "Shell Script": [".env.*"]
}, },
// By default use a recent system version of node, or install our own. /// 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: /// You can override this to use a version of node that is not in $PATH with:
// { /// {
// "node": { /// "node": {
// "path": "/path/to/node" /// "path": "/path/to/node"
// "npm_path": "/path/to/npm" (defaults to node_path/../npm) /// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
// } /// }
// } /// }
// or to ensure Zed always downloads and installs an isolated version of node: /// or to ensure Zed always downloads and installs an isolated version of node:
// { /// {
// "node": { /// "node": {
// "ignore_system_version": true, /// "ignore_system_version": true,
// } /// }
// NOTE: changing this setting currently requires restarting Zed. /// NOTE: changing this setting currently requires restarting Zed.
"node": {}, "node": {},
// The extensions that Zed should automatically install on startup. // The extensions that Zed should automatically install on startup.
// //
@@ -1110,7 +1075,6 @@
"tab_size": 2 "tab_size": 2
}, },
"Diff": { "Diff": {
"show_edit_predictions": false,
"remove_trailing_whitespace_on_save": false, "remove_trailing_whitespace_on_save": false,
"ensure_final_newline_on_save": false "ensure_final_newline_on_save": false
}, },
@@ -1120,9 +1084,6 @@
"Erlang": { "Erlang": {
"language_servers": ["erlang-ls", "!elp", "..."] "language_servers": ["erlang-ls", "!elp", "..."]
}, },
"Git Commit": {
"allow_rewrap": "anywhere"
},
"Go": { "Go": {
"code_actions_on_format": { "code_actions_on_format": {
"source.organizeImports": true "source.organizeImports": true
@@ -1313,7 +1274,6 @@
}, },
// Vim settings // Vim settings
"vim": { "vim": {
"default_mode": "normal",
"toggle_relative_line_numbers": false, "toggle_relative_line_numbers": false,
"use_system_clipboard": "always", "use_system_clipboard": "always",
"use_multiline_find": false, "use_multiline_find": false,

View File

@@ -379,7 +379,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#ebdbb2ff", "color": "#83a598ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
@@ -767,7 +767,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#ebdbb2ff", "color": "#83a598ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
@@ -1155,7 +1155,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#ebdbb2ff", "color": "#83a598ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
@@ -1543,7 +1543,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#282828ff", "color": "#066578ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
@@ -1931,7 +1931,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#282828ff", "color": "#066578ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
@@ -2319,7 +2319,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#282828ff", "color": "#066578ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },

View File

@@ -365,7 +365,7 @@
"font_weight": null "font_weight": null
}, },
"variable": { "variable": {
"color": "#acb2beff", "color": "#dce0e5ff",
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },

View File

@@ -30,8 +30,6 @@ pub enum Model {
#[default] #[default]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")] #[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet, Claude3_5Sonnet,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
Claude3_7Sonnet,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")] #[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku, Claude3_5Haiku,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")] #[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
@@ -61,8 +59,6 @@ impl Model {
pub fn from_id(id: &str) -> Result<Self> { pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-3-5-sonnet") { if id.starts_with("claude-3-5-sonnet") {
Ok(Self::Claude3_5Sonnet) Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-7-sonnet") {
Ok(Self::Claude3_7Sonnet)
} else if id.starts_with("claude-3-5-haiku") { } else if id.starts_with("claude-3-5-haiku") {
Ok(Self::Claude3_5Haiku) Ok(Self::Claude3_5Haiku)
} else if id.starts_with("claude-3-opus") { } else if id.starts_with("claude-3-opus") {
@@ -79,7 +75,6 @@ impl Model {
pub fn id(&self) -> &str { pub fn id(&self) -> &str {
match self { match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest", Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
Model::Claude3_5Haiku => "claude-3-5-haiku-latest", Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
Model::Claude3Opus => "claude-3-opus-latest", Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-20240229", Model::Claude3Sonnet => "claude-3-sonnet-20240229",
@@ -90,7 +85,6 @@ impl Model {
pub fn display_name(&self) -> &str { pub fn display_name(&self) -> &str {
match self { match self {
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet", Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_5Haiku => "Claude 3.5 Haiku", Self::Claude3_5Haiku => "Claude 3.5 Haiku",
Self::Claude3Opus => "Claude 3 Opus", Self::Claude3Opus => "Claude 3 Opus",
@@ -104,14 +98,13 @@ impl Model {
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> { pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
match self { match self {
Self::Claude3_5Sonnet Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
| Self::Claude3_5Haiku Some(AnthropicModelCacheConfiguration {
| Self::Claude3_7Sonnet
| Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048, min_total_token: 2_048,
should_speculate: true, should_speculate: true,
max_cache_anchors: 4, max_cache_anchors: 4,
}), })
}
Self::Custom { Self::Custom {
cache_configuration, cache_configuration,
.. ..
@@ -124,7 +117,6 @@ impl Model {
match self { match self {
Self::Claude3_5Sonnet Self::Claude3_5Sonnet
| Self::Claude3_5Haiku | Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
| Self::Claude3Opus | Self::Claude3Opus
| Self::Claude3Sonnet | Self::Claude3Sonnet
| Self::Claude3Haiku => 200_000, | Self::Claude3Haiku => 200_000,
@@ -135,7 +127,7 @@ impl Model {
pub fn max_output_tokens(&self) -> u32 { pub fn max_output_tokens(&self) -> u32 {
match self { match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096, Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
Self::Claude3_5Sonnet | Self::Claude3_7Sonnet | Self::Claude3_5Haiku => 8_192, Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
Self::Custom { Self::Custom {
max_output_tokens, .. max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096), } => max_output_tokens.unwrap_or(4_096),
@@ -145,7 +137,6 @@ impl Model {
pub fn default_temperature(&self) -> f32 { pub fn default_temperature(&self) -> f32 {
match self { match self {
Self::Claude3_5Sonnet Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_5Haiku | Self::Claude3_5Haiku
| Self::Claude3Opus | Self::Claude3Opus
| Self::Claude3Sonnet | Self::Claude3Sonnet

View File

@@ -43,6 +43,7 @@ indoc.workspace = true
language.workspace = true language.workspace = true
language_model.workspace = true language_model.workspace = true
language_model_selector.workspace = true language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true log.workspace = true
lsp.workspace = true lsp.workspace = true
menu.workspace = true menu.workspace = true

View File

@@ -33,7 +33,7 @@ actions!(
[ [
InsertActivePrompt, InsertActivePrompt,
DeployHistory, DeployHistory,
NewChat, NewContext,
CycleNextInlineAssist, CycleNextInlineAssist,
CyclePreviousInlineAssist CyclePreviousInlineAssist
] ]

View File

@@ -1,6 +1,6 @@
use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent}; use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
use crate::{ use crate::{
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewChat, terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewContext,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use assistant_context_editor::{ use assistant_context_editor::{
@@ -129,7 +129,7 @@ impl AssistantPanel {
workspace.project().clone(), workspace.project().clone(),
Default::default(), Default::default(),
None, None,
NewChat.boxed_clone(), NewContext.boxed_clone(),
window, window,
cx, cx,
); );
@@ -228,12 +228,12 @@ impl AssistantPanel {
IconButton::new("new-chat", IconName::Plus) IconButton::new("new-chat", IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.on_click(cx.listener(|_, _, window, cx| { .on_click(cx.listener(|_, _, window, cx| {
window.dispatch_action(NewChat.boxed_clone(), cx) window.dispatch_action(NewContext.boxed_clone(), cx)
})) }))
.tooltip(move |window, cx| { .tooltip(move |window, cx| {
Tooltip::for_action_in( Tooltip::for_action_in(
"New Chat", "New Chat",
&NewChat, &NewContext,
&focus_handle, &focus_handle,
window, window,
cx, cx,
@@ -256,7 +256,7 @@ impl AssistantPanel {
let focus_handle = _pane.focus_handle(cx); let focus_handle = _pane.focus_handle(cx);
Some(ContextMenu::build(window, cx, move |menu, _, _| { Some(ContextMenu::build(window, cx, move |menu, _, _| {
menu.context(focus_handle.clone()) menu.context(focus_handle.clone())
.action("New Chat", Box::new(NewChat)) .action("New Chat", Box::new(NewContext))
.action("History", Box::new(DeployHistory)) .action("History", Box::new(DeployHistory))
.action("Prompt Library", Box::new(DeployPromptLibrary)) .action("Prompt Library", Box::new(DeployPromptLibrary))
.action("Configure", Box::new(ShowConfiguration)) .action("Configure", Box::new(ShowConfiguration))
@@ -760,7 +760,7 @@ impl AssistantPanel {
pub fn create_new_context( pub fn create_new_context(
workspace: &mut Workspace, workspace: &mut Workspace,
_: &NewChat, _: &NewContext,
window: &mut Window, window: &mut Window,
cx: &mut Context<Workspace>, cx: &mut Context<Workspace>,
) { ) {
@@ -978,7 +978,7 @@ impl AssistantPanel {
.active_provider() .active_provider()
.map_or(true, |p| p.id() != provider.id()) .map_or(true, |p| p.id() != provider.id())
{ {
if let Some(model) = provider.default_model(cx) { if let Some(model) = provider.provided_models(cx).first().cloned() {
update_settings_file::<AssistantSettings>( update_settings_file::<AssistantSettings>(
this.fs.clone(), this.fs.clone(),
cx, cx,
@@ -1206,7 +1206,7 @@ impl Render for AssistantPanel {
v_flex() v_flex()
.key_context("AssistantPanel") .key_context("AssistantPanel")
.size_full() .size_full()
.on_action(cx.listener(|this, _: &NewChat, window, cx| { .on_action(cx.listener(|this, _: &NewContext, window, cx| {
this.new_context(window, cx); this.new_context(window, cx);
})) }))
.on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| { .on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| {

View File

@@ -32,10 +32,11 @@ use gpui::{
}; };
use language::{line_diff, Buffer, IndentKind, Point, Selection, TransactionId}; use language::{line_diff, Buffer, IndentKind, Point, Selection, TransactionId};
use language_model::{ use language_model::{
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestMessage, LanguageModelTextStream, Role, LanguageModelTextStream, Role,
}; };
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector}; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_models::report_assistant_event;
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
@@ -1589,10 +1590,29 @@ impl Render for PromptEditor {
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)) .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.justify_center() .justify_center()
.gap_2() .gap_2()
.child( .child(LanguageModelSelectorPopoverMenu::new(
InlineLanguageModelSelector::new(self.language_model_selector.clone()) self.language_model_selector.clone(),
.render(window, cx), 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| { .map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else { let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
return el; return el;
@@ -2207,7 +2227,7 @@ impl PromptEditor {
}, },
font_family: settings.buffer_font.family.clone(), font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(), font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(), font_size: settings.buffer_font_size.into(),
font_weight: settings.buffer_font.weight, font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()), line_height: relative(settings.buffer_line_height.value()),
..Default::default() ..Default::default()

View File

@@ -16,10 +16,10 @@ use gpui::{
}; };
use language::Buffer; use language::Buffer;
use language_model::{ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
LanguageModelRequestMessage, Role,
}; };
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector}; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_models::report_assistant_event;
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use std::{ use std::{
@@ -506,7 +506,7 @@ struct PromptEditor {
impl EventEmitter<PromptEditorEvent> for PromptEditor {} impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render 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 status = &self.codegen.read(cx).status;
let buttons = match status { let buttons = match status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
@@ -641,10 +641,29 @@ impl Render for PromptEditor {
.w_12() .w_12()
.justify_center() .justify_center()
.gap_2() .gap_2()
.child( .child(LanguageModelSelectorPopoverMenu::new(
InlineLanguageModelSelector::new(self.language_model_selector.clone()) self.language_model_selector.clone(),
.render(window, cx), 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( .children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status { if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string()); let error_message = SharedString::from(error.to_string());
@@ -1030,7 +1049,7 @@ impl PromptEditor {
}, },
font_family: settings.buffer_font.family.clone(), font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(), font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(), font_size: settings.buffer_font_size.into(),
font_weight: settings.buffer_font.weight, font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()), line_height: relative(settings.buffer_line_height.value()),
..Default::default() ..Default::default()
@@ -1054,10 +1073,7 @@ pub enum CodegenEvent {
impl EventEmitter<CodegenEvent> for Codegen {} impl EventEmitter<CodegenEvent> for Codegen {}
#[cfg(not(target_os = "windows"))]
const CLEAR_INPUT: &str = "\x15"; const CLEAR_INPUT: &str = "\x15";
#[cfg(target_os = "windows")]
const CLEAR_INPUT: &str = "\x03";
const CARRIAGE_RETURN: &str = "\x0d"; const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction { struct TerminalTransaction {

View File

@@ -46,6 +46,7 @@ itertools.workspace = true
language.workspace = true language.workspace = true
language_model.workspace = true language_model.workspace = true
language_model_selector.workspace = true language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true log.workspace = true
lsp.workspace = true lsp.workspace = true
markdown.workspace = true markdown.workspace = true
@@ -73,7 +74,6 @@ time_format.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true
uuid.workspace = true uuid.workspace = true
vim_mode_setting.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true

View File

@@ -8,16 +8,15 @@ use gpui::{
UnderlineStyle, WeakEntity, UnderlineStyle, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role}; use language_model::Role;
use markdown::{Markdown, MarkdownStyle}; use markdown::{Markdown, MarkdownStyle};
use settings::Settings as _; use settings::Settings as _;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, Disclosure}; use ui::prelude::*;
use workspace::Workspace; 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::thread_store::ThreadStore;
use crate::tool_use::{ToolUse, ToolUseStatus};
use crate::ui::ContextPill; use crate::ui::ContextPill;
pub struct ActiveThread { pub struct ActiveThread {
@@ -29,7 +28,6 @@ pub struct ActiveThread {
messages: Vec<MessageId>, messages: Vec<MessageId>,
list_state: ListState, list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>, rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
last_error: Option<ThreadError>, last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -57,7 +55,6 @@ impl ActiveThread {
thread: thread.clone(), thread: thread.clone(),
messages: Vec::new(), messages: Vec::new(),
rendered_messages_by_id: HashMap::default(), rendered_messages_by_id: HashMap::default(),
expanded_tool_uses: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), { list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade(); let this = cx.entity().downgrade();
move |ix, _: &mut Window, cx: &mut App| { 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); let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
self.thread.update(cx, |thread, cx| { self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(tool_use.id.clone(), task, cx); thread.insert_tool_output(
}); tool_use.assistant_message_id,
} 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);
}); });
} }
} }
} }
ThreadEvent::ToolFinished { .. } => {}
} }
} }
@@ -291,17 +276,8 @@ impl ActiveThread {
}; };
let context = self.thread.read(cx).context_for_message(message_id); 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(); 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() let message_content = v_flex()
.child(div().p_2p5().text_ui(cx).child(markdown.clone())) .child(div().p_2p5().text_ui(cx).child(markdown.clone()))
.when_some(context, |parent, context| { .when_some(context, |parent, context| {
@@ -356,22 +332,7 @@ impl ActiveThread {
) )
.child(message_content), .child(message_content),
), ),
Role::Assistant => div() Role::Assistant => div().id(("message-container", ix)).child(message_content),
.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::System => div().id(("message-container", ix)).py_1().px_2().child( Role::System => div().id(("message-container", ix)).py_1().px_2().child(
v_flex() v_flex()
.bg(colors.editor_background) .bg(colors.editor_background)
@@ -382,102 +343,6 @@ impl ActiveThread {
styled_message.into_any() 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 { impl Render for ActiveThread {

View File

@@ -7,7 +7,6 @@ mod context;
mod context_picker; mod context_picker;
mod context_store; mod context_store;
mod context_strip; mod context_strip;
mod history_store;
mod inline_assistant; mod inline_assistant;
mod inline_prompt_editor; mod inline_prompt_editor;
mod message_editor; mod message_editor;
@@ -16,7 +15,6 @@ mod terminal_inline_assistant;
mod thread; mod thread;
mod thread_history; mod thread_history;
mod thread_store; mod thread_store;
mod tool_use;
mod ui; mod ui;
use std::sync::Arc; use std::sync::Arc;
@@ -39,8 +37,10 @@ actions!(
NewThread, NewThread,
NewPromptEditor, NewPromptEditor,
ToggleContextPicker, ToggleContextPicker,
ToggleModelSelector,
RemoveAllContext, RemoveAllContext,
OpenHistory, OpenHistory,
OpenPromptEditorHistory,
OpenConfiguration, OpenConfiguration,
RemoveSelectedThread, RemoveSelectedThread,
Chat, Chat,

View File

@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use gpui::{Action, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription}; use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, Divider, DividerColor, ElevationIndex}; use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
use zed_actions::assistant::DeployPromptLibrary; use zed_actions::assistant::DeployPromptLibrary;
@@ -158,16 +158,8 @@ impl Render for AssistantConfiguration {
.child( .child(
v_flex() v_flex()
.p(DynamicSpacing::Base16.rems(cx)) .p(DynamicSpacing::Base16.rems(cx))
.gap_2() .gap_1()
.child(
v_flex()
.gap_0p5()
.child(Headline::new("Prompt Library").size(HeadlineSize::Small)) .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),
),
)
.child( .child(
Button::new("open-prompt-library", "Open Prompt Library") Button::new("open-prompt-library", "Open Prompt Library")
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
@@ -176,8 +168,8 @@ impl Render for AssistantConfiguration {
.icon(IconName::Book) .icon(IconName::Book)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.on_click(|_event, window, cx| { .on_click(|_event, _window, cx| {
window.dispatch_action(DeployPromptLibrary.boxed_clone(), cx) cx.dispatch_action(&DeployPromptLibrary)
}), }),
), ),
) )

View File

@@ -1,19 +1,24 @@
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use fs::Fs; use fs::Fs;
use gpui::{Entity, FocusHandle}; use gpui::{Entity, FocusHandle, SharedString};
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector}; use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file; use settings::update_settings_file;
use std::sync::Arc; use std::sync::Arc;
use ui::prelude::*; use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::ToggleModelSelector;
pub struct AssistantModelSelector { pub struct AssistantModelSelector {
pub selector: Entity<LanguageModelSelector>, selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
impl AssistantModelSelector { impl AssistantModelSelector {
pub(crate) fn new( pub(crate) fn new(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@@ -33,14 +38,50 @@ impl AssistantModelSelector {
cx, cx,
) )
}), }),
menu_handle,
focus_handle, focus_handle,
} }
} }
} }
impl Render for AssistantModelSelector { impl Render for AssistantModelSelector {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
AssistantLanguageModelSelector::new(self.focus_handle.clone(), self.selector.clone()) let active_model = LanguageModelRegistry::read_global(cx).active_model();
.render(window, cx) 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())
} }
} }

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use assistant_context_editor::{ use assistant_context_editor::{
make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError, make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError,
ContextEditor, SlashCommandCompletionProvider, ContextEditor, ContextHistory, SlashCommandCompletionProvider,
}; };
use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
@@ -14,7 +14,7 @@ use client::zed_urls;
use editor::Editor; use editor::Editor;
use fs::Fs; use fs::Fs;
use gpui::{ 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, FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
@@ -31,12 +31,14 @@ use zed_actions::assistant::{DeployPromptLibrary, ToggleFocus};
use crate::active_thread::ActiveThread; use crate::active_thread::ActiveThread;
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent}; use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
use crate::history_store::{HistoryEntry, HistoryStore};
use crate::message_editor::MessageEditor; use crate::message_editor::MessageEditor;
use crate::thread::{Thread, ThreadError, ThreadId}; use crate::thread::{Thread, ThreadError, ThreadId};
use crate::thread_history::{PastContext, PastThread, ThreadHistory}; use crate::thread_history::{PastThread, ThreadHistory};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory}; use crate::{
InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory,
OpenPromptEditorHistory,
};
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
cx.observe_new( cx.observe_new(
@@ -60,6 +62,12 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx)); panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
} }
}) })
.register_action(|workspace, _: &OpenPromptEditorHistory, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(window, cx));
}
})
.register_action(|workspace, _: &OpenConfiguration, window, cx| { .register_action(|workspace, _: &OpenConfiguration, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx); workspace.focus_panel::<AssistantPanel>(window, cx);
@@ -75,6 +83,7 @@ enum ActiveView {
Thread, Thread,
PromptEditor, PromptEditor,
History, History,
PromptEditorHistory,
Configuration, Configuration,
} }
@@ -88,14 +97,15 @@ pub struct AssistantPanel {
message_editor: Entity<MessageEditor>, message_editor: Entity<MessageEditor>,
context_store: Entity<assistant_context_editor::ContextStore>, context_store: Entity<assistant_context_editor::ContextStore>,
context_editor: Option<Entity<ContextEditor>>, context_editor: Option<Entity<ContextEditor>>,
context_history: Option<Entity<ContextHistory>>,
configuration: Option<Entity<AssistantConfiguration>>, configuration: Option<Entity<AssistantConfiguration>>,
configuration_subscription: Option<Subscription>, configuration_subscription: Option<Subscription>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset, local_timezone: UtcOffset,
active_view: ActiveView, active_view: ActiveView,
history_store: Entity<HistoryStore>,
history: Entity<ThreadHistory>, history: Entity<ThreadHistory>,
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>, new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
width: Option<Pixels>, width: Option<Pixels>,
height: Option<Pixels>, height: Option<Pixels>,
} }
@@ -163,9 +173,6 @@ impl AssistantPanel {
) )
}); });
let history_store =
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
Self { Self {
active_view: ActiveView::Thread, active_view: ActiveView::Thread,
workspace: workspace.clone(), workspace: workspace.clone(),
@@ -187,6 +194,7 @@ impl AssistantPanel {
message_editor, message_editor,
context_store, context_store,
context_editor: None, context_editor: None,
context_history: None,
configuration: None, configuration: None,
configuration_subscription: None, configuration_subscription: None,
tools, tools,
@@ -194,9 +202,9 @@ impl AssistantPanel {
chrono::Local::now().offset().local_minus_utc(), chrono::Local::now().offset().local_minus_utc(),
) )
.unwrap(), .unwrap(),
history_store: history_store.clone(), history: cx.new(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, cx)),
new_item_context_menu_handle: PopoverMenuHandle::default(), new_item_context_menu_handle: PopoverMenuHandle::default(),
open_history_context_menu_handle: PopoverMenuHandle::default(),
width: None, width: None,
height: None, height: None,
} }
@@ -323,7 +331,26 @@ impl AssistantPanel {
cx.notify(); cx.notify();
} }
pub(crate) fn open_saved_prompt_editor( fn open_prompt_editor_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.active_view = ActiveView::PromptEditorHistory;
self.context_history = Some(cx.new(|cx| {
ContextHistory::new(
self.project.clone(),
self.context_store.clone(),
self.workspace.clone(),
window,
cx,
)
}));
if let Some(context_history) = self.context_history.as_ref() {
context_history.focus_handle(cx).focus(window);
}
cx.notify();
}
fn open_saved_prompt_editor(
&mut self, &mut self,
path: PathBuf, path: PathBuf,
window: &mut Window, window: &mut Window,
@@ -431,7 +458,7 @@ impl AssistantPanel {
active_provider.id() != provider.id() active_provider.id() != provider.id()
}) })
{ {
if let Some(model) = provider.default_model(cx) { if let Some(model) = provider.provided_models(cx).first().cloned() {
update_settings_file::<AssistantSettings>( update_settings_file::<AssistantSettings>(
self.fs.clone(), self.fs.clone(),
cx, cx,
@@ -458,12 +485,6 @@ impl AssistantPanel {
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> { pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
self.context_editor.clone() 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 { impl Focusable for AssistantPanel {
@@ -478,6 +499,13 @@ impl Focusable for AssistantPanel {
cx.focus_handle() cx.focus_handle()
} }
} }
ActiveView::PromptEditorHistory => {
if let Some(context_history) = self.context_history.as_ref() {
context_history.focus_handle(cx)
} else {
cx.focus_handle()
}
}
ActiveView::Configuration => { ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() { if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx) configuration.focus_handle(cx)
@@ -590,12 +618,21 @@ impl AssistantPanel {
SharedString::from(context_editor.read(cx).title(cx).to_string()) SharedString::from(context_editor.read(cx).title(cx).to_string())
}) })
.unwrap_or_else(|| SharedString::from("Loading Summary…")), .unwrap_or_else(|| SharedString::from("Loading Summary…")),
ActiveView::History => "History".into(), ActiveView::History | ActiveView::PromptEditorHistory => "History".into(),
ActiveView::Configuration => "Assistant Settings".into(), ActiveView::Configuration => "Assistant Settings".into(),
}; };
let sub_title = match self.active_view {
ActiveView::Thread => None,
ActiveView::PromptEditor => None,
ActiveView::History => Some("Thread"),
ActiveView::PromptEditorHistory => Some("Prompt Editor"),
ActiveView::Configuration => None,
};
h_flex() h_flex()
.id("assistant-toolbar") .id("assistant-toolbar")
.px(DynamicSpacing::Base08.rems(cx))
.h(Tab::container_height(cx)) .h(Tab::container_height(cx))
.flex_none() .flex_none()
.justify_between() .justify_between()
@@ -604,29 +641,40 @@ impl AssistantPanel {
.border_b_1() .border_b_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child( .child(
div() h_flex()
.id("title") .w_full()
.overflow_x_scroll() .gap_1()
.px(DynamicSpacing::Base08.rems(cx)) .justify_between()
.child(Label::new(title).text_ellipsis()),
)
.child( .child(
h_flex() h_flex()
.h_full() .child(Label::new(title))
.pl_2() .when(sub_title.is_some(), |this| {
.gap_2() this.child(
.bg(cx.theme().colors().tab_bar_background) h_flex()
.pl_1p5()
.gap_1p5()
.child(
Label::new("/")
.size(LabelSize::Small)
.color(Color::Disabled)
.alpha(0.5),
)
.child(Label::new(sub_title.unwrap())),
)
}),
)
.children(if matches!(self.active_view, ActiveView::PromptEditor) { .children(if matches!(self.active_view, ActiveView::PromptEditor) {
self.context_editor self.context_editor
.as_ref() .as_ref()
.and_then(|editor| render_remaining_tokens(editor, cx)) .and_then(|editor| render_remaining_tokens(editor, cx))
} else { } else {
None None
}) }),
)
.child( .child(
h_flex() h_flex()
.h_full() .h_full()
.px(DynamicSpacing::Base08.rems(cx)) .pl_1p5()
.border_l_1() .border_l_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.gap(DynamicSpacing::Base02.rems(cx)) .gap(DynamicSpacing::Base02.rems(cx))
@@ -641,37 +689,30 @@ impl AssistantPanel {
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone()) .with_handle(self.new_item_context_menu_handle.clone())
.menu(move |window, cx| { .menu(move |window, cx| {
Some(ContextMenu::build( Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
window,
cx,
|menu, _window, _cx| {
menu.action("New Thread", NewThread.boxed_clone()) menu.action("New Thread", NewThread.boxed_clone())
.action( .action("New Prompt Editor", NewPromptEditor.boxed_clone())
"New Prompt Editor", }))
NewPromptEditor.boxed_clone(),
)
},
))
}), }),
) )
.child( .child(
PopoverMenu::new("assistant-toolbar-history-popover-menu")
.trigger_with_tooltip(
IconButton::new("open-history", IconName::HistoryRerun) IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle),
.tooltip({ Tooltip::text("History…"),
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.open_history_context_menu_handle.clone())
.on_click(move |_event, window, cx| { .menu(move |window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx); Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.action("Thread History", OpenHistory.boxed_clone())
.action(
"Prompt Editor History",
OpenPromptEditorHistory.boxed_clone(),
)
}))
}), }),
) )
.child( .child(
@@ -683,7 +724,6 @@ impl AssistantPanel {
window.dispatch_action(OpenConfiguration.boxed_clone(), cx); window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
}), }),
), ),
),
) )
} }
@@ -722,13 +762,14 @@ impl AssistantPanel {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let recent_history = self let recent_threads = self
.history_store .thread_store
.update(cx, |this, cx| this.recent_entries(6, cx)); .update(cx, |this, _cx| this.recent_threads(3));
let create_welcome_heading = || { let create_welcome_heading = || {
h_flex() h_flex()
.w_full() .w_full()
.justify_center()
.child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small)) .child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
}; };
@@ -736,27 +777,35 @@ impl AssistantPanel {
let no_error = configuration_error.is_none(); let no_error = configuration_error.is_none();
v_flex() v_flex()
.p_1p5() .gap_2()
.size_full() .child(
.justify_end() v_flex().w_full().child(
.gap_1() svg()
.path("icons/logo_96.svg")
.text_color(cx.theme().colors().text)
.w(px(40.))
.h(px(40.))
.mx_auto()
.mb_4(),
),
)
.map(|parent| { .map(|parent| {
match configuration_error { match configuration_error {
Some(ConfigurationError::ProviderNotAuthenticated) Some(ConfigurationError::ProviderNotAuthenticated) | Some(ConfigurationError::NoProvider) => {
| Some(ConfigurationError::NoProvider) => {
parent.child( parent.child(
v_flex() v_flex()
.px_1p5()
.gap_0p5() .gap_0p5()
.child(create_welcome_heading()) .child(create_welcome_heading())
.child( .child(
h_flex().mb_2().w_full().justify_center().child(
Label::new( Label::new(
"To start using the assistant, configure at least one LLM provider.", "To start using the assistant, configure at least one LLM provider.",
) )
.color(Color::Muted), .color(Color::Muted),
),
) )
.child( .child(
h_flex().mt_1().w_full().child( h_flex().w_full().justify_center().child(
Button::new("open-configuration", "Configure a Provider") Button::new("open-configuration", "Configure a Provider")
.size(ButtonSize::Compact) .size(ButtonSize::Compact)
.icon(Some(IconName::Sliders)) .icon(Some(IconName::Sliders))
@@ -769,66 +818,64 @@ impl AssistantPanel {
), ),
) )
} }
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
.child(v_flex().px_1p5().gap_0p5().child(create_welcome_heading()).children( parent.child(
provider.render_accept_terms( v_flex()
.gap_0p5()
.child(create_welcome_heading())
.children(provider.render_accept_terms(
LanguageModelProviderTosView::ThreadEmptyState, LanguageModelProviderTosView::ThreadEmptyState,
cx, cx,
),
)), )),
)
}
None => parent, None => parent,
} }
}) })
.when(recent_history.is_empty() && no_error, |parent| { .when(
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child( recent_threads.is_empty() && no_error,
Label::new("Start typing to chat with your codebase").color(Color::Muted), |parent| {
)) parent.child(
}) v_flex().gap_0p5().child(create_welcome_heading()).child(
.when(!recent_history.is_empty(), |parent| { h_flex().w_full().justify_center().child(
Label::new("Start typing to chat with your codebase")
.color(Color::Muted),
),
),
)
},
)
.when(!recent_threads.is_empty(), |parent| {
parent parent
.child( .child(
h_flex() h_flex().w_full().justify_center().child(
.pl_1p5() Label::new("Recent Threads:")
.pb_1()
.w_full()
.justify_between()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(
Label::new("Past Interactions")
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted), .color(Color::Muted),
),
) )
.child(v_flex().mx_auto().w_4_5().gap_2().children(
recent_threads.into_iter().map(|thread| {
// TODO: keyboard navigation
PastThread::new(thread, cx.entity().downgrade(), false)
}),
))
.child( .child(
Button::new("view-history", "View All") h_flex().w_full().justify_center().child(
Button::new("view-all-past-threads", "View All Past Threads")
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action_in( .key_binding(KeyBinding::for_action_in(
&OpenHistory, &OpenHistory,
&self.focus_handle(cx), &self.focus_handle(cx),
window, window,
cx, cx
)) ))
.on_click(move |_event, window, cx| { .on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx); window.dispatch_action(OpenHistory.boxed_clone(), cx);
}), }),
), ),
) )
.child(v_flex().gap_1().children(
recent_history.into_iter().map(|entry| {
// TODO: Add keyboard navigation.
match entry {
HistoryEntry::Thread(thread) => {
PastThread::new(thread, cx.entity().downgrade(), false)
.into_any_element()
}
HistoryEntry::Context(context) => {
PastContext::new(context, cx.entity().downgrade(), false)
.into_any_element()
}
}
}),
))
}) })
} }
@@ -1021,6 +1068,7 @@ impl Render for AssistantPanel {
.children(self.render_last_error(cx)), .children(self.render_last_error(cx)),
ActiveView::History => parent.child(self.history.clone()), ActiveView::History => parent.child(self.history.clone()),
ActiveView::PromptEditor => parent.children(self.context_editor.clone()), ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
ActiveView::PromptEditorHistory => parent.children(self.context_history.clone()),
ActiveView::Configuration => parent.children(self.configuration.clone()), ActiveView::Configuration => parent.children(self.configuration.clone()),
}) })
} }

View File

@@ -9,9 +9,10 @@ use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, Stre
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
use language::{line_diff, Buffer, IndentKind, Point, TransactionId}; use language::{line_diff, Buffer, IndentKind, Point, TransactionId};
use language_model::{ use language_model::{
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestMessage, LanguageModelTextStream, Role, LanguageModelTextStream, Role,
}; };
use language_models::report_assistant_event;
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;

View File

@@ -7,19 +7,19 @@ use std::sync::Arc;
use editor::actions::FoldAt; use editor::actions::FoldAt;
use editor::display_map::{Crease, FoldId}; use editor::display_map::{Crease, FoldId};
use editor::scroll::Autoscroll; use editor::scroll::Autoscroll;
use editor::{Anchor, AnchorRangeExt, Editor, FoldPlaceholder, ToPoint}; use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
use file_icons::FileIcons; use file_icons::FileIcons;
use fuzzy::PathMatch; use fuzzy::PathMatch;
use gpui::{ use gpui::{
AnyElement, App, AppContext, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
Task, WeakEntity, WeakEntity,
}; };
use multi_buffer::{MultiBufferPoint, MultiBufferRow}; use multi_buffer::{MultiBufferPoint, MultiBufferRow};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId}; use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use rope::Point; use rope::Point;
use text::SelectionGoal; use text::SelectionGoal;
use ui::{prelude::*, ButtonLike, Disclosure, ListItem, TintColor, Tooltip}; use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip};
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{notifications::NotifyResultExt, Workspace}; use workspace::{notifications::NotifyResultExt, Workspace};
@@ -238,11 +238,11 @@ impl PickerDelegate for FileContextPickerDelegate {
path: mat.path.clone(), path: mat.path.clone(),
}; };
let Some(editor_entity) = self.editor.upgrade() else { let Some(editor) = self.editor.upgrade() else {
return; return;
}; };
editor_entity.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.transact(window, cx, |editor, window, cx| { editor.transact(window, cx, |editor, window, cx| {
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert. // Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
{ {
@@ -292,11 +292,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.unwrap_or_else(|| SharedString::new("")); .unwrap_or_else(|| SharedString::new(""));
let placeholder = FoldPlaceholder { let placeholder = FoldPlaceholder {
render: render_fold_icon_button( render: render_fold_icon_button(file_icon, file_name.into()),
file_icon,
file_name.into(),
editor_entity.downgrade(),
),
..Default::default() ..Default::default()
}; };
@@ -468,50 +464,11 @@ pub fn render_file_context_entry(
fn render_fold_icon_button( fn render_fold_icon_button(
icon: SharedString, icon: SharedString,
label: SharedString, label: SharedString,
editor: WeakEntity<Editor>, ) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> { Arc::new(move |fold_id, _fold_range, _window, _cx| {
Arc::new(move |fold_id, fold_range, cx| {
let is_in_text_selection = editor.upgrade().is_some_and(|editor| {
editor.update(cx, |editor, cx| {
let snapshot = editor
.buffer()
.update(cx, |multi_buffer, cx| multi_buffer.snapshot(cx));
let is_in_pending_selection = || {
editor
.selections
.pending
.as_ref()
.is_some_and(|pending_selection| {
pending_selection
.selection
.range()
.includes(&fold_range, &snapshot)
})
};
let mut is_in_complete_selection = || {
editor
.selections
.disjoint_in_range::<usize>(fold_range.clone(), cx)
.into_iter()
.any(|selection| {
// This is needed to cover a corner case, if we just check for an existing
// selection in the fold range, having a cursor at the start of the fold
// marks it as selected. Non-empty selections don't cause this.
let length = selection.end - selection.start;
length > 0
})
};
is_in_pending_selection() || is_in_complete_selection()
})
});
ButtonLike::new(fold_id) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.selected_style(ButtonStyle::Tinted(TintColor::Accent)) .layer(ElevationIndex::ElevatedSurface)
.toggle_state(is_in_text_selection)
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()

View File

@@ -1,61 +0,0 @@
use assistant_context_editor::SavedContextMetadata;
use chrono::{DateTime, Utc};
use gpui::{prelude::*, Entity};
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
pub enum HistoryEntry {
Thread(SavedThreadMetadata),
Context(SavedContextMetadata),
}
impl HistoryEntry {
pub fn updated_at(&self) -> DateTime<Utc> {
match self {
HistoryEntry::Thread(thread) => thread.updated_at,
HistoryEntry::Context(context) => context.mtime.to_utc(),
}
}
}
pub struct HistoryStore {
thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>,
}
impl HistoryStore {
pub fn new(
thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>,
_cx: &mut Context<Self>,
) -> Self {
Self {
thread_store,
context_store,
}
}
/// Returns the number of history entries.
pub fn entry_count(&self, cx: &mut Context<Self>) -> usize {
self.entries(cx).len()
}
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
let mut history_entries = Vec::new();
for thread in self.thread_store.update(cx, |this, _cx| this.threads()) {
history_entries.push(HistoryEntry::Thread(thread));
}
for context in self.context_store.update(cx, |this, _cx| this.contexts()) {
history_entries.push(HistoryEntry::Context(context));
}
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
history_entries
}
pub fn recent_entries(&self, limit: usize, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
self.entries(cx).into_iter().take(limit).collect()
}
}

View File

@@ -24,7 +24,8 @@ use gpui::{
UpdateGlobal, WeakEntity, Window, UpdateGlobal, WeakEntity, Window,
}; };
use language::{Buffer, Point, Selection, TransactionId}; use language::{Buffer, Point, Selection, TransactionId};
use language_model::{report_assistant_event, LanguageModelRegistry}; use language_model::LanguageModelRegistry;
use language_models::report_assistant_event;
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};

View File

@@ -6,7 +6,7 @@ use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::terminal_codegen::TerminalCodegen; use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist}; use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{RemoveAllContext, ToggleContextPicker}; use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
use client::ErrorExt; use client::ErrorExt;
use collections::VecDeque; use collections::VecDeque;
use editor::{ use editor::{
@@ -20,6 +20,7 @@ use gpui::{
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window, EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
}; };
use language_model::{LanguageModel, LanguageModelRegistry}; use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
use parking_lot::Mutex; use parking_lot::Mutex;
use settings::Settings; use settings::Settings;
use std::cmp; use std::cmp;
@@ -39,6 +40,7 @@ pub struct PromptEditor<T> {
context_strip: Entity<ContextStrip>, context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool, edited_since_done: bool,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>, prompt_history_ix: Option<usize>,
@@ -54,7 +56,7 @@ impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
impl<T: 'static> Render for PromptEditor<T> { impl<T: 'static> Render for PromptEditor<T> {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx); let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let mut buttons = Vec::new(); let mut buttons = Vec::new();
let left_gutter_width = match &self.mode { let left_gutter_width = match &self.mode {
@@ -102,12 +104,7 @@ impl<T: 'static> Render for PromptEditor<T> {
.items_start() .items_start()
.cursor(CursorStyle::Arrow) .cursor(CursorStyle::Arrow)
.on_action(cx.listener(Self::toggle_context_picker)) .on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(|this, action, window, cx| { .on_action(cx.listener(Self::toggle_model_selector))
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::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_up))
@@ -350,6 +347,15 @@ impl<T: 'static> PromptEditor<T> {
self.context_picker_menu_handle.toggle(window, cx); 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( pub fn remove_all_context(
&mut self, &mut self,
_: &RemoveAllContext, _: &RemoveAllContext,
@@ -858,6 +864,7 @@ impl PromptEditor<BufferCodegen> {
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
@@ -881,8 +888,15 @@ impl PromptEditor<BufferCodegen> {
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new(|cx| { 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, edited_since_done: false,
prompt_history, prompt_history,
prompt_history_ix: None, prompt_history_ix: None,
@@ -1006,6 +1020,7 @@ impl PromptEditor<TerminalCodegen> {
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
@@ -1029,8 +1044,15 @@ impl PromptEditor<TerminalCodegen> {
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new(|cx| { 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, edited_since_done: false,
prompt_history, prompt_history,
prompt_history_ix: None, prompt_history_ix: None,

View File

@@ -7,17 +7,16 @@ use gpui::{
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
TextStyle, WeakEntity, TextStyle, WeakEntity,
}; };
use language_model::LanguageModelRegistry; use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
use rope::Point; use rope::Point;
use settings::Settings; use settings::Settings;
use std::time::Duration; use std::time::Duration;
use text::Bias; use text::Bias;
use theme::ThemeSettings; use theme::{get_ui_font_size, ThemeSettings};
use ui::{ use ui::{
prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch, prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
TintColor, Tooltip,
}; };
use vim_mode_setting::VimModeSetting;
use workspace::Workspace; use workspace::Workspace;
use crate::assistant_model_selector::AssistantModelSelector; 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::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::thread::{RequestKind, Thread}; use crate::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker}; use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor { pub struct MessageEditor {
thread: Entity<Thread>, thread: Entity<Thread>,
@@ -37,6 +36,7 @@ pub struct MessageEditor {
inline_context_picker: Entity<ContextPicker>, inline_context_picker: Entity<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool, use_tools: bool,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -53,6 +53,7 @@ impl MessageEditor {
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone())); let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_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 editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx); let mut editor = Editor::auto_height(10, window, cx);
@@ -105,13 +106,30 @@ impl MessageEditor {
context_picker_menu_handle, context_picker_menu_handle,
inline_context_picker, inline_context_picker,
inline_context_picker_menu_handle, inline_context_picker_menu_handle,
model_selector: cx model_selector: cx.new(|cx| {
.new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)), AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
editor.focus_handle(cx),
window,
cx,
)
}),
model_selector_menu_handle,
use_tools: false, use_tools: false,
_subscriptions: subscriptions, _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>) { fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
self.use_tools = !self.use_tools; self.use_tools = !self.use_tools;
cx.notify(); cx.notify();
@@ -187,7 +205,22 @@ impl MessageEditor {
.update(&mut cx, |thread, cx| { .update(&mut cx, |thread, cx| {
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>(); let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
thread.insert_user_message(user_message, context, cx); 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(); .ok();
}) })
@@ -276,6 +309,7 @@ impl Render for MessageEditor {
let inline_context_picker = self.inline_context_picker.clone(); let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background; let bg_color = cx.theme().colors().editor_background;
let is_streaming_completion = self.thread.read(cx).is_streaming(); 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_model_selected = self.is_model_selected(cx);
let is_editor_empty = self.is_editor_empty(cx); let is_editor_empty = self.is_editor_empty(cx);
let submit_label_color = if is_editor_empty { let submit_label_color = if is_editor_empty {
@@ -284,25 +318,10 @@ impl Render for MessageEditor {
Color::Default 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() v_flex()
.key_context("MessageEditor") .key_context("MessageEditor")
.on_action(cx.listener(Self::chat)) .on_action(cx.listener(Self::chat))
.on_action(cx.listener(|this, action, window, cx| { .on_action(cx.listener(Self::toggle_model_selector))
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_context_picker)) .on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::remove_all_context)) .on_action(cx.listener(Self::remove_all_context))
.on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_up))
@@ -314,7 +333,7 @@ impl Render for MessageEditor {
.child(self.context_strip.clone()) .child(self.context_strip.clone())
.child( .child(
v_flex() v_flex()
.gap_5() .gap_4()
.child({ .child({
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
@@ -350,7 +369,7 @@ impl Render for MessageEditor {
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point { .offset(gpui::Point {
x: px(0.0), x: px(0.0),
y: (-ThemeSettings::get_global(cx).ui_font_size(cx) * 2) - px(4.0), y: (-get_ui_font_size(cx) * 2) - px(4.0),
}) })
.with_handle(self.inline_context_picker_menu_handle.clone()), .with_handle(self.inline_context_picker_menu_handle.clone()),
) )

View File

@@ -2,7 +2,8 @@ use crate::inline_prompt_editor::CodegenStatus;
use client::telemetry::Telemetry; use client::telemetry::Telemetry;
use futures::{channel::mpsc, SinkExt, StreamExt}; use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
use language_model::{report_assistant_event, LanguageModelRegistry, LanguageModelRequest}; use language_model::{LanguageModelRegistry, LanguageModelRequest};
use language_models::report_assistant_event;
use std::{sync::Arc, time::Instant}; use std::{sync::Arc, time::Instant};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal; use terminal::Terminal;
@@ -155,10 +156,7 @@ pub enum CodegenEvent {
Finished, Finished,
} }
#[cfg(not(target_os = "windows"))]
pub const CLEAR_INPUT: &str = "\x15"; pub const CLEAR_INPUT: &str = "\x15";
#[cfg(target_os = "windows")]
pub const CLEAR_INPUT: &str = "\x03";
const CARRIAGE_RETURN: &str = "\x0d"; const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction { struct TerminalTransaction {

View File

@@ -13,9 +13,9 @@ use fs::Fs;
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity}; use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
use language::Buffer; use language::Buffer;
use language_model::{ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
LanguageModelRequestMessage, Role,
}; };
use language_models::report_assistant_event;
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;
use std::sync::Arc; use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};

View File

@@ -4,26 +4,25 @@ use anyhow::Result;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet}; 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 gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{ use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId, LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason, LanguageModelToolUseId, MessageContent, Role, StopReason,
}; };
use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::{post_inc, TryFutureExt as _}; use util::{post_inc, TryFutureExt as _};
use uuid::Uuid; use uuid::Uuid;
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot}; use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
use crate::thread_store::SavedThread; use crate::thread_store::SavedThread;
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum RequestKind { pub enum RequestKind {
Chat, Chat,
/// Used when summarizing a thread.
Summarize,
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)] #[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)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct MessageId(pub(crate) usize); pub struct MessageId(usize);
impl MessageId { impl MessageId {
fn post_inc(&mut self) -> Self { fn post_inc(&mut self) -> Self {
@@ -71,7 +70,9 @@ pub struct Thread {
completion_count: usize, completion_count: usize,
pending_completions: Vec<PendingCompletion>, pending_completions: Vec<PendingCompletion>,
tools: Arc<ToolWorkingSet>, 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 { impl Thread {
@@ -88,7 +89,9 @@ impl Thread {
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
tools, 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, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
tools, 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> { pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.tool_use.pending_tool_uses() self.pending_tool_uses_by_id.values().collect()
}
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)
} }
pub fn insert_user_message( pub fn insert_user_message(
@@ -244,34 +241,9 @@ impl Thread {
text 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( pub fn to_completion_request(
&self, &self,
request_kind: RequestKind, _request_kind: RequestKind,
_cx: &App, _cx: &App,
) -> LanguageModelRequest { ) -> LanguageModelRequest {
let mut request = LanguageModelRequest { let mut request = LanguageModelRequest {
@@ -293,13 +265,12 @@ impl Thread {
content: Vec::new(), content: Vec::new(),
cache: false, cache: false,
}; };
match request_kind {
RequestKind::Chat => { if let Some(tool_results) = self.tool_results_by_message.get(&message.id) {
self.tool_use for tool_result in tool_results {
.attach_tool_results(message.id, &mut request_message); request_message
} .content
RequestKind::Summarize => { .push(MessageContent::ToolResult(tool_result.clone()));
// We don't care about tool use during summarization.
} }
} }
@@ -309,13 +280,11 @@ impl Thread {
.push(MessageContent::Text(message.text.clone())); .push(MessageContent::Text(message.text.clone()));
} }
match request_kind { if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) {
RequestKind::Chat => { for tool_use in tool_uses {
self.tool_use request_message
.attach_tool_uses(message.id, &mut request_message); .content
} .push(MessageContent::ToolUse(tool_use.clone()));
RequestKind::Summarize => {
// We don't care about tool use during summarization.
} }
} }
@@ -391,8 +360,21 @@ impl Thread {
.rfind(|message| message.role == Role::Assistant) .rfind(|message| message.role == Role::Assistant)
{ {
thread thread
.tool_use .tool_uses_by_message
.request_tool_use(last_assistant_message.id, tool_use); .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; 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 { request.messages.push(LanguageModelRequestMessage {
role: Role::User, role: Role::User,
content: vec![ content: vec![
@@ -512,6 +494,7 @@ impl Thread {
pub fn insert_tool_output( pub fn insert_tool_output(
&mut self, &mut self,
assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>, output: Task<Result<String>>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
@@ -522,18 +505,50 @@ impl Thread {
let output = output.await; let output = output.await;
thread thread
.update(&mut cx, |thread, cx| { .update(&mut cx, |thread, cx| {
thread // The tool use was requested by an Assistant message,
.tool_use // so we want to attach the tool results to the next
.insert_tool_output(tool_use_id.clone(), output); // user message.
let next_user_message = MessageId(assistant_message_id.0 + 1);
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.to_string(),
content: output,
is_error: false,
});
cx.emit(ThreadEvent::ToolFinished { tool_use_id }); cx.emit(ThreadEvent::ToolFinished { tool_use_id });
}
Err(err) => {
tool_results.push(LanguageModelToolResult {
tool_use_id: tool_use_id.to_string(),
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(); .ok();
} }
}); });
self.tool_use if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
.run_pending_tool(tool_use_id, insert_output_task); tool_use.status = PendingToolUseStatus::Running {
_task: insert_output_task.shared(),
};
}
} }
/// Cancels the last pending completion, if there are any pending. /// Cancels the last pending completion, if there are any pending.
@@ -575,3 +590,26 @@ struct PendingCompletion {
id: usize, id: usize,
_task: Task<()>, _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)
}
}

View File

@@ -1,4 +1,3 @@
use assistant_context_editor::SavedContextMetadata;
use gpui::{ use gpui::{
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle, uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
WeakEntity, WeakEntity,
@@ -6,14 +5,13 @@ use gpui::{
use time::{OffsetDateTime, UtcOffset}; use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip}; use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
use crate::history_store::{HistoryEntry, HistoryStore}; use crate::thread_store::{SavedThreadMetadata, ThreadStore};
use crate::thread_store::SavedThreadMetadata;
use crate::{AssistantPanel, RemoveSelectedThread}; use crate::{AssistantPanel, RemoveSelectedThread};
pub struct ThreadHistory { pub struct ThreadHistory {
focus_handle: FocusHandle, focus_handle: FocusHandle,
assistant_panel: WeakEntity<AssistantPanel>, assistant_panel: WeakEntity<AssistantPanel>,
history_store: Entity<HistoryStore>, thread_store: Entity<ThreadStore>,
scroll_handle: UniformListScrollHandle, scroll_handle: UniformListScrollHandle,
selected_index: usize, selected_index: usize,
} }
@@ -21,13 +19,14 @@ pub struct ThreadHistory {
impl ThreadHistory { impl ThreadHistory {
pub(crate) fn new( pub(crate) fn new(
assistant_panel: WeakEntity<AssistantPanel>, assistant_panel: WeakEntity<AssistantPanel>,
history_store: Entity<HistoryStore>, thread_store: Entity<ThreadStore>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
assistant_panel, assistant_panel,
history_store, thread_store,
scroll_handle: UniformListScrollHandle::default(), scroll_handle: UniformListScrollHandle::default(),
selected_index: 0, selected_index: 0,
} }
@@ -39,9 +38,7 @@ impl ThreadHistory {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let count = self let count = self.thread_store.read(cx).thread_count();
.history_store
.update(cx, |this, cx| this.entry_count(cx));
if count > 0 { if count > 0 {
if self.selected_index == 0 { if self.selected_index == 0 {
self.set_selected_index(count - 1, window, cx); self.set_selected_index(count - 1, window, cx);
@@ -57,9 +54,7 @@ impl ThreadHistory {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let count = self let count = self.thread_store.read(cx).thread_count();
.history_store
.update(cx, |this, cx| this.entry_count(cx));
if count > 0 { if count > 0 {
if self.selected_index == count - 1 { if self.selected_index == count - 1 {
self.set_selected_index(0, window, cx); self.set_selected_index(0, window, cx);
@@ -70,18 +65,14 @@ impl ThreadHistory {
} }
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) { fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
let count = self let count = self.thread_store.read(cx).thread_count();
.history_store
.update(cx, |this, cx| this.entry_count(cx));
if count > 0 { if count > 0 {
self.set_selected_index(0, window, cx); self.set_selected_index(0, window, cx);
} }
} }
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) { fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
let count = self let count = self.thread_store.read(cx).thread_count();
.history_store
.update(cx, |this, cx| this.entry_count(cx));
if count > 0 { if count > 0 {
self.set_selected_index(count - 1, window, cx); self.set_selected_index(count - 1, window, cx);
} }
@@ -95,23 +86,12 @@ impl ThreadHistory {
} }
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) { fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let entries = self.history_store.update(cx, |this, cx| this.entries(cx)); let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(entry) = entries.get(self.selected_index) { if let Some(thread) = threads.get(self.selected_index) {
match entry {
HistoryEntry::Thread(thread) => {
self.assistant_panel self.assistant_panel
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx)) .update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
.ok(); .ok();
}
HistoryEntry::Context(context) => {
self.assistant_panel
.update(cx, move |this, cx| {
this.open_saved_prompt_editor(context.path.clone(), window, cx)
})
.ok();
}
}
cx.notify(); cx.notify();
} }
@@ -123,25 +103,14 @@ impl ThreadHistory {
_window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let entries = self.history_store.update(cx, |this, cx| this.entries(cx)); let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(entry) = entries.get(self.selected_index) { if let Some(thread) = threads.get(self.selected_index) {
match entry {
HistoryEntry::Thread(thread) => {
self.assistant_panel self.assistant_panel
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.delete_thread(&thread.id, cx); this.delete_thread(&thread.id, cx);
}) })
.ok(); .ok();
}
HistoryEntry::Context(context) => {
self.assistant_panel
.update(cx, |this, cx| {
this.delete_context(context.path.clone(), cx);
})
.ok();
}
}
cx.notify(); cx.notify();
} }
@@ -156,7 +125,7 @@ impl Focusable for ThreadHistory {
impl Render for ThreadHistory { impl Render for ThreadHistory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let history_entries = self.history_store.update(cx, |this, cx| this.entries(cx)); let threads = self.thread_store.update(cx, |this, _cx| this.threads());
let selected_index = self.selected_index; let selected_index = self.selected_index;
v_flex() v_flex()
@@ -173,7 +142,7 @@ impl Render for ThreadHistory {
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::remove_selected_thread)) .on_action(cx.listener(Self::remove_selected_thread))
.map(|history| { .map(|history| {
if history_entries.is_empty() { if threads.is_empty() {
history history
.justify_center() .justify_center()
.child( .child(
@@ -187,26 +156,17 @@ impl Render for ThreadHistory {
uniform_list( uniform_list(
cx.entity().clone(), cx.entity().clone(),
"thread-history", "thread-history",
history_entries.len(), threads.len(),
move |history, range, _window, _cx| { move |history, range, _window, _cx| {
history_entries[range] threads[range]
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, entry)| { .map(|(index, thread)| {
h_flex().w_full().pb_1().child(match entry { h_flex().w_full().pb_1().child(PastThread::new(
HistoryEntry::Thread(thread) => PastThread::new(
thread.clone(), thread.clone(),
history.assistant_panel.clone(), history.assistant_panel.clone(),
selected_index == index, selected_index == index,
) ))
.into_any_element(),
HistoryEntry::Context(context) => PastContext::new(
context.clone(),
history.assistant_panel.clone(),
selected_index == index,
)
.into_any_element(),
})
}) })
.collect() .collect()
}, },
@@ -254,28 +214,18 @@ impl RenderOnce for PastThread {
); );
ListItem::new(SharedString::from(self.thread.id.to_string())) ListItem::new(SharedString::from(self.thread.id.to_string()))
.rounded() .outlined()
.toggle_state(self.selected) .toggle_state(self.selected)
.spacing(ListItemSpacing::Sparse)
.start_slot( .start_slot(
div() Icon::new(IconName::MessageCircle)
.max_w_4_5() .size(IconSize::Small)
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()), .color(Color::Muted),
) )
.spacing(ListItemSpacing::Sparse)
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
.end_slot( .end_slot(
h_flex() h_flex()
.gap_1p5() .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( .child(
Label::new(thread_timestamp) Label::new(thread_timestamp)
.color(Color::Muted) .color(Color::Muted)
@@ -312,100 +262,3 @@ impl RenderOnce for PastThread {
}) })
} }
} }
#[derive(IntoElement)]
pub struct PastContext {
context: SavedContextMetadata,
assistant_panel: WeakEntity<AssistantPanel>,
selected: bool,
}
impl PastContext {
pub fn new(
context: SavedContextMetadata,
assistant_panel: WeakEntity<AssistantPanel>,
selected: bool,
) -> Self {
Self {
context,
assistant_panel,
selected,
}
}
}
impl RenderOnce for PastContext {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let summary = self.context.title;
let context_timestamp = time_format::format_localized_timestamp(
OffsetDateTime::from_unix_timestamp(self.context.mtime.timestamp()).unwrap(),
OffsetDateTime::now_utc(),
self.assistant_panel
.update(cx, |this, _cx| this.local_timezone())
.unwrap_or(UtcOffset::UTC),
time_format::TimestampFormat::EnhancedAbsolute,
);
ListItem::new(SharedString::from(
self.context.path.to_string_lossy().to_string(),
))
.rounded()
.toggle_state(self.selected)
.spacing(ListItemSpacing::Sparse)
.start_slot(
div()
.max_w_4_5()
.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();
}
}),
),
)
.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.open_saved_prompt_editor(path.clone(), window, cx)
.detach_and_log_err(cx);
})
.ok();
}
})
}
}

View File

@@ -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(_))
}
}

View File

@@ -30,6 +30,7 @@ indexed_docs.workspace = true
language.workspace = true language.workspace = true
language_model.workspace = true language_model.workspace = true
language_model_selector.workspace = true language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true log.workspace = true
multi_buffer.workspace = true multi_buffer.workspace = true
open_ai.workspace = true open_ai.workspace = true

View File

@@ -19,10 +19,13 @@ use gpui::{
}; };
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset}; use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{ use language_model::{
report_assistant_event, LanguageModel, LanguageModelCacheConfiguration, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
LanguageModelCompletionEvent, LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestMessage, LanguageModelToolUseId, MaxMonthlySpendReachedError, LanguageModelToolUseId, MessageContent, Role, StopReason,
MessageContent, PaymentRequiredError, Role, StopReason, };
use language_models::{
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
report_assistant_event,
}; };
use open_ai::Model as OpenAiModel; use open_ai::Model as OpenAiModel;
use paths::contexts_dir; use paths::contexts_dir;
@@ -3363,7 +3366,7 @@ impl SavedContextV0_1_0 {
} }
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct SavedContextMetadata { pub struct SavedContextMetadata {
pub title: String, pub title: String,
pub path: PathBuf, pub path: PathBuf,

View File

@@ -29,22 +29,19 @@ use gpui::{
WeakEntity, WeakEntity,
}; };
use indexed_docs::IndexedDocsStore; use indexed_docs::IndexedDocsStore;
use language::{ use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
language_settings::{all_language_settings, SoftWrap},
BufferSnapshot, LspAdapterDelegate, ToOffset,
};
use language_model::{ use language_model::{
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry, LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
Role, Role,
}; };
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector}; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use picker::Picker; use picker::Picker;
use project::lsp_store::LocalLspAdapterDelegate; use project::lsp_store::LocalLspAdapterDelegate;
use project::{Project, Worktree}; use project::{Project, Worktree};
use rope::Point; use rope::Point;
use serde::{Deserialize, Serialize}; 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 std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration};
use text::SelectionGoal; use text::SelectionGoal;
use ui::{ use ui::{
@@ -80,6 +77,7 @@ actions!(
InsertIntoEditor, InsertIntoEditor,
QuoteSelection, QuoteSelection,
Split, Split,
ToggleModelSelector,
] ]
); );
@@ -196,6 +194,7 @@ pub struct ContextEditor {
// context editor, we keep a reference here. // context editor, we keep a reference here.
dragged_file_worktrees: Vec<Entity<Worktree>>, dragged_file_worktrees: Vec<Entity<Worktree>>,
language_model_selector: Entity<LanguageModelSelector>, language_model_selector: Entity<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
} }
pub const DEFAULT_TAB_TITLE: &str = "New Chat"; 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_completion_provider(Some(Box::new(completion_provider)));
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never); editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
editor.set_collaboration_hub(Box::new(project.clone())); 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 editor
}); });
@@ -246,7 +238,6 @@ impl ContextEditor {
cx.subscribe_in(&context, window, Self::handle_context_event), 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_event),
cx.subscribe_in(&editor, window, Self::handle_editor_search_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(); 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 sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>(); let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands().clone(); let slash_commands = context.read(cx).slash_commands().clone();
@@ -289,6 +281,7 @@ impl ContextEditor {
slash_menu_handle: Default::default(), slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(), dragged_file_worktrees: Vec::new(),
language_model_selector, language_model_selector,
language_model_selector_menu_handle,
}; };
this.update_message_headers(cx); this.update_message_headers(cx);
this.update_image_blocks(cx); this.update_image_blocks(cx);
@@ -297,16 +290,6 @@ impl ContextEditor {
this 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> { pub fn context(&self) -> &Entity<AssistantContext> {
&self.context &self.context
} }
@@ -651,7 +634,7 @@ impl ContextEditor {
} }
}); });
let placeholder = FoldPlaceholder { let placeholder = FoldPlaceholder {
render: Arc::new(move |_, _, _| Empty.into_any()), render: Arc::new(move |_, _, _, _| Empty.into_any()),
..Default::default() ..Default::default()
}; };
let render_toggle = { let render_toggle = {
@@ -1104,7 +1087,7 @@ impl ContextEditor {
patch: AssistantPatch, patch: AssistantPatch,
mut cx: AsyncWindowContext, mut cx: AsyncWindowContext,
) -> Result<()> { ) -> Result<()> {
let project = this.read_with(&cx, |this, _| this.project.clone())?; let project = this.update(&mut cx, |this, _| this.project.clone())?;
let resolved_patch = patch.resolve(project.clone(), &mut cx).await; let resolved_patch = patch.resolve(project.clone(), &mut cx).await;
let editor = cx.new_window_entity(|window, cx| { let editor = cx.new_window_entity(|window, cx| {
@@ -1129,7 +1112,7 @@ impl ContextEditor {
editor editor
})?; })?;
this.update(&mut cx, |this, _| { this.update_in(&mut cx, |this, window, cx| {
if let Some(patch_state) = this.patches.get_mut(&patch.range) { if let Some(patch_state) = this.patches.get_mut(&patch.range) {
patch_state.editor = Some(PatchEditorState { patch_state.editor = Some(PatchEditorState {
editor: editor.downgrade(), editor: editor.downgrade(),
@@ -1137,12 +1120,19 @@ impl ContextEditor {
}); });
patch_state.update_task.take(); patch_state.update_task.take();
} }
})?;
this.read_with(&cx, |this, _| this.workspace.clone())? this.workspace
.update_in(&mut cx, |workspace, window, cx| { .update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, window, cx) workspace.add_item_to_active_pane(
Box::new(editor.clone()),
None,
false,
window,
cx,
)
}) })
.log_err(); .log_err();
})?;
Ok(()) Ok(())
} }
@@ -1244,8 +1234,8 @@ impl ContextEditor {
.px_1() .px_1()
.mr_0p5() .mr_0p5()
.border_1() .border_1()
.border_color(colors.border_variant.alpha(0.6)) .border_color(theme::color_alpha(colors.border_variant, 0.6))
.bg(colors.element_background.alpha(0.6)) .bg(theme::color_alpha(colors.element_background, 0.6))
.child("esc"), .child("esc"),
) )
.child("to cancel") .child("to cancel")
@@ -1524,11 +1514,15 @@ impl ContextEditor {
(!text.is_empty()).then_some((text, true)) (!text.is_empty()).then_some((text, true))
} else { } else {
let selection = context_editor.selections.newest_adjusted(cx); let anchor = context_editor.selections.newest_anchor();
let buffer = context_editor.buffer().read(cx).snapshot(cx); let text = context_editor
let selected_text = buffer.text_for_range(selection.range()).collect::<String>(); .buffer()
.read(cx)
.read(cx)
.text_for_range(anchor.range())
.collect::<String>();
(!selected_text.is_empty()).then_some((selected_text, false)) (!text.is_empty()).then_some((text, false))
} }
}) })
} }
@@ -1783,16 +1777,23 @@ impl ContextEditor {
&mut self, &mut self,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) { ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
let (selection, creases) = self.editor.update(cx, |editor, cx| { let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
let mut selection = editor.selections.newest_adjusted(cx); let mut selection = editor.selections.newest::<Point>(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx); let snapshot = editor.buffer().read(cx).snapshot(cx);
let is_entire_line = selection.is_empty() || editor.selections.line_mode;
if is_entire_line {
selection.start = Point::new(selection.start.row, 0);
selection.end =
cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
selection.goal = SelectionGoal::None; selection.goal = SelectionGoal::None;
}
let selection_start = snapshot.point_to_offset(selection.start); let selection_start = snapshot.point_to_offset(selection.start);
( (
selection.map(|point| snapshot.point_to_offset(point)), snapshot.clone(),
selection.clone(),
editor.display_map.update(cx, |display_map, cx| { editor.display_map.update(cx, |display_map, cx| {
display_map display_map
.snapshot(cx) .snapshot(cx)
@@ -1832,6 +1833,7 @@ impl ContextEditor {
) )
}); });
let selection = selection.map(|point| snapshot.point_to_offset(point));
let context = self.context.read(cx); let context = self.context.read(cx);
let mut text = String::new(); let mut text = String::new();
@@ -2388,6 +2390,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> { fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?; let last_error = self.last_error.as_ref()?;
@@ -2617,8 +2659,8 @@ fn render_fold_icon_button(
editor: WeakEntity<Editor>, editor: WeakEntity<Editor>,
icon: IconName, icon: IconName,
label: SharedString, label: SharedString,
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> { ) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
Arc::new(move |fold_id, fold_range, _cx| { Arc::new(move |fold_id, fold_range, _window, _cx| {
let editor = editor.clone(); let editor = editor.clone();
ButtonLike::new(fold_id) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
@@ -2678,7 +2720,7 @@ pub fn fold_toggle(
fn quote_selection_fold_placeholder(title: String, editor: WeakEntity<Editor>) -> FoldPlaceholder { fn quote_selection_fold_placeholder(title: String, editor: WeakEntity<Editor>) -> FoldPlaceholder {
FoldPlaceholder { FoldPlaceholder {
render: Arc::new({ render: Arc::new({
move |fold_id, fold_range, _cx| { move |fold_id, fold_range, _window, _cx| {
let editor = editor.clone(); let editor = editor.clone();
ButtonLike::new(fold_id) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
@@ -2832,7 +2874,6 @@ impl Render for ContextEditor {
None None
}; };
let language_model_selector = self.language_model_selector.clone();
v_flex() v_flex()
.key_context("ContextEditor") .key_context("ContextEditor")
.capture_action(cx.listener(ContextEditor::cancel)) .capture_action(cx.listener(ContextEditor::cancel))
@@ -2845,11 +2886,6 @@ impl Render for ContextEditor {
.on_action(cx.listener(ContextEditor::edit)) .on_action(cx.listener(ContextEditor::edit))
.on_action(cx.listener(ContextEditor::assist)) .on_action(cx.listener(ContextEditor::assist))
.on_action(cx.listener(ContextEditor::split)) .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);
})
})
.size_full() .size_full()
.children(self.render_notice(cx)) .children(self.render_notice(cx))
.child( .child(
@@ -2887,14 +2923,11 @@ impl Render for ContextEditor {
.gap_1() .gap_1()
.child(self.render_inject_context_menu(cx)) .child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical()) .child(ui::Divider::vertical())
.child(div().pl_0p5().child({ .child(
let focus_handle = self.editor().focus_handle(cx).clone(); div()
AssistantLanguageModelSelector::new( .pl_0p5()
focus_handle, .child(self.render_language_model_selector(cx)),
self.language_model_selector.clone(), ),
)
.render(window, cx)
})),
) )
.child( .child(
h_flex() h_flex()
@@ -3370,7 +3403,7 @@ fn invoked_slash_command_fold_placeholder(
FoldPlaceholder { FoldPlaceholder {
constrain_width: false, constrain_width: false,
merge_adjacent: false, merge_adjacent: false,
render: Arc::new(move |fold_id, _, cx| { render: Arc::new(move |fold_id, _, _window, cx| {
let Some(context) = context.upgrade() else { let Some(context) = context.upgrade() else {
return Empty.into_any(); return Empty.into_any();
}; };

View File

@@ -9,7 +9,7 @@ use clock::ReplicaId;
use collections::HashMap; use collections::HashMap;
use context_server::manager::ContextServerManager; use context_server::manager::ContextServerManager;
use context_server::ContextServerFactoryRegistry; use context_server::ContextServerFactoryRegistry;
use fs::{Fs, RemoveOptions}; use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
@@ -350,12 +350,6 @@ impl ContextStore {
} }
} }
pub fn contexts(&self) -> Vec<SavedContextMetadata> {
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
contexts
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> { pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new(|cx| { let context = cx.new(|cx| {
AssistantContext::local( AssistantContext::local(
@@ -475,38 +469,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>> { fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| { self.contexts.iter().find_map(|context| {
let context = context.upgrade()?; let context = context.upgrade()?;

View File

@@ -140,7 +140,7 @@ impl ResolvedPatch {
buffer.edit( buffer.edit(
edits, edits,
Some(AutoindentMode::Block { Some(AutoindentMode::Block {
original_start_columns: Vec::new(), original_indent_columns: Vec::new(),
}), }),
cx, cx,
); );

View File

@@ -359,7 +359,6 @@ fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema:
schemars::schema::SchemaObject { schemars::schema::SchemaObject {
enum_values: Some(vec![ enum_values: Some(vec![
"anthropic".into(), "anthropic".into(),
"bedrock".into(),
"google".into(), "google".into(),
"lmstudio".into(), "lmstudio".into(),
"ollama".into(), "ollama".into(),
@@ -513,7 +512,7 @@ mod tests {
AssistantSettings::get_global(cx).default_model, AssistantSettings::get_global(cx).default_model,
LanguageModelSelection { LanguageModelSelection {
provider: "zed.dev".into(), provider: "zed.dev".into(),
model: "claude-3-5-sonnet-latest".into(), model: "claude-3-5-sonnet".into(),
} }
); );
}); });

View File

@@ -21,11 +21,11 @@ impl SlashCommand for DefaultSlashCommand {
} }
fn description(&self) -> String { fn description(&self) -> String {
"Insert default prompt".into() "insert default prompt".into()
} }
fn menu_text(&self) -> String { fn menu_text(&self) -> String {
self.description() "Insert Default Prompt".into()
} }
fn requires_argument(&self) -> bool { fn requires_argument(&self) -> bool {

View File

@@ -17,7 +17,7 @@ pub enum Timezone {
} }
#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct NowToolInput { pub struct FileToolInput {
/// The timezone to use for the datetime. /// The timezone to use for the datetime.
timezone: Timezone, timezone: Timezone,
} }
@@ -34,7 +34,7 @@ impl Tool for NowTool {
} }
fn input_schema(&self) -> serde_json::Value { fn input_schema(&self) -> serde_json::Value {
let schema = schemars::schema_for!(NowToolInput); let schema = schemars::schema_for!(FileToolInput);
serde_json::to_value(&schema).unwrap() serde_json::to_value(&schema).unwrap()
} }
@@ -45,7 +45,7 @@ impl Tool for NowTool {
_window: &mut Window, _window: &mut Window,
_cx: &mut App, _cx: &mut App,
) -> Task<Result<String>> { ) -> Task<Result<String>> {
let input: NowToolInput = match serde_json::from_value(input) { let input: FileToolInput = match serde_json::from_value(input) {
Ok(input) => input, Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))), Err(err) => return Task::ready(Err(anyhow!(err))),
}; };

View File

@@ -1,22 +0,0 @@
[package]
name = "aws_http_client"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/aws_http_client.rs"
[features]
default = []
[dependencies]
aws-smithy-runtime-api.workspace = true
aws-smithy-types.workspace = true
futures.workspace = true
http_client.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }

View File

@@ -1 +0,0 @@
../../LICENSE-GPL

View File

@@ -1,118 +0,0 @@
use std::fmt;
use std::sync::Arc;
use aws_smithy_runtime_api::client::http::{
HttpClient as AwsClient, HttpConnector as AwsConnector,
HttpConnectorFuture as AwsConnectorFuture, HttpConnectorFuture, HttpConnectorSettings,
SharedHttpConnector,
};
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest as AwsHttpRequest, HttpResponse};
use aws_smithy_runtime_api::client::result::ConnectorError;
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
use aws_smithy_runtime_api::http::StatusCode;
use aws_smithy_types::body::SdkBody;
use futures::AsyncReadExt;
use http_client::{AsyncBody, Inner};
use http_client::{HttpClient, Request};
use tokio::runtime::Handle;
struct AwsHttpConnector {
client: Arc<dyn HttpClient>,
handle: Handle,
}
impl std::fmt::Debug for AwsHttpConnector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AwsHttpConnector").finish()
}
}
impl AwsConnector for AwsHttpConnector {
fn call(&self, request: AwsHttpRequest) -> AwsConnectorFuture {
let req = match request.try_into_http1x() {
Ok(req) => req,
Err(err) => {
return HttpConnectorFuture::ready(Err(ConnectorError::other(err.into(), None)))
}
};
let (parts, body) = req.into_parts();
let response = self
.client
.send(Request::from_parts(parts, convert_to_async_body(body)));
let handle = self.handle.clone();
HttpConnectorFuture::new(async move {
let response = match response.await {
Ok(response) => response,
Err(err) => return Err(ConnectorError::other(err.into(), None)),
};
let (parts, body) = response.into_parts();
let body = convert_to_sdk_body(body, handle).await;
Ok(HttpResponse::new(
StatusCode::try_from(parts.status.as_u16()).unwrap(),
body,
))
})
}
}
#[derive(Clone)]
pub struct AwsHttpClient {
client: Arc<dyn HttpClient>,
handler: Handle,
}
impl std::fmt::Debug for AwsHttpClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AwsHttpClient").finish()
}
}
impl AwsHttpClient {
pub fn new(client: Arc<dyn HttpClient>, handle: Handle) -> Self {
Self {
client,
handler: handle,
}
}
}
impl AwsClient for AwsHttpClient {
fn http_connector(
&self,
_settings: &HttpConnectorSettings,
_components: &RuntimeComponents,
) -> SharedHttpConnector {
SharedHttpConnector::new(AwsHttpConnector {
client: self.client.clone(),
handle: self.handler.clone(),
})
}
}
pub async fn convert_to_sdk_body(body: AsyncBody, handle: Handle) -> SdkBody {
match body.0 {
Inner::Empty => SdkBody::empty(),
Inner::Bytes(bytes) => SdkBody::from(bytes.into_inner()),
Inner::AsyncReader(mut reader) => {
let buffer = handle.spawn(async move {
let mut buffer = Vec::new();
let _ = reader.read_to_end(&mut buffer).await;
buffer
});
SdkBody::from(buffer.await.unwrap_or_default())
}
}
}
pub fn convert_to_async_body(body: SdkBody) -> AsyncBody {
match body.bytes() {
Some(bytes) => AsyncBody::from((*bytes).to_vec()),
None => AsyncBody::empty(),
}
}

View File

@@ -1,28 +0,0 @@
[package]
name = "bedrock"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/bedrock.rs"
[features]
default = []
schemars = ["dep:schemars"]
[dependencies]
anyhow.workspace = true
aws-sdk-bedrockruntime = { workspace = true, features = ["behavior-version-latest"] }
aws-smithy-types = {workspace = true}
futures.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
strum.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }

View File

@@ -1 +0,0 @@
../../LICENSE-GPL

View File

@@ -1,166 +0,0 @@
mod models;
use std::pin::Pin;
use anyhow::{anyhow, Context, Error, Result};
use aws_sdk_bedrockruntime as bedrock;
pub use aws_sdk_bedrockruntime as bedrock_client;
pub use aws_sdk_bedrockruntime::types::{
ContentBlock as BedrockInnerContent, SpecificToolChoice as BedrockSpecificTool,
ToolChoice as BedrockToolChoice, ToolInputSchema as BedrockToolInputSchema,
ToolSpecification as BedrockTool,
};
use aws_smithy_types::{Document, Number as AwsNumber};
pub use bedrock::operation::converse_stream::ConverseStreamInput as BedrockStreamingRequest;
pub use bedrock::types::{
ContentBlock as BedrockRequestContent, ConversationRole as BedrockRole,
ConverseOutput as BedrockResponse, ConverseStreamOutput as BedrockStreamingResponse,
Message as BedrockMessage, ResponseStream as BedrockResponseStream,
};
use futures::stream::{self, BoxStream, Stream};
use serde::{Deserialize, Serialize};
use serde_json::{Number, Value};
use thiserror::Error;
pub use crate::models::*;
pub async fn complete(
client: &bedrock::Client,
request: Request,
) -> Result<BedrockResponse, BedrockError> {
let response = bedrock::Client::converse(client)
.model_id(request.model.clone())
.set_messages(request.messages.into())
.send()
.await
.context("failed to send request to Bedrock");
match response {
Ok(output) => output
.output
.ok_or_else(|| BedrockError::Other(anyhow!("no output"))),
Err(err) => Err(BedrockError::Other(err)),
}
}
pub async fn stream_completion(
client: bedrock::Client,
request: Request,
handle: tokio::runtime::Handle,
) -> Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>, Error> {
handle
.spawn(async move {
let response = bedrock::Client::converse_stream(&client)
.model_id(request.model.clone())
.set_messages(request.messages.into())
.send()
.await;
match response {
Ok(output) => {
let stream: Pin<
Box<
dyn Stream<Item = Result<BedrockStreamingResponse, BedrockError>>
+ Send,
>,
> = Box::pin(stream::unfold(output.stream, |mut stream| async move {
match stream.recv().await {
Ok(Some(output)) => Some((Ok(output), stream)),
Ok(None) => None,
Err(err) => {
Some((
// TODO: Figure out how we can capture Throttling Exceptions
Err(BedrockError::ClientError(anyhow!(
"{:?}",
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
))),
stream,
))
}
}
}));
Ok(stream)
}
Err(err) => Err(anyhow!(
"{:?}",
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
)),
}
})
.await
.map_err(|err| anyhow!("failed to spawn task: {err:?}"))?
}
pub fn aws_document_to_value(document: &Document) -> Value {
match document {
Document::Null => Value::Null,
Document::Bool(value) => Value::Bool(*value),
Document::Number(value) => match *value {
AwsNumber::PosInt(value) => Value::Number(Number::from(value)),
AwsNumber::NegInt(value) => Value::Number(Number::from(value)),
AwsNumber::Float(value) => Value::Number(Number::from_f64(value).unwrap()),
},
Document::String(value) => Value::String(value.clone()),
Document::Array(array) => Value::Array(array.iter().map(aws_document_to_value).collect()),
Document::Object(map) => Value::Object(
map.iter()
.map(|(key, value)| (key.clone(), aws_document_to_value(value)))
.collect(),
),
}
}
pub fn value_to_aws_document(value: &Value) -> Document {
match value {
Value::Null => Document::Null,
Value::Bool(value) => Document::Bool(*value),
Value::Number(value) => {
if let Some(value) = value.as_u64() {
Document::Number(AwsNumber::PosInt(value))
} else if let Some(value) = value.as_i64() {
Document::Number(AwsNumber::NegInt(value))
} else if let Some(value) = value.as_f64() {
Document::Number(AwsNumber::Float(value))
} else {
Document::Null
}
}
Value::String(value) => Document::String(value.clone()),
Value::Array(array) => Document::Array(array.iter().map(value_to_aws_document).collect()),
Value::Object(map) => Document::Object(
map.iter()
.map(|(key, value)| (key.clone(), value_to_aws_document(value)))
.collect(),
),
}
}
#[derive(Debug)]
pub struct Request {
pub model: String,
pub max_tokens: u32,
pub messages: Vec<BedrockMessage>,
pub tools: Vec<BedrockTool>,
pub tool_choice: Option<BedrockToolChoice>,
pub system: Option<String>,
pub metadata: Option<Metadata>,
pub stop_sequences: Vec<String>,
pub temperature: Option<f32>,
pub top_k: Option<u32>,
pub top_p: Option<f32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
pub user_id: Option<String>,
}
#[derive(Error, Debug)]
pub enum BedrockError {
#[error("client error: {0}")]
ClientError(anyhow::Error),
#[error("extension error: {0}")]
ExtensionError(anyhow::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
}

View File

@@ -1,207 +0,0 @@
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use strum::EnumIter;
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
// Anthropic models (already included)
#[default]
#[serde(rename = "claude-3-5-sonnet-v2", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
Claude3_7Sonnet,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
Claude3Sonnet,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku,
// Amazon Nova Models
AmazonNovaLite,
AmazonNovaMicro,
AmazonNovaPro,
// AI21 models
AI21J2GrandeInstruct,
AI21J2JumboInstruct,
AI21J2Mid,
AI21J2MidV1,
AI21J2Ultra,
AI21J2UltraV1_8k,
AI21J2UltraV1,
AI21JambaInstructV1,
AI21Jamba15LargeV1,
AI21Jamba15MiniV1,
// Cohere models
CohereCommandTextV14_4k,
CohereCommandRV1,
CohereCommandRPlusV1,
CohereCommandLightTextV14_4k,
// Meta models
MetaLlama38BInstructV1,
MetaLlama370BInstructV1,
MetaLlama318BInstructV1_128k,
MetaLlama318BInstructV1,
MetaLlama3170BInstructV1_128k,
MetaLlama3170BInstructV1,
MetaLlama3211BInstructV1,
MetaLlama3290BInstructV1,
MetaLlama321BInstructV1,
MetaLlama323BInstructV1,
// Mistral models
MistralMistral7BInstructV0,
MistralMixtral8x7BInstructV0,
MistralMistralLarge2402V1,
MistralMistralSmall2402V1,
#[serde(rename = "custom")]
Custom {
name: String,
max_tokens: usize,
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
display_name: Option<String>,
max_output_tokens: Option<u32>,
default_temperature: Option<f32>,
},
}
impl Model {
pub fn from_id(id: &str) -> anyhow::Result<Self> {
if id.starts_with("claude-3-5-sonnet-v2") {
Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-opus") {
Ok(Self::Claude3Opus)
} else if id.starts_with("claude-3-sonnet") {
Ok(Self::Claude3Sonnet)
} else if id.starts_with("claude-3-5-haiku") {
Ok(Self::Claude3_5Haiku)
} else if id.starts_with("claude-3-7-sonnet") {
Ok(Self::Claude3_7Sonnet)
} else {
Err(anyhow!("invalid model id"))
}
}
pub fn id(&self) -> &str {
match self {
Model::Claude3_5Sonnet => "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
Model::Claude3Opus => "us.anthropic.claude-3-opus-20240229-v1:0",
Model::Claude3Sonnet => "us.anthropic.claude-3-sonnet-20240229-v1:0",
Model::Claude3_5Haiku => "us.anthropic.claude-3-5-haiku-20241022-v1:0",
Model::Claude3_7Sonnet => "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
Model::AmazonNovaLite => "us.amazon.nova-lite-v1:0",
Model::AmazonNovaMicro => "us.amazon.nova-micro-v1:0",
Model::AmazonNovaPro => "us.amazon.nova-pro-v1:0",
Model::AI21J2GrandeInstruct => "ai21.j2-grande-instruct",
Model::AI21J2JumboInstruct => "ai21.j2-jumbo-instruct",
Model::AI21J2Mid => "ai21.j2-mid",
Model::AI21J2MidV1 => "ai21.j2-mid-v1",
Model::AI21J2Ultra => "ai21.j2-ultra",
Model::AI21J2UltraV1_8k => "ai21.j2-ultra-v1:0:8k",
Model::AI21J2UltraV1 => "ai21.j2-ultra-v1",
Model::AI21JambaInstructV1 => "ai21.jamba-instruct-v1:0",
Model::AI21Jamba15LargeV1 => "ai21.jamba-1-5-large-v1:0",
Model::AI21Jamba15MiniV1 => "ai21.jamba-1-5-mini-v1:0",
Model::CohereCommandTextV14_4k => "cohere.command-text-v14:7:4k",
Model::CohereCommandRV1 => "cohere.command-r-v1:0",
Model::CohereCommandRPlusV1 => "cohere.command-r-plus-v1:0",
Model::CohereCommandLightTextV14_4k => "cohere.command-light-text-v14:7:4k",
Model::MetaLlama38BInstructV1 => "meta.llama3-8b-instruct-v1:0",
Model::MetaLlama370BInstructV1 => "meta.llama3-70b-instruct-v1:0",
Model::MetaLlama318BInstructV1_128k => "meta.llama3-1-8b-instruct-v1:0:128k",
Model::MetaLlama318BInstructV1 => "meta.llama3-1-8b-instruct-v1:0",
Model::MetaLlama3170BInstructV1_128k => "meta.llama3-1-70b-instruct-v1:0:128k",
Model::MetaLlama3170BInstructV1 => "meta.llama3-1-70b-instruct-v1:0",
Model::MetaLlama3211BInstructV1 => "meta.llama3-2-11b-instruct-v1:0",
Model::MetaLlama3290BInstructV1 => "meta.llama3-2-90b-instruct-v1:0",
Model::MetaLlama321BInstructV1 => "meta.llama3-2-1b-instruct-v1:0",
Model::MetaLlama323BInstructV1 => "meta.llama3-2-3b-instruct-v1:0",
Model::MistralMistral7BInstructV0 => "mistral.mistral-7b-instruct-v0:2",
Model::MistralMixtral8x7BInstructV0 => "mistral.mixtral-8x7b-instruct-v0:1",
Model::MistralMistralLarge2402V1 => "mistral.mistral-large-2402-v1:0",
Model::MistralMistralSmall2402V1 => "mistral.mistral-small-2402-v1:0",
Self::Custom { name, .. } => name,
}
}
pub fn display_name(&self) -> &str {
match self {
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet v2",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::AmazonNovaLite => "Amazon Nova Lite",
Self::AmazonNovaMicro => "Amazon Nova Micro",
Self::AmazonNovaPro => "Amazon Nova Pro",
Self::AI21J2GrandeInstruct => "AI21 Jurassic2 Grande Instruct",
Self::AI21J2JumboInstruct => "AI21 Jurassic2 Jumbo Instruct",
Self::AI21J2Mid => "AI21 Jurassic2 Mid",
Self::AI21J2MidV1 => "AI21 Jurassic2 Mid V1",
Self::AI21J2Ultra => "AI21 Jurassic2 Ultra",
Self::AI21J2UltraV1_8k => "AI21 Jurassic2 Ultra V1 8K",
Self::AI21J2UltraV1 => "AI21 Jurassic2 Ultra V1",
Self::AI21JambaInstructV1 => "AI21 Jamba Instruct",
Self::AI21Jamba15LargeV1 => "AI21 Jamba 1.5 Large",
Self::AI21Jamba15MiniV1 => "AI21 Jamba 1.5 Mini",
Self::CohereCommandTextV14_4k => "Cohere Command Text V14 4K",
Self::CohereCommandRV1 => "Cohere Command R V1",
Self::CohereCommandRPlusV1 => "Cohere Command R Plus V1",
Self::CohereCommandLightTextV14_4k => "Cohere Command Light Text V14 4K",
Self::MetaLlama38BInstructV1 => "Meta Llama 3 8B Instruct V1",
Self::MetaLlama370BInstructV1 => "Meta Llama 3 70B Instruct V1",
Self::MetaLlama318BInstructV1_128k => "Meta Llama 3 1.8B Instruct V1 128K",
Self::MetaLlama318BInstructV1 => "Meta Llama 3 1.8B Instruct V1",
Self::MetaLlama3170BInstructV1_128k => "Meta Llama 3 1 70B Instruct V1 128K",
Self::MetaLlama3170BInstructV1 => "Meta Llama 3 1 70B Instruct V1",
Self::MetaLlama3211BInstructV1 => "Meta Llama 3 2 11B Instruct V1",
Self::MetaLlama3290BInstructV1 => "Meta Llama 3 2 90B Instruct V1",
Self::MetaLlama321BInstructV1 => "Meta Llama 3 2 1B Instruct V1",
Self::MetaLlama323BInstructV1 => "Meta Llama 3 2 3B Instruct V1",
Self::MistralMistral7BInstructV0 => "Mistral 7B Instruct V0",
Self::MistralMixtral8x7BInstructV0 => "Mistral Mixtral 8x7B Instruct V0",
Self::MistralMistralLarge2402V1 => "Mistral Large 2402 V1",
Self::MistralMistralSmall2402V1 => "Mistral Small 2402 V1",
Self::Custom {
display_name, name, ..
} => display_name.as_deref().unwrap_or(name),
}
}
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet => 200_000,
Self::Custom { max_tokens, .. } => *max_tokens,
_ => 200_000,
}
}
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
Self::Claude3_5Sonnet => 8_192,
Self::Custom {
max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096),
_ => 4_096,
}
}
pub fn default_temperature(&self) -> f32 {
match self {
Self::Claude3_5Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet => 1.0,
Self::Custom {
default_temperature,
..
} => default_temperature.unwrap_or(1.0),
_ => 1.0,
}
}
}

View File

@@ -81,7 +81,7 @@ impl Render for Breadcrumbs {
} }
text_style.color = Color::Muted.color(cx); text_style.color = Color::Muted.color(cx);
StyledText::new(segment.text.replace('\n', "")) StyledText::new(segment.text.replace('\n', ""))
.with_highlights(&text_style, segment.highlights.unwrap_or_default()) .with_highlights(&text_style, segment.highlights.unwrap_or_default())
.into_any() .into_any()
}); });

View File

@@ -3,8 +3,7 @@ use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter};
use language::{Language, LanguageRegistry}; use language::{Language, LanguageRegistry};
use rope::Rope; use rope::Rope;
use std::cmp::Ordering; use std::{cmp, future::Future, iter, ops::Range, sync::Arc};
use std::{future::Future, iter, ops::Range, sync::Arc};
use sum_tree::SumTree; use sum_tree::SumTree;
use text::ToOffset as _; use text::ToOffset as _;
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point}; use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point};
@@ -69,6 +68,7 @@ pub struct DiffHunk {
/// The range in the buffer's diff base text to which this hunk corresponds. /// The range in the buffer's diff base text to which this hunk corresponds.
pub diff_base_byte_range: Range<usize>, pub diff_base_byte_range: Range<usize>,
pub secondary_status: DiffHunkSecondaryStatus, pub secondary_status: DiffHunkSecondaryStatus,
pub secondary_diff_base_byte_range: Option<Range<usize>>,
} }
/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range. /// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
@@ -109,18 +109,13 @@ 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 { fn cmp(
if self &self,
.cmp(&cursor_location.buffer_range.start, buffer) cursor_location: &DiffHunkSummary,
.is_lt() buffer: &text::BufferSnapshot,
{ ) -> cmp::Ordering {
Ordering::Less self.cmp(&cursor_location.buffer_range.end, buffer)
} else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
Ordering::Greater
} else {
Ordering::Equal
}
} }
} }
@@ -176,110 +171,97 @@ impl BufferDiffSnapshot {
} }
} }
pub fn new_secondary_text_for_stage_or_unstage( fn buffer_range_to_unchanged_diff_base_range(
&self, &self,
stage: bool, buffer_range: Range<Anchor>,
hunks: impl Iterator<Item = (Range<Anchor>, Range<usize>)>,
buffer: &text::BufferSnapshot, buffer: &text::BufferSnapshot,
cx: &mut App, ) -> Option<Range<usize>> {
) -> Option<Rope> { let mut hunks = self.inner.hunks.iter();
let secondary_diff = self.secondary_diff()?; let mut start = 0;
let head_text = self.base_text().map(|text| text.as_rope().clone()); let mut pos = buffer.anchor_before(0);
let index_text = secondary_diff while let Some(hunk) = hunks.next() {
.base_text() assert!(buffer_range.start.cmp(&pos, buffer).is_ge());
.map(|text| text.as_rope().clone()); assert!(hunk.buffer_range.start.cmp(&pos, buffer).is_ge());
let (index_text, head_text) = match (index_text, head_text) { if hunk
(Some(index_text), Some(head_text)) => (index_text, head_text), .buffer_range
// file is deleted in both index and head
(None, None) => return None,
// file is deleted in index
(None, Some(head_text)) => {
return if stage {
Some(buffer.as_rope().clone())
} else {
Some(head_text)
}
}
// file exists in the index, but is deleted in head
(Some(_), None) => {
return if stage {
Some(buffer.as_rope().clone())
} else {
None
}
}
};
let mut secondary_cursor = secondary_diff.inner.hunks.cursor::<DiffHunkSummary>(buffer);
secondary_cursor.next(buffer);
let mut edits = Vec::new();
let mut prev_secondary_hunk_buffer_offset = 0;
let mut prev_secondary_hunk_base_text_offset = 0;
for (buffer_range, diff_base_byte_range) in hunks {
let skipped_hunks = secondary_cursor.slice(&buffer_range.start, Bias::Left, buffer);
if let Some(secondary_hunk) = skipped_hunks.last() {
prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;
prev_secondary_hunk_buffer_offset =
secondary_hunk.buffer_range.end.to_offset(buffer);
}
let mut buffer_offset_range = buffer_range.to_offset(buffer);
let start_overshoot = buffer_offset_range.start - prev_secondary_hunk_buffer_offset;
let mut secondary_base_text_start =
prev_secondary_hunk_base_text_offset + start_overshoot;
while let Some(secondary_hunk) = secondary_cursor.item().filter(|item| {
item.buffer_range
.start .start
.cmp(&buffer_range.end, buffer) .cmp(&buffer_range.end, buffer)
.is_le() .is_ge()
}) { {
let secondary_hunk_offset_range = secondary_hunk.buffer_range.to_offset(buffer); // target buffer range is contained in the unchanged stretch leading up to this next hunk,
prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; // so do a final adjustment based on that
prev_secondary_hunk_buffer_offset = secondary_hunk_offset_range.end; break;
secondary_base_text_start =
secondary_base_text_start.min(secondary_hunk.diff_base_byte_range.start);
buffer_offset_range.start = buffer_offset_range
.start
.min(secondary_hunk_offset_range.start);
secondary_cursor.next(buffer);
} }
let end_overshoot = buffer_offset_range // if the target buffer range intersects this hunk at all, no dice
.end if buffer_range
.saturating_sub(prev_secondary_hunk_buffer_offset); .start
let secondary_base_text_end = prev_secondary_hunk_base_text_offset + end_overshoot; .cmp(&hunk.buffer_range.end, buffer)
.is_lt()
{
return None;
}
let secondary_base_text_range = secondary_base_text_start..secondary_base_text_end; start += hunk.buffer_range.start.to_offset(buffer) - pos.to_offset(buffer);
buffer_offset_range.end = buffer_offset_range start += hunk.diff_base_byte_range.end - hunk.diff_base_byte_range.start;
.end pos = hunk.buffer_range.end;
.max(prev_secondary_hunk_buffer_offset); }
start += buffer_range.start.to_offset(buffer) - pos.to_offset(buffer);
let end = start + buffer_range.end.to_offset(buffer) - buffer_range.start.to_offset(buffer);
Some(start..end)
}
let replacement_text = if stage { pub fn secondary_edits_for_stage_or_unstage(
&self,
stage: bool,
hunks: impl Iterator<Item = (Range<usize>, Option<Range<usize>>, Range<Anchor>)>,
buffer: &text::BufferSnapshot,
) -> Vec<(Range<usize>, String)> {
let Some(secondary_diff) = self.secondary_diff() else {
log::debug!("no secondary diff");
return Vec::new();
};
let index_base = secondary_diff.base_text().map_or_else(
|| Rope::from(""),
|snapshot| snapshot.text.as_rope().clone(),
);
let head_base = self.base_text().map_or_else(
|| Rope::from(""),
|snapshot| snapshot.text.as_rope().clone(),
);
log::debug!("original: {:?}", index_base.to_string());
let mut edits = Vec::new();
for (diff_base_byte_range, secondary_diff_base_byte_range, buffer_range) in hunks {
let (index_byte_range, replacement_text) = if stage {
log::debug!("staging"); log::debug!("staging");
buffer let mut replacement_text = String::new();
.text_for_range(buffer_offset_range) let Some(index_byte_range) = secondary_diff_base_byte_range.clone() else {
.collect::<String>() log::debug!("not a stageable hunk");
continue;
};
log::debug!("using {:?}", index_byte_range);
for chunk in buffer.text_for_range(buffer_range.clone()) {
replacement_text.push_str(chunk);
}
(index_byte_range, replacement_text)
} else { } else {
log::debug!("unstaging"); log::debug!("unstaging");
head_text let mut replacement_text = String::new();
.chunks_in_range(diff_base_byte_range.clone()) let Some(index_byte_range) = secondary_diff
.collect::<String>() .buffer_range_to_unchanged_diff_base_range(buffer_range.clone(), &buffer)
else {
log::debug!("not an unstageable hunk");
continue;
}; };
edits.push((secondary_base_text_range, replacement_text)); for chunk in head_base.chunks_in_range(diff_base_byte_range.clone()) {
replacement_text.push_str(chunk);
} }
(index_byte_range, replacement_text)
let buffer = cx.new(|cx| { };
language::Buffer::local_normalized(index_text, text::LineEnding::default(), cx) edits.push((index_byte_range, replacement_text));
}); }
let new_text = buffer.update(cx, |buffer, cx| { log::debug!("edits: {edits:?}");
buffer.edit(edits, None, cx); edits
buffer.as_rope().clone()
});
Some(new_text)
} }
} }
@@ -340,12 +322,13 @@ impl BufferDiffInner {
} }
let mut secondary_status = DiffHunkSecondaryStatus::None; let mut secondary_status = DiffHunkSecondaryStatus::None;
let mut secondary_diff_base_byte_range = None;
if let Some(secondary_cursor) = secondary_cursor.as_mut() { if let Some(secondary_cursor) = secondary_cursor.as_mut() {
if start_anchor if start_anchor
.cmp(&secondary_cursor.start().buffer_range.start, buffer) .cmp(&secondary_cursor.start().buffer_range.start, buffer)
.is_gt() .is_gt()
{ {
secondary_cursor.seek_forward(&start_anchor, Bias::Left, buffer); secondary_cursor.seek_forward(&end_anchor, Bias::Left, buffer);
} }
if let Some(secondary_hunk) = secondary_cursor.item() { if let Some(secondary_hunk) = secondary_cursor.item() {
@@ -356,12 +339,12 @@ impl BufferDiffInner {
} }
if secondary_range == (start_point..end_point) { if secondary_range == (start_point..end_point) {
secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
secondary_diff_base_byte_range =
Some(secondary_hunk.diff_base_byte_range.clone());
} else if secondary_range.start <= end_point { } else if secondary_range.start <= end_point {
secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
} }
} }
} else {
log::debug!("no secondary cursor!!");
} }
return Some(DiffHunk { return Some(DiffHunk {
@@ -369,6 +352,7 @@ impl BufferDiffInner {
diff_base_byte_range: start_base..end_base, diff_base_byte_range: start_base..end_base,
buffer_range: start_anchor..end_anchor, buffer_range: start_anchor..end_anchor,
secondary_status, secondary_status,
secondary_diff_base_byte_range,
}); });
}) })
} }
@@ -403,6 +387,7 @@ impl BufferDiffInner {
buffer_range: hunk.buffer_range.clone(), buffer_range: hunk.buffer_range.clone(),
// The secondary status is not used by callers of this method. // The secondary status is not used by callers of this method.
secondary_status: DiffHunkSecondaryStatus::None, secondary_status: DiffHunkSecondaryStatus::None,
secondary_diff_base_byte_range: None,
}) })
}) })
} }
@@ -423,12 +408,12 @@ impl BufferDiffInner {
.start .start
.cmp(&old_hunk.buffer_range.start, new_snapshot) .cmp(&old_hunk.buffer_range.start, new_snapshot)
{ {
Ordering::Less => { cmp::Ordering::Less => {
start.get_or_insert(new_hunk.buffer_range.start); start.get_or_insert(new_hunk.buffer_range.start);
end.replace(new_hunk.buffer_range.end); end.replace(new_hunk.buffer_range.end);
new_cursor.next(new_snapshot); new_cursor.next(new_snapshot);
} }
Ordering::Equal => { cmp::Ordering::Equal => {
if new_hunk != old_hunk { if new_hunk != old_hunk {
start.get_or_insert(new_hunk.buffer_range.start); start.get_or_insert(new_hunk.buffer_range.start);
if old_hunk if old_hunk
@@ -446,7 +431,7 @@ impl BufferDiffInner {
new_cursor.next(new_snapshot); new_cursor.next(new_snapshot);
old_cursor.next(new_snapshot); old_cursor.next(new_snapshot);
} }
Ordering::Greater => { cmp::Ordering::Greater => {
start.get_or_insert(old_hunk.buffer_range.start); start.get_or_insert(old_hunk.buffer_range.start);
end.replace(old_hunk.buffer_range.end); end.replace(old_hunk.buffer_range.end);
old_cursor.next(new_snapshot); old_cursor.next(new_snapshot);
@@ -1074,7 +1059,6 @@ mod tests {
use rand::{rngs::StdRng, Rng as _}; use rand::{rngs::StdRng, Rng as _};
use text::{Buffer, BufferId, Rope}; use text::{Buffer, BufferId, Rope};
use unindent::Unindent as _; use unindent::Unindent as _;
use util::test::marked_text_ranges;
#[ctor::ctor] #[ctor::ctor]
fn init_logger() { fn init_logger() {
@@ -1273,208 +1257,6 @@ mod tests {
); );
} }
#[gpui::test]
async fn test_stage_hunk(cx: &mut TestAppContext) {
struct Example {
name: &'static str,
head_text: String,
index_text: String,
buffer_marked_text: String,
final_index_text: String,
}
let table = [
Example {
name: "uncommitted hunk straddles end of unstaged hunk",
head_text: "
one
two
three
four
five
"
.unindent(),
index_text: "
one
TWO_HUNDRED
three
FOUR_HUNDRED
five
"
.unindent(),
buffer_marked_text: "
ZERO
one
two
«THREE_HUNDRED
FOUR_HUNDRED»
five
SIX
"
.unindent(),
final_index_text: "
one
two
THREE_HUNDRED
FOUR_HUNDRED
five
"
.unindent(),
},
Example {
name: "uncommitted hunk straddles start of unstaged hunk",
head_text: "
one
two
three
four
five
"
.unindent(),
index_text: "
one
TWO_HUNDRED
three
FOUR_HUNDRED
five
"
.unindent(),
buffer_marked_text: "
ZERO
one
«TWO_HUNDRED
THREE_HUNDRED»
four
five
SIX
"
.unindent(),
final_index_text: "
one
TWO_HUNDRED
THREE_HUNDRED
four
five
"
.unindent(),
},
Example {
name: "uncommitted hunk strictly contains unstaged hunks",
head_text: "
one
two
three
four
five
six
seven
"
.unindent(),
index_text: "
one
TWO
THREE
FOUR
FIVE
SIX
seven
"
.unindent(),
buffer_marked_text: "
one
TWO
«THREE_HUNDRED
FOUR
FIVE_HUNDRED»
SIX
seven
"
.unindent(),
final_index_text: "
one
TWO
THREE_HUNDRED
FOUR
FIVE_HUNDRED
SIX
seven
"
.unindent(),
},
Example {
name: "uncommitted deletion hunk",
head_text: "
one
two
three
four
five
"
.unindent(),
index_text: "
one
two
three
four
five
"
.unindent(),
buffer_marked_text: "
one
ˇfive
"
.unindent(),
final_index_text: "
one
five
"
.unindent(),
},
];
for example in table {
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let uncommitted_diff =
BufferDiff::build_sync(buffer.clone(), example.head_text.clone(), cx);
let unstaged_diff =
BufferDiff::build_sync(buffer.clone(), example.index_text.clone(), cx);
let uncommitted_diff = BufferDiffSnapshot {
inner: uncommitted_diff,
secondary_diff: Some(Box::new(BufferDiffSnapshot {
inner: unstaged_diff,
is_single_insertion: false,
secondary_diff: None,
})),
is_single_insertion: false,
};
let range = buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
let new_index_text = cx
.update(|cx| {
uncommitted_diff.new_secondary_text_for_stage_or_unstage(
true,
uncommitted_diff
.hunks_intersecting_range(range, &buffer)
.map(|hunk| {
(hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone())
}),
&buffer,
cx,
)
})
.unwrap()
.to_string();
pretty_assertions::assert_eq!(
new_index_text,
example.final_index_text,
"example: {}",
example.name
);
}
}
#[gpui::test] #[gpui::test]
async fn test_buffer_diff_compare(cx: &mut TestAppContext) { async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
let base_text = " let base_text = "
@@ -1600,7 +1382,7 @@ mod tests {
} }
#[gpui::test(iterations = 100)] #[gpui::test(iterations = 100)]
async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) { async fn test_secondary_edits_for_stage_unstage(cx: &mut TestAppContext, mut rng: StdRng) {
fn gen_line(rng: &mut StdRng) -> String { fn gen_line(rng: &mut StdRng) -> String {
if rng.gen_bool(0.2) { if rng.gen_bool(0.2) {
"\n".to_owned() "\n".to_owned()
@@ -1665,7 +1447,7 @@ mod tests {
fn uncommitted_diff( fn uncommitted_diff(
working_copy: &language::BufferSnapshot, working_copy: &language::BufferSnapshot,
index_text: &Rope, index_text: &Entity<language::Buffer>,
head_text: String, head_text: String,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> BufferDiff { ) -> BufferDiff {
@@ -1674,7 +1456,7 @@ mod tests {
buffer_id: working_copy.remote_id(), buffer_id: working_copy.remote_id(),
inner: BufferDiff::build_sync( inner: BufferDiff::build_sync(
working_copy.text.clone(), working_copy.text.clone(),
index_text.to_string(), index_text.read_with(cx, |index_text, _| index_text.text()),
cx, cx,
), ),
secondary_diff: None, secondary_diff: None,
@@ -1705,11 +1487,17 @@ mod tests {
) )
}); });
let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot()); let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
let mut index_text = if rng.gen() { let index_text = cx.new(|cx| {
language::Buffer::local_normalized(
if rng.gen() {
Rope::from(head_text.as_str()) Rope::from(head_text.as_str())
} else { } else {
working_copy.as_rope().clone() working_copy.as_rope().clone()
}; },
text::LineEnding::default(),
cx,
)
});
let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx); let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
let mut hunks = cx.update(|cx| { let mut hunks = cx.update(|cx| {
@@ -1723,29 +1511,37 @@ mod tests {
for _ in 0..operations { for _ in 0..operations {
let i = rng.gen_range(0..hunks.len()); let i = rng.gen_range(0..hunks.len());
let hunk = &mut hunks[i]; let hunk = &mut hunks[i];
let stage = match hunk.secondary_status { let hunk_fields = (
DiffHunkSecondaryStatus::HasSecondaryHunk => { hunk.diff_base_byte_range.clone(),
hunk.secondary_diff_base_byte_range.clone(),
hunk.buffer_range.clone(),
);
let stage = match (
hunk.secondary_status,
hunk.secondary_diff_base_byte_range.clone(),
) {
(DiffHunkSecondaryStatus::HasSecondaryHunk, Some(_)) => {
hunk.secondary_status = DiffHunkSecondaryStatus::None; hunk.secondary_status = DiffHunkSecondaryStatus::None;
hunk.secondary_diff_base_byte_range = None;
true true
} }
DiffHunkSecondaryStatus::None => { (DiffHunkSecondaryStatus::None, None) => {
hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
// We don't look at this, just notice whether it's Some or not.
hunk.secondary_diff_base_byte_range = Some(17..17);
false false
} }
_ => unreachable!(), _ => unreachable!(),
}; };
let snapshot = cx.update(|cx| diff.snapshot(cx)); let snapshot = cx.update(|cx| diff.snapshot(cx));
index_text = cx.update(|cx| { let edits = snapshot.secondary_edits_for_stage_or_unstage(
snapshot
.new_secondary_text_for_stage_or_unstage(
stage, stage,
[(hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone())] [hunk_fields].into_iter(),
.into_iter(),
&working_copy, &working_copy,
cx, );
) index_text.update(cx, |index_text, cx| {
.unwrap() index_text.edit(edits, None, cx);
}); });
diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx); diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
@@ -1754,7 +1550,6 @@ mod tests {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
assert_eq!(hunks.len(), found_hunks.len()); assert_eq!(hunks.len(), found_hunks.len());
for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) { for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
assert_eq!( assert_eq!(
expected_hunk.buffer_range.to_point(&working_copy), expected_hunk.buffer_range.to_point(&working_copy),
@@ -1765,6 +1560,10 @@ mod tests {
found_hunk.diff_base_byte_range found_hunk.diff_base_byte_range
); );
assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status); assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
assert_eq!(
expected_hunk.secondary_diff_base_byte_range.is_some(),
found_hunk.secondary_diff_base_byte_range.is_some()
)
} }
hunks = found_hunks; hunks = found_hunks;
} }

View File

@@ -48,7 +48,7 @@ pub struct ChannelPathsInsertGuard<'a> {
channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>, 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 { pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
let mut ret = false; let mut ret = false;
let parent_path = channel_proto 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) { fn drop(&mut self) {
self.channels_ordered.sort_by(|a, b| { self.channels_ordered.sort_by(|a, b| {
let a = channel_path_sorting_key(*a, self.channels_by_id); let a = channel_path_sorting_key(*a, self.channels_by_id);

View File

@@ -40,6 +40,3 @@ fork.workspace = true
core-foundation.workspace = true core-foundation.workspace = true
core-services = "0.2" core-services = "0.2"
plist = "1.3" plist = "1.3"
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true

View File

@@ -521,108 +521,30 @@ mod flatpak {
} }
} }
// todo("windows")
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod 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 crate::{Detect, InstalledApp};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::process::ExitStatus; use std::process::ExitStatus;
fn check_single_instance() -> bool { struct App;
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);
impl InstalledApp for App { impl InstalledApp for App {
fn zed_version_string(&self) -> String { fn zed_version_string(&self) -> String {
format!( unimplemented!()
"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(),
)
} }
fn launch(&self, _ipc_url: String) -> anyhow::Result<()> {
fn launch(&self, ipc_url: String) -> anyhow::Result<()> { unimplemented!()
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)?;
} }
} fn run_foreground(&self, _ipc_url: String) -> io::Result<ExitStatus> {
Ok(()) 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()
} }
} }
impl Detect { impl Detect {
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> { pub fn detect(_path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
let path = if let Some(path) = path { Ok(App)
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))
} }
} }
} }

View File

@@ -22,7 +22,6 @@ async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
chrono = { workspace = true, features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
clock.workspace = true clock.workspace = true
collections.workspace = true collections.workspace = true
credentials_provider.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true

View File

@@ -15,7 +15,6 @@ use async_tungstenite::tungstenite::{
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use clock::SystemClock; use clock::SystemClock;
use credentials_provider::CredentialsProvider;
use futures::{ use futures::{
channel::oneshot, future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, channel::oneshot, future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt,
TryFutureExt as _, TryStreamExt, TryFutureExt as _, TryStreamExt,
@@ -58,6 +57,14 @@ static ZED_SERVER_URL: LazyLock<Option<String>> =
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok()); LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok()); static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
/// An environment variable whose presence indicates that the development auth
/// provider should be used.
///
/// Only works in development. Setting this environment variable in other release
/// channels is a no-op.
pub static ZED_DEVELOPMENT_AUTH: LazyLock<bool> = LazyLock::new(|| {
std::env::var("ZED_DEVELOPMENT_AUTH").map_or(false, |value| !value.is_empty())
});
pub static IMPERSONATE_LOGIN: LazyLock<Option<String>> = LazyLock::new(|| { pub static IMPERSONATE_LOGIN: LazyLock<Option<String>> = LazyLock::new(|| {
std::env::var("ZED_IMPERSONATE") std::env::var("ZED_IMPERSONATE")
.ok() .ok()
@@ -186,7 +193,7 @@ pub struct Client {
peer: Arc<Peer>, peer: Arc<Peer>,
http: Arc<HttpClientWithUrl>, http: Arc<HttpClientWithUrl>,
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
credentials_provider: ClientCredentialsProvider, credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static>,
state: RwLock<ClientState>, state: RwLock<ClientState>,
handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>, handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>,
@@ -297,46 +304,16 @@ impl Credentials {
} }
} }
pub struct ClientCredentialsProvider { /// A provider for [`Credentials`].
provider: Arc<dyn CredentialsProvider>, ///
} /// Used to abstract over reading and writing credentials to some form of
/// persistence (like the system keychain).
impl ClientCredentialsProvider { trait CredentialsProvider {
pub fn new(cx: &App) -> Self {
Self {
provider: <dyn CredentialsProvider>::global(cx),
}
}
fn server_url(&self, cx: &AsyncApp) -> Result<String> {
cx.update(|cx| ClientSettings::get_global(cx).server_url.clone())
}
/// Reads the credentials from the provider. /// Reads the credentials from the provider.
fn read_credentials<'a>( fn read_credentials<'a>(
&'a self, &'a self,
cx: &'a AsyncApp, cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>> { ) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>>;
async move {
if IMPERSONATE_LOGIN.is_some() {
return None;
}
let server_url = self.server_url(cx).ok()?;
let (user_id, access_token) = self
.provider
.read_credentials(&server_url, cx)
.await
.log_err()
.flatten()?;
Some(Credentials {
user_id: user_id.parse().ok()?,
access_token: String::from_utf8(access_token).ok()?,
})
}
.boxed_local()
}
/// Writes the credentials to the provider. /// Writes the credentials to the provider.
fn write_credentials<'a>( fn write_credentials<'a>(
@@ -344,32 +321,13 @@ impl ClientCredentialsProvider {
user_id: u64, user_id: u64,
access_token: String, access_token: String,
cx: &'a AsyncApp, cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> { ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
async move {
let server_url = self.server_url(cx)?;
self.provider
.write_credentials(
&server_url,
&user_id.to_string(),
access_token.as_bytes(),
cx,
)
.await
}
.boxed_local()
}
/// Deletes the credentials from the provider. /// Deletes the credentials from the provider.
fn delete_credentials<'a>( fn delete_credentials<'a>(
&'a self, &'a self,
cx: &'a AsyncApp, cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> { ) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
async move {
let server_url = self.server_url(cx)?;
self.provider.delete_credentials(&server_url, cx).await
}
.boxed_local()
}
} }
impl Default for ClientState { impl Default for ClientState {
@@ -526,12 +484,27 @@ impl Client {
http: Arc<HttpClientWithUrl>, http: Arc<HttpClientWithUrl>,
cx: &mut App, cx: &mut App,
) -> Arc<Self> { ) -> Arc<Self> {
let use_zed_development_auth = match ReleaseChannel::try_global(cx) {
Some(ReleaseChannel::Dev) => *ZED_DEVELOPMENT_AUTH,
Some(ReleaseChannel::Nightly | ReleaseChannel::Preview | ReleaseChannel::Stable)
| None => false,
};
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
if use_zed_development_auth {
Arc::new(DevelopmentCredentialsProvider {
path: paths::config_dir().join("development_auth"),
})
} else {
Arc::new(KeychainCredentialsProvider)
};
Arc::new(Self { Arc::new(Self {
id: AtomicU64::new(0), id: AtomicU64::new(0),
peer: Peer::new(0), peer: Peer::new(0),
telemetry: Telemetry::new(clock, http.clone(), cx), telemetry: Telemetry::new(clock, http.clone(), cx),
http, http,
credentials_provider: ClientCredentialsProvider::new(cx), credentials_provider,
state: Default::default(), state: Default::default(),
handler_set: Default::default(), handler_set: Default::default(),
@@ -870,6 +843,7 @@ impl Client {
self.state.write().credentials = Some(credentials.clone()); self.state.write().credentials = Some(credentials.clone());
if !read_from_provider && IMPERSONATE_LOGIN.is_none() { if !read_from_provider && IMPERSONATE_LOGIN.is_none() {
self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err(); self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err();
} }
futures::select_biased! { futures::select_biased! {
@@ -1614,6 +1588,130 @@ impl ProtoClient for Client {
} }
} }
#[derive(Serialize, Deserialize)]
struct DevelopmentCredentials {
user_id: u64,
access_token: String,
}
/// A credentials provider that stores credentials in a local file.
///
/// This MUST only be used in development, as this is not a secure way of storing
/// credentials on user machines.
///
/// Its existence is purely to work around the annoyance of having to constantly
/// re-allow access to the system keychain when developing Zed.
struct DevelopmentCredentialsProvider {
path: PathBuf,
}
impl CredentialsProvider for DevelopmentCredentialsProvider {
fn read_credentials<'a>(
&'a self,
_cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>> {
async move {
if IMPERSONATE_LOGIN.is_some() {
return None;
}
let json = std::fs::read(&self.path).log_err()?;
let credentials: DevelopmentCredentials = serde_json::from_slice(&json).log_err()?;
Some(Credentials {
user_id: credentials.user_id,
access_token: credentials.access_token,
})
}
.boxed_local()
}
fn write_credentials<'a>(
&'a self,
user_id: u64,
access_token: String,
_cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
let json = serde_json::to_string(&DevelopmentCredentials {
user_id,
access_token,
})?;
std::fs::write(&self.path, json)?;
Ok(())
}
.boxed_local()
}
fn delete_credentials<'a>(
&'a self,
_cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move { Ok(std::fs::remove_file(&self.path)?) }.boxed_local()
}
}
/// A credentials provider that stores credentials in the system keychain.
struct KeychainCredentialsProvider;
impl CredentialsProvider for KeychainCredentialsProvider {
fn read_credentials<'a>(
&'a self,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>> {
async move {
if IMPERSONATE_LOGIN.is_some() {
return None;
}
let (user_id, access_token) = cx
.update(|cx| cx.read_credentials(&ClientSettings::get_global(cx).server_url))
.log_err()?
.await
.log_err()??;
Some(Credentials {
user_id: user_id.parse().ok()?,
access_token: String::from_utf8(access_token).ok()?,
})
}
.boxed_local()
}
fn write_credentials<'a>(
&'a self,
user_id: u64,
access_token: String,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
cx.update(move |cx| {
cx.write_credentials(
&ClientSettings::get_global(cx).server_url,
&user_id.to_string(),
access_token.as_bytes(),
)
})?
.await
}
.boxed_local()
}
fn delete_credentials<'a>(
&'a self,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
cx.update(move |cx| cx.delete_credentials(&ClientSettings::get_global(cx).server_url))?
.await
}
.boxed_local()
}
}
/// prefix for the zed:// url scheme /// prefix for the zed:// url scheme
pub const ZED_URL_SCHEME: &str = "zed"; pub const ZED_URL_SCHEME: &str = "zed";

View File

@@ -97,7 +97,6 @@ extension.workspace = true
file_finder.workspace = true file_finder.workspace = true
fs = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] } git = { workspace = true, features = ["test-support"] }
git_ui = { workspace = true, features = ["test-support"] }
git_hosting_providers.workspace = true git_hosting_providers.workspace = true
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
hyper.workspace = true hyper.workspace = true

View File

@@ -202,7 +202,7 @@ impl Database {
.await .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 { self.transaction(|tx| async move {
let mut extension_external_ids_by_id = HashMap::default(); let mut extension_external_ids_by_id = HashMap::default();

View File

@@ -256,7 +256,6 @@ async fn perform_completion(
// so that users can use the new version, without having to update Zed. // so that users can use the new version, without having to update Zed.
request.model = match model.as_str() { request.model = match model.as_str() {
"claude-3-5-sonnet" => anthropic::Model::Claude3_5Sonnet.id().to_string(), "claude-3-5-sonnet" => anthropic::Model::Claude3_5Sonnet.id().to_string(),
"claude-3-7-sonnet" => anthropic::Model::Claude3_7Sonnet.id().to_string(),
"claude-3-opus" => anthropic::Model::Claude3Opus.id().to_string(), "claude-3-opus" => anthropic::Model::Claude3Opus.id().to_string(),
"claude-3-haiku" => anthropic::Model::Claude3Haiku.id().to_string(), "claude-3-haiku" => anthropic::Model::Claude3Haiku.id().to_string(),
"claude-3-sonnet" => anthropic::Model::Claude3Sonnet.id().to_string(), "claude-3-sonnet" => anthropic::Model::Claude3Sonnet.id().to_string(),
@@ -451,15 +450,19 @@ async fn check_usage_limit(
return Ok(()); return Ok(());
} }
let user_id = UserId::from_proto(claims.user_id);
let model = state.db.model(provider, model_name)?; let model = state.db.model(provider, model_name)?;
let usage = state
.db
.get_usage(
UserId::from_proto(claims.user_id),
provider,
model_name,
Utc::now(),
)
.await?;
let free_tier = claims.free_tier_monthly_spending_limit(); let free_tier = claims.free_tier_monthly_spending_limit();
let spending_this_month = state if usage.spending_this_month >= free_tier {
.db
.get_user_spending_for_month(user_id, Utc::now())
.await?;
if spending_this_month >= free_tier {
if !claims.has_llm_subscription { if !claims.has_llm_subscription {
return Err(Error::http( return Err(Error::http(
StatusCode::PAYMENT_REQUIRED, StatusCode::PAYMENT_REQUIRED,
@@ -467,8 +470,7 @@ async fn check_usage_limit(
)); ));
} }
let monthly_spend = spending_this_month.saturating_sub(free_tier); if (usage.spending_this_month - free_tier) >= Cents(claims.max_monthly_spend_in_cents) {
if monthly_spend >= Cents(claims.max_monthly_spend_in_cents) {
return Err(Error::Http( return Err(Error::Http(
StatusCode::FORBIDDEN, StatusCode::FORBIDDEN,
"Maximum spending limit reached for this month.".to_string(), "Maximum spending limit reached for this month.".to_string(),
@@ -493,11 +495,6 @@ async fn check_usage_limit(
model.max_tokens_per_minute as usize / users_in_recent_minutes; model.max_tokens_per_minute as usize / users_in_recent_minutes;
let per_user_max_tokens_per_day = model.max_tokens_per_day as usize / users_in_recent_days; let per_user_max_tokens_per_day = model.max_tokens_per_day as usize / users_in_recent_days;
let usage = state
.db
.get_usage(user_id, provider, model_name, Utc::now())
.await?;
let checks = [ let checks = [
( (
usage.requests_this_minute, usage.requests_this_minute,

View File

@@ -27,7 +27,7 @@ fn authorize_access_to_model(
} }
if provider == LanguageModelProvider::Anthropic { if provider == LanguageModelProvider::Anthropic {
if model == "claude-3-5-sonnet" || model == "claude-3-7-sonnet" { if model == "claude-3-5-sonnet" {
return Ok(()); return Ok(());
} }

View File

@@ -393,12 +393,10 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::CreateContext>) .add_request_handler(forward_mutating_project_request::<proto::CreateContext>)
.add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>) .add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
.add_request_handler(forward_mutating_project_request::<proto::Push>) .add_request_handler(forward_mutating_project_request::<proto::Push>)
.add_request_handler(forward_mutating_project_request::<proto::Pull>) .add_request_handler(forward_mutating_project_request::<proto::ForcePush>)
.add_request_handler(forward_mutating_project_request::<proto::Fetch>)
.add_request_handler(forward_mutating_project_request::<proto::Stage>) .add_request_handler(forward_mutating_project_request::<proto::Stage>)
.add_request_handler(forward_mutating_project_request::<proto::Unstage>) .add_request_handler(forward_mutating_project_request::<proto::Unstage>)
.add_request_handler(forward_mutating_project_request::<proto::Commit>) .add_request_handler(forward_mutating_project_request::<proto::Commit>)
.add_request_handler(forward_read_only_project_request::<proto::GetRemotes>)
.add_request_handler(forward_read_only_project_request::<proto::GitShow>) .add_request_handler(forward_read_only_project_request::<proto::GitShow>)
.add_request_handler(forward_read_only_project_request::<proto::GitReset>) .add_request_handler(forward_read_only_project_request::<proto::GitReset>)
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>) .add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
@@ -975,7 +973,7 @@ impl Server {
} }
} }
impl Deref for ConnectionPoolGuard<'_> { impl<'a> Deref for ConnectionPoolGuard<'a> {
type Target = ConnectionPool; type Target = ConnectionPool;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -983,13 +981,13 @@ impl Deref for ConnectionPoolGuard<'_> {
} }
} }
impl DerefMut for ConnectionPoolGuard<'_> { impl<'a> DerefMut for ConnectionPoolGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard &mut self.guard
} }
} }
impl Drop for ConnectionPoolGuard<'_> { impl<'a> Drop for ConnectionPoolGuard<'a> {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(test)] #[cfg(test)]
self.check_invariants(); self.check_invariants();

View File

@@ -13,7 +13,6 @@ mod channel_message_tests;
mod channel_tests; mod channel_tests;
mod editor_tests; mod editor_tests;
mod following_tests; mod following_tests;
mod git_tests;
mod integration_tests; mod integration_tests;
mod notification_tests; mod notification_tests;
mod random_channel_buffer_tests; mod random_channel_buffer_tests;

View File

@@ -1537,7 +1537,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
show_parameter_hints: false, show_parameter_hints: false,
show_other_hints: true, show_other_hints: true,
show_background: false, 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_parameter_hints: false,
show_other_hints: true, show_other_hints: true,
show_background: false, 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_parameter_hints: false,
show_other_hints: false, show_other_hints: false,
show_background: 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_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
}); });

View File

@@ -1,130 +0,0 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use call::ActiveCall;
use git::status::{FileStatus, StatusCode, TrackedStatus};
use git_ui::project_diff::ProjectDiff;
use gpui::{TestAppContext, VisualTestContext};
use project::ProjectPath;
use serde_json::json;
use workspace::Workspace;
//
use crate::tests::TestServer;
#[gpui::test]
async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let mut server = TestServer::start(cx_a.background_executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
cx_a.set_name("cx_a");
cx_b.set_name("cx_b");
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
client_a
.fs()
.insert_tree(
"/a",
json!({
".git": {},
"changed.txt": "after\n",
"unchanged.txt": "unchanged\n",
"created.txt": "created\n",
"secret.pem": "secret-changed\n",
}),
)
.await;
client_a.fs().set_git_content_for_repo(
Path::new("/a/.git"),
&[
("changed.txt".into(), "before\n".to_string(), None),
("unchanged.txt".into(), "unchanged\n".to_string(), None),
("deleted.txt".into(), "deleted\n".to_string(), None),
("secret.pem".into(), "shh\n".to_string(), None),
],
);
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
let active_call_a = cx_a.read(ActiveCall::global);
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
cx_b.update(editor::init);
cx_b.update(git_ui::init);
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let workspace_b = cx_b.add_window(|window, cx| {
Workspace::new(
None,
project_b.clone(),
client_b.app_state.clone(),
window,
cx,
)
});
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
let workspace_b = workspace_b.root(cx_b).unwrap();
cx_b.update(|window, cx| {
window
.focused(cx)
.unwrap()
.dispatch_action(&git_ui::project_diff::Diff, window, cx)
});
let diff = workspace_b.update(cx_b, |workspace, cx| {
workspace.active_item(cx).unwrap().act_as::<ProjectDiff>(cx)
});
let diff = diff.unwrap();
cx_b.run_until_parked();
diff.update(cx_b, |diff, cx| {
assert_eq!(
diff.excerpt_paths(cx),
vec!["changed.txt", "deleted.txt", "created.txt"]
);
});
client_a
.fs()
.insert_tree(
"/a",
json!({
".git": {},
"changed.txt": "before\n",
"unchanged.txt": "changed\n",
"created.txt": "created\n",
"secret.pem": "secret-changed\n",
}),
)
.await;
client_a.fs().recalculate_git_status(Path::new("/a/.git"));
cx_b.run_until_parked();
project_b.update(cx_b, |project, cx| {
let project_path = ProjectPath {
worktree_id,
path: Arc::from(PathBuf::from("unchanged.txt")),
};
let status = project.project_path_git_status(&project_path, cx);
assert_eq!(
status.unwrap(),
FileStatus::Tracked(TrackedStatus {
worktree_status: StatusCode::Modified,
index_status: StatusCode::Unmodified,
})
);
});
diff.update(cx_b, |diff, cx| {
assert_eq!(
diff.excerpt_paths(cx),
vec!["deleted.txt", "unchanged.txt", "created.txt"]
);
});
}

View File

@@ -6354,7 +6354,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 1 as preview // Open item 1 as preview
workspace workspace
.update_in(cx, |workspace, window, cx| { .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 .await
.unwrap(); .unwrap();
@@ -6375,7 +6375,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview // Open item 2 as preview
workspace workspace
.update_in(cx, |workspace, window, cx| { .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 .await
.unwrap(); .unwrap();
@@ -6507,7 +6507,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in right pane // Open item 2 as preview in right pane
workspace workspace
.update_in(cx, |workspace, window, cx| { .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 .await
.unwrap(); .unwrap();
@@ -6545,7 +6545,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in left pane // Open item 2 as preview in left pane
workspace workspace
.update_in(cx, |workspace, window, cx| { .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 .await
.unwrap(); .unwrap();

View File

@@ -2458,8 +2458,8 @@ impl CollabPanel {
Avatar::new(contact.user.avatar_uri.clone()) Avatar::new(contact.user.avatar_uri.clone())
.indicator::<AvatarAvailabilityIndicator>(if online { .indicator::<AvatarAvailabilityIndicator>(if online {
Some(AvatarAvailabilityIndicator::new(match busy { Some(AvatarAvailabilityIndicator::new(match busy {
true => ui::CollaboratorAvailability::Busy, true => ui::Availability::Busy,
false => ui::CollaboratorAvailability::Free, false => ui::Availability::Free,
})) }))
} else { } else {
None None

View File

@@ -27,7 +27,7 @@ impl<'de> Deserialize<'de> for ChatPanelButton {
{ {
struct Visitor; struct Visitor;
impl serde::de::Visitor<'_> for Visitor { impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = ChatPanelButton; type Value = ChatPanelButton;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {

View File

@@ -15,6 +15,7 @@ path = "src/component.rs"
collections.workspace = true collections.workspace = true
gpui.workspace = true gpui.workspace = true
linkme.workspace = true linkme.workspace = true
once_cell.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
theme.workspace = true theme.workspace = true

View File

@@ -1,9 +1,9 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
use collections::HashMap; use collections::HashMap;
use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window}; use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
use linkme::distributed_slice; use linkme::distributed_slice;
use once_cell::sync::Lazy;
use parking_lot::RwLock; use parking_lot::RwLock;
use theme::ActiveTheme; use theme::ActiveTheme;
@@ -27,8 +27,8 @@ pub static __ALL_COMPONENTS: [fn()] = [..];
#[distributed_slice] #[distributed_slice]
pub static __ALL_PREVIEWS: [fn()] = [..]; pub static __ALL_PREVIEWS: [fn()] = [..];
pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> = pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
LazyLock::new(|| RwLock::new(ComponentRegistry::new())); Lazy::new(|| RwLock::new(ComponentRegistry::new()));
pub struct ComponentRegistry { pub struct ComponentRegistry {
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>, components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
@@ -173,9 +173,9 @@ pub enum ExampleLabelSide {
Left, Left,
/// Right side /// Right side
Right, Right,
#[default]
/// Top side /// Top side
Top, Top,
#[default]
/// Bottom side /// Bottom side
Bottom, Bottom,
} }
@@ -200,10 +200,10 @@ impl RenderOnce for ComponentExample {
ExampleLabelSide::Top => base.flex_col_reverse(), ExampleLabelSide::Top => base.flex_col_reverse(),
}; };
base.gap_2() base.gap_1()
.p_2() .p_2()
.text_size(px(10.)) .text_sm()
.text_color(cx.theme().colors().text_muted) .text_color(cx.theme().colors().text)
.when(self.grow, |this| this.flex_1()) .when(self.grow, |this| this.flex_1())
.child(self.element) .child(self.element)
.child(self.variant_name) .child(self.variant_name)
@@ -245,13 +245,12 @@ impl RenderOnce for ComponentExampleGroup {
.text_color(cx.theme().colors().text_muted) .text_color(cx.theme().colors().text_muted)
.when(self.grow, |this| this.w_full().flex_1()) .when(self.grow, |this| this.w_full().flex_1())
.when_some(self.title, |this, title| { .when_some(self.title, |this, title| {
this.gap_4().child( this.gap_4().pb_5().child(
div() div()
.flex() .flex()
.items_center() .items_center()
.gap_3() .gap_3()
.pb_1() .child(div().h_px().w_4().bg(cx.theme().colors().border_variant))
.child(div().h_px().w_4().bg(cx.theme().colors().border))
.child( .child(
div() div()
.flex_none() .flex_none()
@@ -272,7 +271,7 @@ impl RenderOnce for ComponentExampleGroup {
.flex() .flex()
.items_start() .items_start()
.w_full() .w_full()
.gap_6() .gap_8()
.children(self.examples) .children(self.examples)
.into_any_element(), .into_any_element(),
) )

View File

@@ -3,9 +3,8 @@
//! A view for exploring Zed components. //! A view for exploring Zed components.
use component::{components, ComponentMetadata}; use component::{components, ComponentMetadata};
use gpui::{list, prelude::*, uniform_list, App, EventEmitter, FocusHandle, Focusable, Window}; use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
use gpui::{ListState, ScrollHandle, UniformListScrollHandle}; use ui::prelude::*;
use ui::{prelude::*, ListItem};
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId}; use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
@@ -13,7 +12,7 @@ pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _, _cx| { cx.observe_new(|workspace: &mut Workspace, _, _cx| {
workspace.register_action( workspace.register_action(
|workspace, _: &workspace::OpenComponentPreview, window, cx| { |workspace, _: &workspace::OpenComponentPreview, window, cx| {
let component_preview = cx.new(|cx| ComponentPreview::new(window, cx)); let component_preview = cx.new(ComponentPreview::new);
workspace.add_item_to_active_pane( workspace.add_item_to_active_pane(
Box::new(component_preview), Box::new(component_preview),
None, None,
@@ -29,98 +28,73 @@ pub fn init(cx: &mut App) {
struct ComponentPreview { struct ComponentPreview {
focus_handle: FocusHandle, focus_handle: FocusHandle,
_view_scroll_handle: ScrollHandle,
nav_scroll_handle: UniformListScrollHandle,
components: Vec<ComponentMetadata>,
component_list: ListState,
selected_index: usize,
} }
impl ComponentPreview { impl ComponentPreview {
pub fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self { pub fn new(cx: &mut Context<Self>) -> Self {
let components = components().all_sorted();
let initial_length = components.len();
let component_list = ListState::new(initial_length, gpui::ListAlignment::Top, px(500.0), {
let this = cx.entity().downgrade();
move |ix, window: &mut Window, cx: &mut App| {
this.update(cx, |this, cx| {
this.render_preview(ix, window, cx).into_any_element()
})
.unwrap()
}
});
Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
_view_scroll_handle: ScrollHandle::new(),
nav_scroll_handle: UniformListScrollHandle::new(),
components,
component_list,
selected_index: 0,
} }
} }
fn scroll_to_preview(&mut self, ix: usize, cx: &mut Context<Self>) { fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
self.component_list.scroll_to_reveal_item(ix); let components = components().all_sorted();
self.selected_index = ix; let sorted_components = components.clone();
cx.notify();
}
fn get_component(&self, ix: usize) -> ComponentMetadata { v_flex()
self.components[ix].clone() .max_w_48()
.gap_px()
.p_1()
.children(
sorted_components
.into_iter()
.map(|component| self.render_sidebar_entry(&component, _cx)),
)
.child(
Label::new("These will be clickable once the layout is moved to a gpui::List.")
.color(Color::Muted)
.size(LabelSize::XSmall)
.italic(),
)
} }
fn render_sidebar_entry( fn render_sidebar_entry(
&self, &self,
ix: usize, component: &ComponentMetadata,
selected: bool, _cx: &Context<Self>,
cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let component = self.get_component(ix); h_flex()
.w_40()
ListItem::new(ix) .px_1p5()
.child(Label::new(component.name().clone()).color(Color::Default)) .py_0p5()
.selectable(true) .text_sm()
.toggle_state(selected) .child(component.name().clone())
.inset(true)
.on_click(cx.listener(move |this, _, _, cx| {
this.scroll_to_preview(ix, cx);
}))
} }
fn render_preview( fn render_preview(
&self, &self,
ix: usize, component: &ComponentMetadata,
window: &mut Window, window: &mut Window,
cx: &Context<Self>, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let component = self.get_component(ix);
let name = component.name(); let name = component.name();
let scope = component.scope(); let scope = component.scope();
let description = component.description(); let description = component.description();
v_flex() v_flex()
.py_2() .border_b_1()
.child(
v_flex()
.border_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.rounded_md()
.w_full() .w_full()
.gap_4() .gap_3()
.py_4() .py_6()
.px_6()
.flex_none()
.child( .child(
v_flex() v_flex()
.gap_1() .gap_1()
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.text_xl() .text_2xl()
.child(div().child(name)) .child(div().child(name))
.when_some(scope, |this, scope| { .when_some(scope, |this, scope| {
this.child(div().opacity(0.5).child(format!("({})", scope))) this.child(div().opacity(0.5).child(format!("({})", scope)))
@@ -138,52 +112,40 @@ impl ComponentPreview {
) )
.when_some(component.preview(), |this, preview| { .when_some(component.preview(), |this, preview| {
this.child(preview(window, cx)) this.child(preview(window, cx))
}), })
)
.into_any_element() .into_any_element()
} }
fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
v_flex()
.id("component-previews")
.size_full()
.overflow_y_scroll()
.p_4()
.gap_4()
.children(
components()
.all_previews_sorted()
.iter()
.map(|component| self.render_preview(component, window, cx)),
)
}
} }
impl Render for ComponentPreview { impl Render for ComponentPreview {
fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
h_flex() h_flex()
.id("component-preview") .id("component-preview")
.key_context("ComponentPreview") .key_context("ComponentPreview")
.items_start() .items_start()
.overflow_hidden() .overflow_hidden()
.size_full() .size_full()
.max_h_full()
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.px_2() .px_2()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child( .child(self.render_sidebar(window, cx))
uniform_list( .child(self.render_previews(window, cx))
cx.entity().clone(),
"component-nav",
self.components.len(),
move |this, range, _window, cx| {
range
.map(|ix| this.render_sidebar_entry(ix, ix == this.selected_index, cx))
.collect()
},
)
.track_scroll(self.nav_scroll_handle.clone())
.pt_4()
.w(px(240.))
.h_full()
.flex_grow(),
)
.child(
v_flex()
.id("component-list")
.px_8()
.pt_4()
.size_full()
.child(
list(self.component_list.clone())
.flex_grow()
.with_sizing_behavior(gpui::ListSizingBehavior::Auto),
),
)
} }
} }
@@ -213,13 +175,13 @@ impl Item for ComponentPreview {
fn clone_on_split( fn clone_on_split(
&self, &self,
_workspace_id: Option<WorkspaceId>, _workspace_id: Option<WorkspaceId>,
window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<gpui::Entity<Self>> ) -> Option<gpui::Entity<Self>>
where where
Self: Sized, Self: Sized,
{ {
Some(cx.new(|cx| Self::new(window, cx))) Some(cx.new(Self::new))
} }
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) { fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {

View File

@@ -14,7 +14,6 @@ path = "src/context_server.rs"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
assistant_tool.workspace = true assistant_tool.workspace = true
async-trait.workspace = true
collections.workspace = true collections.workspace = true
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
context_server_settings.workspace = true context_server_settings.workspace = true

View File

@@ -1,12 +1,16 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use collections::HashMap; use collections::HashMap;
use futures::{channel::oneshot, select, FutureExt, StreamExt}; use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, FutureExt};
use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Task}; use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::barrier; use postage::barrier;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{value::RawValue, Value}; use serde_json::{value::RawValue, Value};
use smol::channel; use smol::{
channel,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
process::Child,
};
use std::{ use std::{
fmt, fmt,
path::PathBuf, path::PathBuf,
@@ -18,8 +22,6 @@ use std::{
}; };
use util::TryFutureExt; use util::TryFutureExt;
use crate::transport::{StdioTransport, Transport};
const JSON_RPC_VERSION: &str = "2.0"; const JSON_RPC_VERSION: &str = "2.0";
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60); const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
@@ -53,8 +55,7 @@ pub struct Client {
#[allow(dead_code)] #[allow(dead_code)]
output_done_rx: Mutex<Option<barrier::Receiver>>, output_done_rx: Mutex<Option<barrier::Receiver>>,
executor: BackgroundExecutor, executor: BackgroundExecutor,
#[allow(dead_code)] server: Arc<Mutex<Option<Child>>>,
transport: Arc<dyn Transport>,
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -151,13 +152,25 @@ impl Client {
&binary.args &binary.args
); );
let server_name = binary let mut command = util::command::new_smol_command(&binary.executable);
.executable command
.file_name() .args(&binary.args)
.map(|name| name.to_string_lossy().to_string()) .envs(binary.env.unwrap_or_default())
.unwrap_or_else(String::new); .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
let transport = Arc::new(StdioTransport::new(binary, &cx)?); let mut server = command.spawn().with_context(|| {
format!(
"failed to spawn command. (path={:?}, args={:?})",
binary.executable, &binary.args
)
})?;
let stdin = server.stdin.take().unwrap();
let stdout = server.stdout.take().unwrap();
let stderr = server.stderr.take().unwrap();
let (outbound_tx, outbound_rx) = channel::unbounded::<String>(); let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
let (output_done_tx, output_done_rx) = barrier::channel(); let (output_done_tx, output_done_rx) = barrier::channel();
@@ -170,22 +183,18 @@ impl Client {
let stdout_input_task = cx.spawn({ let stdout_input_task = cx.spawn({
let notification_handlers = notification_handlers.clone(); let notification_handlers = notification_handlers.clone();
let response_handlers = response_handlers.clone(); let response_handlers = response_handlers.clone();
let transport = transport.clone();
move |cx| { move |cx| {
Self::handle_input(transport, notification_handlers, response_handlers, cx) Self::handle_input(stdout, notification_handlers, response_handlers, cx).log_err()
.log_err()
} }
}); });
let stderr_input_task = cx.spawn(|_| Self::handle_stderr(transport.clone()).log_err()); let stderr_input_task = cx.spawn(|_| Self::handle_stderr(stderr).log_err());
let input_task = cx.spawn(|_| async move { let input_task = cx.spawn(|_| async move {
let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task); let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
stdout.or(stderr) stdout.or(stderr)
}); });
let output_task = cx.background_spawn({ let output_task = cx.background_spawn({
let transport = transport.clone();
Self::handle_output( Self::handle_output(
transport, stdin,
outbound_rx, outbound_rx,
output_done_tx, output_done_tx,
response_handlers.clone(), response_handlers.clone(),
@@ -193,18 +202,24 @@ impl Client {
.log_err() .log_err()
}); });
Ok(Self { let mut context_server = Self {
server_id, server_id,
notification_handlers, notification_handlers,
response_handlers, response_handlers,
name: server_name.into(), name: "".into(),
next_id: Default::default(), next_id: Default::default(),
outbound_tx, outbound_tx,
executor: cx.background_executor().clone(), executor: cx.background_executor().clone(),
io_tasks: Mutex::new(Some((input_task, output_task))), io_tasks: Mutex::new(Some((input_task, output_task))),
output_done_rx: Mutex::new(Some(output_done_rx)), output_done_rx: Mutex::new(Some(output_done_rx)),
transport, server: Arc::new(Mutex::new(Some(server))),
}) };
if let Some(name) = binary.executable.file_name() {
context_server.name = name.to_string_lossy().into();
}
Ok(context_server)
} }
/// Handles input from the server's stdout. /// Handles input from the server's stdout.
@@ -213,53 +228,79 @@ impl Client {
/// parses them as JSON-RPC responses or notifications, and dispatches them /// parses them as JSON-RPC responses or notifications, and dispatches them
/// to the appropriate handlers. It processes both responses (which are matched /// to the appropriate handlers. It processes both responses (which are matched
/// to pending requests) and notifications (which trigger registered handlers). /// to pending requests) and notifications (which trigger registered handlers).
async fn handle_input( async fn handle_input<Stdout>(
transport: Arc<dyn Transport>, stdout: Stdout,
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>, notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>, response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
cx: AsyncApp, cx: AsyncApp,
) -> anyhow::Result<()> { ) -> anyhow::Result<()>
let mut receiver = transport.receive(); where
Stdout: AsyncRead + Unpin + Send + 'static,
{
let mut stdout = BufReader::new(stdout);
let mut buffer = String::new();
while let Some(message) = receiver.next().await { loop {
if let Ok(response) = serde_json::from_str::<AnyResponse>(&message) { buffer.clear();
if stdout.read_line(&mut buffer).await? == 0 {
return Ok(());
}
let content = buffer.trim();
if !content.is_empty() {
if let Ok(response) = serde_json::from_str::<AnyResponse>(content) {
if let Some(handlers) = response_handlers.lock().as_mut() { if let Some(handlers) = response_handlers.lock().as_mut() {
if let Some(handler) = handlers.remove(&response.id) { if let Some(handler) = handlers.remove(&response.id) {
handler(Ok(message.to_string())); handler(Ok(content.to_string()));
} }
} }
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(&message) { } else if let Ok(notification) = serde_json::from_str::<AnyNotification>(content) {
let mut notification_handlers = notification_handlers.lock(); let mut notification_handlers = notification_handlers.lock();
if let Some(handler) = notification_handlers.get_mut(notification.method.as_str()) { if let Some(handler) =
notification_handlers.get_mut(notification.method.as_str())
{
handler(notification.params.unwrap_or(Value::Null), cx.clone()); handler(notification.params.unwrap_or(Value::Null), cx.clone());
} }
} }
} }
smol::future::yield_now().await; smol::future::yield_now().await;
}
Ok(())
} }
/// Handles the stderr output from the context server. /// Handles the stderr output from the context server.
/// Continuously reads and logs any error messages from the server. /// Continuously reads and logs any error messages from the server.
async fn handle_stderr(transport: Arc<dyn Transport>) -> anyhow::Result<()> { async fn handle_stderr<Stderr>(stderr: Stderr) -> anyhow::Result<()>
while let Some(err) = transport.receive_err().next().await { where
log::warn!("context server stderr: {}", err.trim()); Stderr: AsyncRead + Unpin + Send + 'static,
} {
let mut stderr = BufReader::new(stderr);
let mut buffer = String::new();
Ok(()) loop {
buffer.clear();
if stderr.read_line(&mut buffer).await? == 0 {
return Ok(());
}
log::warn!("context server stderr: {}", buffer.trim());
smol::future::yield_now().await;
}
} }
/// Handles the output to the context server's stdin. /// Handles the output to the context server's stdin.
/// This function continuously receives messages from the outbound channel, /// This function continuously receives messages from the outbound channel,
/// writes them to the server's stdin, and manages the lifecycle of response handlers. /// writes them to the server's stdin, and manages the lifecycle of response handlers.
async fn handle_output( async fn handle_output<Stdin>(
transport: Arc<dyn Transport>, stdin: Stdin,
outbound_rx: channel::Receiver<String>, outbound_rx: channel::Receiver<String>,
output_done_tx: barrier::Sender, output_done_tx: barrier::Sender,
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>, response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()>
where
Stdin: AsyncWrite + Unpin + Send + 'static,
{
let mut stdin = BufWriter::new(stdin);
let _clear_response_handlers = util::defer({ let _clear_response_handlers = util::defer({
let response_handlers = response_handlers.clone(); let response_handlers = response_handlers.clone();
move || { move || {
@@ -268,7 +309,10 @@ impl Client {
}); });
while let Ok(message) = outbound_rx.recv().await { while let Ok(message) = outbound_rx.recv().await {
log::trace!("outgoing message: {}", message); log::trace!("outgoing message: {}", message);
transport.send(message).await?;
stdin.write_all(message.as_bytes()).await?;
stdin.write_all(b"\n").await?;
stdin.flush().await?;
} }
drop(output_done_tx); drop(output_done_tx);
Ok(()) Ok(())
@@ -372,6 +416,14 @@ impl Client {
} }
} }
impl Drop for Client {
fn drop(&mut self) {
if let Some(mut server) = self.server.lock().take() {
let _ = server.kill();
}
}
}
impl fmt::Display for ContextServerId { impl fmt::Display for ContextServerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f) self.0.fmt(f)

View File

@@ -4,7 +4,6 @@ mod extension_context_server;
pub mod manager; pub mod manager;
pub mod protocol; pub mod protocol;
mod registry; mod registry;
mod transport;
pub mod types; pub mod types;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;

View File

@@ -1,16 +0,0 @@
mod stdio_transport;
use std::pin::Pin;
use anyhow::Result;
use async_trait::async_trait;
use futures::Stream;
pub use stdio_transport::*;
#[async_trait]
pub trait Transport: Send + Sync {
async fn send(&self, message: String) -> Result<()>;
fn receive(&self) -> Pin<Box<dyn Stream<Item = String> + Send>>;
fn receive_err(&self) -> Pin<Box<dyn Stream<Item = String> + Send>>;
}

View File

@@ -1,140 +0,0 @@
use std::pin::Pin;
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use futures::io::{BufReader, BufWriter};
use futures::{
AsyncBufReadExt as _, AsyncRead, AsyncWrite, AsyncWriteExt as _, Stream, StreamExt as _,
};
use gpui::AsyncApp;
use smol::channel;
use smol::process::Child;
use util::TryFutureExt as _;
use crate::client::ModelContextServerBinary;
use crate::transport::Transport;
pub struct StdioTransport {
stdout_sender: channel::Sender<String>,
stdin_receiver: channel::Receiver<String>,
stderr_receiver: channel::Receiver<String>,
server: Child,
}
impl StdioTransport {
pub fn new(binary: ModelContextServerBinary, cx: &AsyncApp) -> Result<Self> {
let mut command = util::command::new_smol_command(&binary.executable);
command
.args(&binary.args)
.envs(binary.env.unwrap_or_default())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
let mut server = command.spawn().with_context(|| {
format!(
"failed to spawn command. (path={:?}, args={:?})",
binary.executable, &binary.args
)
})?;
let stdin = server.stdin.take().unwrap();
let stdout = server.stdout.take().unwrap();
let stderr = server.stderr.take().unwrap();
let (stdin_sender, stdin_receiver) = channel::unbounded::<String>();
let (stdout_sender, stdout_receiver) = channel::unbounded::<String>();
let (stderr_sender, stderr_receiver) = channel::unbounded::<String>();
cx.spawn(|_| Self::handle_output(stdin, stdout_receiver).log_err())
.detach();
cx.spawn(|_| async move { Self::handle_input(stdout, stdin_sender).await })
.detach();
cx.spawn(|_| async move { Self::handle_err(stderr, stderr_sender).await })
.detach();
Ok(Self {
stdout_sender,
stdin_receiver,
stderr_receiver,
server,
})
}
async fn handle_input<Stdout>(stdin: Stdout, inbound_rx: channel::Sender<String>)
where
Stdout: AsyncRead + Unpin + Send + 'static,
{
let mut stdin = BufReader::new(stdin);
let mut line = String::new();
while let Ok(n) = stdin.read_line(&mut line).await {
if n == 0 {
break;
}
if inbound_rx.send(line.clone()).await.is_err() {
break;
}
line.clear();
}
}
async fn handle_output<Stdin>(
stdin: Stdin,
outbound_rx: channel::Receiver<String>,
) -> Result<()>
where
Stdin: AsyncWrite + Unpin + Send + 'static,
{
let mut stdin = BufWriter::new(stdin);
let mut pinned_rx = Box::pin(outbound_rx);
while let Some(message) = pinned_rx.next().await {
log::trace!("outgoing message: {}", message);
stdin.write_all(message.as_bytes()).await?;
stdin.write_all(b"\n").await?;
stdin.flush().await?;
}
Ok(())
}
async fn handle_err<Stderr>(stderr: Stderr, stderr_tx: channel::Sender<String>)
where
Stderr: AsyncRead + Unpin + Send + 'static,
{
let mut stderr = BufReader::new(stderr);
let mut line = String::new();
while let Ok(n) = stderr.read_line(&mut line).await {
if n == 0 {
break;
}
if stderr_tx.send(line.clone()).await.is_err() {
break;
}
line.clear();
}
}
}
#[async_trait]
impl Transport for StdioTransport {
async fn send(&self, message: String) -> Result<()> {
Ok(self.stdout_sender.send(message).await?)
}
fn receive(&self) -> Pin<Box<dyn Stream<Item = String> + Send>> {
Box::pin(self.stdin_receiver.clone())
}
fn receive_err(&self) -> Pin<Box<dyn Stream<Item = String> + Send>> {
Box::pin(self.stderr_receiver.clone())
}
}
impl Drop for StdioTransport {
fn drop(&mut self) {
let _ = self.server.kill();
}
}

View File

@@ -38,7 +38,6 @@ gpui.workspace = true
http_client.workspace = true http_client.workspace = true
inline_completion.workspace = true inline_completion.workspace = true
language.workspace = true language.workspace = true
log.workspace = true
lsp.workspace = true lsp.workspace = true
menu.workspace = true menu.workspace = true
node_runtime.workspace = true node_runtime.workspace = true
@@ -63,9 +62,7 @@ async-std = { version = "1.12.0", features = ["unstable"] }
client = { workspace = true, features = ["test-support"] } client = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] }

View File

@@ -16,7 +16,6 @@ use gpui::{
}; };
use http_client::github::get_release_by_tag_name; use http_client::github::get_release_by_tag_name;
use http_client::HttpClient; use http_client::HttpClient;
use language::language_settings::CopilotSettings;
use language::{ use language::{
language_settings::{all_language_settings, language_settings, EditPredictionProvider}, language_settings::{all_language_settings, language_settings, EditPredictionProvider},
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
@@ -368,13 +367,13 @@ impl Copilot {
let server_id = self.server_id; let server_id = self.server_id;
let http = self.http.clone(); let http = self.http.clone();
let node_runtime = self.node_runtime.clone(); let node_runtime = self.node_runtime.clone();
let language_settings = all_language_settings(None, cx); if all_language_settings(None, cx).edit_predictions.provider
if language_settings.edit_predictions.provider == EditPredictionProvider::Copilot { == EditPredictionProvider::Copilot
{
if matches!(self.server, CopilotServer::Disabled) { if matches!(self.server, CopilotServer::Disabled) {
let env = self.build_env(&language_settings.edit_predictions.copilot);
let start_task = cx let start_task = cx
.spawn(move |this, cx| { .spawn(move |this, cx| {
Self::start_language_server(server_id, http, node_runtime, env, this, cx) Self::start_language_server(server_id, http, node_runtime, this, cx)
}) })
.shared(); .shared();
self.server = CopilotServer::Starting { task: start_task }; self.server = CopilotServer::Starting { task: start_task };
@@ -386,30 +385,6 @@ impl Copilot {
} }
} }
fn build_env(&self, copilot_settings: &CopilotSettings) -> Option<HashMap<String, String>> {
let proxy_url = copilot_settings.proxy.clone()?;
let no_verify = copilot_settings.proxy_no_verify;
let http_or_https_proxy = if proxy_url.starts_with("http:") {
"HTTP_PROXY"
} else if proxy_url.starts_with("https:") {
"HTTPS_PROXY"
} else {
log::error!(
"Unsupported protocol scheme for language server proxy (must be http or https)"
);
return None;
};
let mut env = HashMap::default();
env.insert(http_or_https_proxy.to_string(), proxy_url);
if let Some(true) = no_verify {
env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string());
};
Some(env)
}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) { pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
use lsp::FakeLanguageServer; use lsp::FakeLanguageServer;
@@ -447,7 +422,6 @@ impl Copilot {
new_server_id: LanguageServerId, new_server_id: LanguageServerId,
http: Arc<dyn HttpClient>, http: Arc<dyn HttpClient>,
node_runtime: NodeRuntime, node_runtime: NodeRuntime,
env: Option<HashMap<String, String>>,
this: WeakEntity<Self>, this: WeakEntity<Self>,
mut cx: AsyncApp, mut cx: AsyncApp,
) { ) {
@@ -458,7 +432,8 @@ impl Copilot {
let binary = LanguageServerBinary { let binary = LanguageServerBinary {
path: node_path, path: node_path,
arguments, arguments,
env, // TODO: We could set HTTP_PROXY etc here and fix the copilot issue.
env: None,
}; };
let root_path = if cfg!(target_os = "windows") { let root_path = if cfg!(target_os = "windows") {
@@ -475,7 +450,6 @@ impl Copilot {
binary, binary,
root_path, root_path,
None, None,
Default::default(),
cx.clone(), cx.clone(),
)?; )?;
@@ -637,8 +611,6 @@ impl Copilot {
} }
pub fn reinstall(&mut self, cx: &mut Context<Self>) -> Task<()> { pub fn reinstall(&mut self, cx: &mut Context<Self>) -> Task<()> {
let language_settings = all_language_settings(None, cx);
let env = self.build_env(&language_settings.edit_predictions.copilot);
let start_task = cx let start_task = cx
.spawn({ .spawn({
let http = self.http.clone(); let http = self.http.clone();
@@ -646,7 +618,7 @@ impl Copilot {
let server_id = self.server_id; let server_id = self.server_id;
move |this, cx| async move { move |this, cx| async move {
clear_copilot_dir().await; clear_copilot_dir().await;
Self::start_language_server(server_id, http, node_runtime, env, this, cx).await Self::start_language_server(server_id, http, node_runtime, this, cx).await
} }
}) })
.shared(); .shared();
@@ -1307,11 +1279,3 @@ mod tests {
} }
} }
} }
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
}
}

View File

@@ -40,8 +40,6 @@ pub enum Model {
O3Mini, O3Mini,
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")] #[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet, Claude3_5Sonnet,
#[serde(alias = "claude-3-7-sonnet", rename = "claude-3.7-sonnet")]
Claude3_7Sonnet,
#[serde(alias = "gemini-2.0-flash", rename = "gemini-2.0-flash-001")] #[serde(alias = "gemini-2.0-flash", rename = "gemini-2.0-flash-001")]
Gemini20Flash, Gemini20Flash,
} }
@@ -49,11 +47,7 @@ pub enum Model {
impl Model { impl Model {
pub fn uses_streaming(&self) -> bool { pub fn uses_streaming(&self) -> bool {
match self { match self {
Self::Gpt4o Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
| Self::Gpt4
| Self::Gpt3_5Turbo
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet => true,
Self::O3Mini | Self::O1 | Self::Gemini20Flash => false, Self::O3Mini | Self::O1 | Self::Gemini20Flash => false,
} }
} }
@@ -66,7 +60,6 @@ impl Model {
"o1" => Ok(Self::O1), "o1" => Ok(Self::O1),
"o3-mini" => Ok(Self::O3Mini), "o3-mini" => Ok(Self::O3Mini),
"claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet), "claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet),
"claude-3-7-sonnet" => Ok(Self::Claude3_7Sonnet),
"gemini-2.0-flash-001" => Ok(Self::Gemini20Flash), "gemini-2.0-flash-001" => Ok(Self::Gemini20Flash),
_ => Err(anyhow!("Invalid model id: {}", id)), _ => Err(anyhow!("Invalid model id: {}", id)),
} }
@@ -80,7 +73,6 @@ impl Model {
Self::O3Mini => "o3-mini", Self::O3Mini => "o3-mini",
Self::O1 => "o1", Self::O1 => "o1",
Self::Claude3_5Sonnet => "claude-3-5-sonnet", Self::Claude3_5Sonnet => "claude-3-5-sonnet",
Self::Claude3_7Sonnet => "claude-3-7-sonnet",
Self::Gemini20Flash => "gemini-2.0-flash-001", Self::Gemini20Flash => "gemini-2.0-flash-001",
} }
} }
@@ -93,7 +85,6 @@ impl Model {
Self::O3Mini => "o3-mini", Self::O3Mini => "o3-mini",
Self::O1 => "o1", Self::O1 => "o1",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet", Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Gemini20Flash => "Gemini 2.0 Flash", Self::Gemini20Flash => "Gemini 2.0 Flash",
} }
} }
@@ -105,8 +96,7 @@ impl Model {
Self::Gpt3_5Turbo => 12_288, Self::Gpt3_5Turbo => 12_288,
Self::O3Mini => 64_000, Self::O3Mini => 64_000,
Self::O1 => 20_000, Self::O1 => 20_000,
Self::Claude3_5Sonnet => 200_000, Self::Claude3_5Sonnet => 128_000,
Self::Claude3_7Sonnet => 90_000,
Model::Gemini20Flash => 128_000, Model::Gemini20Flash => 128_000,
} }
} }
@@ -411,7 +401,7 @@ async fn stream_completion(
match serde_json::from_str::<ResponseEvent>(line) { match serde_json::from_str::<ResponseEvent>(line) {
Ok(response) => { Ok(response) => {
if response.choices.is_empty() if response.choices.first().is_none()
|| response.choices.first().unwrap().finish_reason.is_some() || response.choices.first().unwrap().finish_reason.is_some()
{ {
None None

View File

@@ -192,7 +192,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
fn discard(&mut self, cx: &mut Context<Self>) { fn discard(&mut self, cx: &mut Context<Self>) {
let settings = AllLanguageSettings::get_global(cx); 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 { if !copilot_enabled {
return; return;

View File

@@ -1,21 +0,0 @@
[package]
name = "credentials_provider"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/credentials_provider.rs"
[dependencies]
anyhow.workspace = true
futures.workspace = true
gpui.workspace = true
paths.workspace = true
release_channel.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -1 +0,0 @@
../../LICENSE-GPL

View File

@@ -1,199 +0,0 @@
use std::collections::HashMap;
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::{Arc, LazyLock};
use anyhow::Result;
use futures::FutureExt as _;
use gpui::{App, AsyncApp};
use release_channel::ReleaseChannel;
/// An environment variable whose presence indicates that the system keychain
/// should be used in development.
///
/// By default, running Zed in development uses the development credentials
/// provider. Setting this environment variable allows you to interact with the
/// system keychain (for instance, if you need to test something).
///
/// Only works in development. Setting this environment variable in other
/// release channels is a no-op.
static ZED_DEVELOPMENT_USE_KEYCHAIN: LazyLock<bool> = LazyLock::new(|| {
std::env::var("ZED_DEVELOPMENT_USE_KEYCHAIN").map_or(false, |value| !value.is_empty())
});
/// A provider for credentials.
///
/// Used to abstract over reading and writing credentials to some form of
/// persistence (like the system keychain).
pub trait CredentialsProvider: Send + Sync {
/// Reads the credentials from the provider.
fn read_credentials<'a>(
&'a self,
url: &'a str,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>>;
/// Writes the credentials to the provider.
fn write_credentials<'a>(
&'a self,
url: &'a str,
username: &'a str,
password: &'a [u8],
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
/// Deletes the credentials from the provider.
fn delete_credentials<'a>(
&'a self,
url: &'a str,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
}
impl dyn CredentialsProvider {
/// Returns the global [`CredentialsProvider`].
pub fn global(cx: &App) -> Arc<Self> {
// The `CredentialsProvider` trait has `Send + Sync` bounds on it, so it
// seems like this is a false positive from Clippy.
#[allow(clippy::arc_with_non_send_sync)]
Self::new(cx)
}
fn new(cx: &App) -> Arc<Self> {
let use_development_provider = match ReleaseChannel::try_global(cx) {
Some(ReleaseChannel::Dev) => {
// In development we default to using the development
// credentials provider to avoid getting spammed by relentless
// keychain access prompts.
//
// However, if the `ZED_DEVELOPMENT_USE_KEYCHAIN` environment
// variable is set, we will use the actual keychain.
!*ZED_DEVELOPMENT_USE_KEYCHAIN
}
Some(ReleaseChannel::Nightly | ReleaseChannel::Preview | ReleaseChannel::Stable)
| None => false,
};
if use_development_provider {
Arc::new(DevelopmentCredentialsProvider::new())
} else {
Arc::new(KeychainCredentialsProvider)
}
}
}
/// A credentials provider that stores credentials in the system keychain.
struct KeychainCredentialsProvider;
impl CredentialsProvider for KeychainCredentialsProvider {
fn read_credentials<'a>(
&'a self,
url: &'a str,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>> {
async move { cx.update(|cx| cx.read_credentials(url))?.await }.boxed_local()
}
fn write_credentials<'a>(
&'a self,
url: &'a str,
username: &'a str,
password: &'a [u8],
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
cx.update(move |cx| cx.write_credentials(url, username, password))?
.await
}
.boxed_local()
}
fn delete_credentials<'a>(
&'a self,
url: &'a str,
cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move { cx.update(move |cx| cx.delete_credentials(url))?.await }.boxed_local()
}
}
/// A credentials provider that stores credentials in a local file.
///
/// This MUST only be used in development, as this is not a secure way of storing
/// credentials on user machines.
///
/// Its existence is purely to work around the annoyance of having to constantly
/// re-allow access to the system keychain when developing Zed.
struct DevelopmentCredentialsProvider {
path: PathBuf,
}
impl DevelopmentCredentialsProvider {
fn new() -> Self {
let path = paths::config_dir().join("development_credentials");
Self { path }
}
fn load_credentials(&self) -> Result<HashMap<String, (String, Vec<u8>)>> {
let json = std::fs::read(&self.path)?;
let credentials: HashMap<String, (String, Vec<u8>)> = serde_json::from_slice(&json)?;
Ok(credentials)
}
fn save_credentials(&self, credentials: &HashMap<String, (String, Vec<u8>)>) -> Result<()> {
let json = serde_json::to_string(credentials)?;
std::fs::write(&self.path, json)?;
Ok(())
}
}
impl CredentialsProvider for DevelopmentCredentialsProvider {
fn read_credentials<'a>(
&'a self,
url: &'a str,
_cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>> {
async move {
Ok(self
.load_credentials()
.unwrap_or_default()
.get(url)
.cloned())
}
.boxed_local()
}
fn write_credentials<'a>(
&'a self,
url: &'a str,
username: &'a str,
password: &'a [u8],
_cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
let mut credentials = self.load_credentials().unwrap_or_default();
credentials.insert(url.to_string(), (username.to_string(), password.to_vec()));
self.save_credentials(&credentials)
}
.boxed_local()
}
fn delete_credentials<'a>(
&'a self,
url: &'a str,
_cx: &'a AsyncApp,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
let mut credentials = self.load_credentials()?;
credentials.remove(url);
self.save_credentials(&credentials)
}
.boxed_local()
}
}

View File

@@ -24,6 +24,7 @@ log.workspace = true
lsp.workspace = true lsp.workspace = true
project.workspace = true project.workspace = true
rand.workspace = true rand.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
settings.workspace = true settings.workspace = true
theme.workspace = true theme.workspace = true

View File

@@ -1,4 +1,5 @@
pub mod items; pub mod items;
mod project_diagnostics_settings;
mod toolbar_controls; mod toolbar_controls;
#[cfg(test)] #[cfg(test)]
@@ -14,16 +15,17 @@ use editor::{
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
}; };
use gpui::{ use gpui::{
actions, div, svg, AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, actions, div, svg, AnyElement, AnyView, App, Context, Entity, EventEmitter, FocusHandle,
FocusHandle, Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, ParentElement, Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, ParentElement, Render,
Render, SharedString, Styled, StyledText, Subscription, Task, WeakEntity, Window, SharedString, Styled, StyledText, Subscription, Task, WeakEntity, Window,
}; };
use language::{ use language::{
Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity,
Point, Selection, SelectionGoal, ToTreeSitterPoint, Point, Selection, SelectionGoal, ToTreeSitterPoint,
}; };
use lsp::LanguageServerId; use lsp::LanguageServerId;
use project::{project_settings::ProjectSettings, DiagnosticSummary, Project, ProjectPath}; use project::{DiagnosticSummary, Project, ProjectPath};
use project_diagnostics_settings::ProjectDiagnosticsSettings;
use settings::Settings; use settings::Settings;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
@@ -50,6 +52,7 @@ struct IncludeWarnings(bool);
impl Global for IncludeWarnings {} impl Global for IncludeWarnings {}
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
ProjectDiagnosticsSettings::register(cx);
cx.observe_new(ProjectDiagnosticsEditor::register).detach(); cx.observe_new(ProjectDiagnosticsEditor::register).detach();
} }
@@ -88,46 +91,15 @@ const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
impl Render for ProjectDiagnosticsEditor { impl Render for ProjectDiagnosticsEditor {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let warning_count = if self.include_warnings { let child = if self.path_states.is_empty() {
self.summary.warning_count div()
} else {
0
};
let child = if warning_count + self.summary.error_count == 0 {
let label = if self.summary.warning_count == 0 {
SharedString::new_static("No problems in workspace")
} else {
SharedString::new_static("No errors in workspace")
};
v_flex()
.key_context("EmptyPane") .key_context("EmptyPane")
.size_full()
.gap_1()
.justify_center()
.items_center()
.text_center()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child(Label::new(label).color(Color::Muted)) .flex()
.when(self.summary.warning_count > 0, |this| { .items_center()
let plural_suffix = if self.summary.warning_count > 1 { .justify_center()
"s" .size_full()
} else { .child(Label::new("No problems in workspace"))
""
};
let label = format!(
"Show {} warning{}",
self.summary.warning_count, plural_suffix
);
this.child(
Button::new("diagnostics-show-warning-label", label).on_click(cx.listener(
|this, _, window, cx| {
this.toggle_warnings(&Default::default(), window, cx);
cx.notify();
},
)),
)
})
} else { } else {
div().size_full().child(self.editor.clone()) div().size_full().child(self.editor.clone())
}; };
@@ -206,7 +178,6 @@ impl ProjectDiagnosticsEditor {
cx, cx,
); );
editor.set_vertical_scroll_margin(5, cx); editor.set_vertical_scroll_margin(5, cx);
editor.disable_inline_diagnostics();
editor editor
}); });
cx.subscribe_in( cx.subscribe_in(
@@ -278,9 +249,8 @@ impl ProjectDiagnosticsEditor {
.log_err() .log_err()
{ {
this.update_in(&mut cx, |this, window, cx| { this.update_in(&mut cx, |this, window, cx| {
this.update_excerpts(path, language_server_id, buffer, window, cx) this.update_excerpts(path, language_server_id, buffer, window, cx);
})? })?;
.await?;
} }
} }
Ok(()) Ok(())
@@ -317,7 +287,7 @@ impl ProjectDiagnosticsEditor {
let include_warnings = match cx.try_global::<IncludeWarnings>() { let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0, Some(include_warnings) => include_warnings.0,
None => ProjectSettings::get_global(cx).diagnostics.include_warnings, None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
}; };
let diagnostics = cx.new(|cx| { let diagnostics = cx.new(|cx| {
@@ -380,7 +350,7 @@ impl ProjectDiagnosticsEditor {
buffer: Entity<Buffer>, buffer: Entity<Buffer>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) {
let was_empty = self.path_states.is_empty(); let was_empty = self.path_states.is_empty();
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let path_ix = match self let path_ix = match self
@@ -399,6 +369,7 @@ impl ProjectDiagnosticsEditor {
ix ix
} }
}; };
let mut prev_excerpt_id = if path_ix > 0 { let mut prev_excerpt_id = if path_ix > 0 {
let prev_path_last_group = &self.path_states[path_ix - 1] let prev_path_last_group = &self.path_states[path_ix - 1]
.diagnostic_groups .diagnostic_groups
@@ -409,6 +380,7 @@ impl ProjectDiagnosticsEditor {
ExcerptId::min() ExcerptId::min()
}; };
let path_state = &mut self.path_states[path_ix];
let mut new_group_ixs = Vec::new(); let mut new_group_ixs = Vec::new();
let mut blocks_to_add = Vec::new(); let mut blocks_to_add = Vec::new();
let mut blocks_to_remove = HashSet::default(); let mut blocks_to_remove = HashSet::default();
@@ -418,14 +390,8 @@ impl ProjectDiagnosticsEditor {
} else { } else {
DiagnosticSeverity::ERROR DiagnosticSeverity::ERROR
}; };
let excerpts = self.excerpts.clone().downgrade(); let excerpts_snapshot = self.excerpts.update(cx, |excerpts, cx| {
let context = self.context; let mut old_groups = mem::take(&mut path_state.diagnostic_groups)
let editor = self.editor.clone().downgrade();
cx.spawn_in(window, move |this, mut cx| async move {
let mut old_groups = this
.update(&mut cx, |this, _| {
mem::take(&mut this.path_states[path_ix].diagnostic_groups)
})?
.into_iter() .into_iter()
.enumerate() .enumerate()
.peekable(); .peekable();
@@ -487,19 +453,9 @@ impl ProjectDiagnosticsEditor {
let mut is_first_excerpt_for_group = true; let mut is_first_excerpt_for_group = true;
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() { for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot)); let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
let expanded_range = if let Some(entry) = &resolved_entry { let expanded_range = resolved_entry.as_ref().map(|entry| {
Some( context_range_for_entry(entry, self.context, &snapshot, cx)
context_range_for_entry( });
entry.range.clone(),
context,
snapshot.clone(),
(*cx).clone(),
)
.await,
)
} else {
None
};
if let Some((range, context_range, start_ix)) = &mut pending_range { if let Some((range, context_range, start_ix)) = &mut pending_range {
if let Some(expanded_range) = expanded_range.clone() { if let Some(expanded_range) = expanded_range.clone() {
// If the entries are overlapping or next to each-other, merge them into one excerpt. // If the entries are overlapping or next to each-other, merge them into one excerpt.
@@ -509,8 +465,7 @@ impl ProjectDiagnosticsEditor {
} }
} }
let excerpt_id = excerpts.update(&mut cx, |excerpts, cx| { let excerpt_id = excerpts
excerpts
.insert_excerpts_after( .insert_excerpts_after(
prev_excerpt_id, prev_excerpt_id,
buffer.clone(), buffer.clone(),
@@ -521,8 +476,7 @@ impl ProjectDiagnosticsEditor {
cx, cx,
) )
.pop() .pop()
.unwrap() .unwrap();
})?;
prev_excerpt_id = excerpt_id; prev_excerpt_id = excerpt_id;
first_excerpt_id.get_or_insert(prev_excerpt_id); first_excerpt_id.get_or_insert(prev_excerpt_id);
@@ -562,7 +516,9 @@ impl ProjectDiagnosticsEditor {
)), )),
height: diagnostic.message.matches('\n').count() as u32 + 1, height: diagnostic.message.matches('\n').count() as u32 + 1,
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
render: diagnostic_block_renderer(diagnostic, None, true), render: diagnostic_block_renderer(
diagnostic, None, true, true,
),
priority: 0, priority: 0,
}); });
} }
@@ -577,46 +533,32 @@ impl ProjectDiagnosticsEditor {
} }
} }
this.update(&mut cx, |this, _| { new_group_ixs.push(path_state.diagnostic_groups.len());
new_group_ixs.push(this.path_states[path_ix].diagnostic_groups.len()); path_state.diagnostic_groups.push(group_state);
this.path_states[path_ix]
.diagnostic_groups
.push(group_state);
})?;
} else if let Some((_, group_state)) = to_remove { } else if let Some((_, group_state)) = to_remove {
excerpts.update(&mut cx, |excerpts, cx| { excerpts.remove_excerpts(group_state.excerpts.iter().copied(), cx);
excerpts.remove_excerpts(group_state.excerpts.iter().copied(), cx)
})?;
blocks_to_remove.extend(group_state.blocks.iter().copied()); blocks_to_remove.extend(group_state.blocks.iter().copied());
} else if let Some((_, group_state)) = to_keep { } else if let Some((_, group_state)) = to_keep {
prev_excerpt_id = *group_state.excerpts.last().unwrap(); prev_excerpt_id = *group_state.excerpts.last().unwrap();
first_excerpt_id.get_or_insert(prev_excerpt_id); first_excerpt_id.get_or_insert(prev_excerpt_id);
path_state.diagnostic_groups.push(group_state);
this.update(&mut cx, |this, _| {
this.path_states[path_ix]
.diagnostic_groups
.push(group_state)
})?;
} }
} }
let excerpts_snapshot = excerpts.snapshot(cx)
excerpts.update(&mut cx, |excerpts, cx| excerpts.snapshot(cx))?; });
editor.update(&mut cx, |editor, cx| {
self.editor.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, None, cx); editor.remove_blocks(blocks_to_remove, None, cx);
let block_ids = editor.insert_blocks( let block_ids = editor.insert_blocks(
blocks_to_add.into_iter().flat_map(|block| { blocks_to_add.into_iter().flat_map(|block| {
let placement = match block.placement { let placement = match block.placement {
BlockPlacement::Above((excerpt_id, text_anchor)) => { BlockPlacement::Above((excerpt_id, text_anchor)) => BlockPlacement::Above(
BlockPlacement::Above(
excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?, excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
) ),
} BlockPlacement::Below((excerpt_id, text_anchor)) => BlockPlacement::Below(
BlockPlacement::Below((excerpt_id, text_anchor)) => {
BlockPlacement::Below(
excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?, excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
) ),
}
BlockPlacement::Replace(_) => { BlockPlacement::Replace(_) => {
unreachable!( unreachable!(
"no Replace block should have been pushed to blocks_to_add" "no Replace block should have been pushed to blocks_to_add"
@@ -636,29 +578,23 @@ impl ProjectDiagnosticsEditor {
); );
let mut block_ids = block_ids.into_iter(); let mut block_ids = block_ids.into_iter();
this.update(cx, |this, _| {
for ix in new_group_ixs { for ix in new_group_ixs {
let group_state = &mut this.path_states[path_ix].diagnostic_groups[ix]; let group_state = &mut path_state.diagnostic_groups[ix];
group_state.blocks = group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect();
block_ids.by_ref().take(group_state.block_count).collect();
} }
})?; });
Result::<(), anyhow::Error>::Ok(())
})??;
this.update_in(&mut cx, |this, window, cx| { if path_state.diagnostic_groups.is_empty() {
if this.path_states[path_ix].diagnostic_groups.is_empty() { self.path_states.remove(path_ix);
this.path_states.remove(path_ix);
} }
this.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
let groups; let groups;
let mut selections; let mut selections;
let new_excerpt_ids_by_selection_id; let new_excerpt_ids_by_selection_id;
if was_empty { if was_empty {
groups = this.path_states.first()?.diagnostic_groups.as_slice(); groups = self.path_states.first()?.diagnostic_groups.as_slice();
new_excerpt_ids_by_selection_id = new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect();
[(0, ExcerptId::min())].into_iter().collect();
selections = vec![Selection { selections = vec![Selection {
id: 0, id: 0,
start: 0, start: 0,
@@ -667,20 +603,16 @@ impl ProjectDiagnosticsEditor {
goal: SelectionGoal::None, goal: SelectionGoal::None,
}]; }];
} else { } else {
groups = this.path_states.get(path_ix)?.diagnostic_groups.as_slice(); groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
new_excerpt_ids_by_selection_id = new_excerpt_ids_by_selection_id =
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.refresh());
s.refresh()
});
selections = editor.selections.all::<usize>(cx); selections = editor.selections.all::<usize>(cx);
} }
// If any selection has lost its position, move it to start of the next primary diagnostic. // If any selection has lost its position, move it to start of the next primary diagnostic.
let snapshot = editor.snapshot(window, cx); let snapshot = editor.snapshot(window, cx);
for selection in &mut selections { for selection in &mut selections {
if let Some(new_excerpt_id) = if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
new_excerpt_ids_by_selection_id.get(&selection.id)
{
let group_ix = match groups.binary_search_by(|probe| { let group_ix = match groups.binary_search_by(|probe| {
probe probe
.excerpts .excerpts
@@ -709,24 +641,20 @@ impl ProjectDiagnosticsEditor {
}); });
Some(()) Some(())
}); });
})?;
this.update_in(&mut cx, |this, window, cx| { if self.path_states.is_empty() {
if this.path_states.is_empty() { if self.editor.focus_handle(cx).is_focused(window) {
if this.editor.focus_handle(cx).is_focused(window) { window.focus(&self.focus_handle);
window.focus(&this.focus_handle);
} }
} else if this.focus_handle.is_focused(window) { } else if self.focus_handle.is_focused(window) {
let focus_handle = this.editor.focus_handle(cx); let focus_handle = self.editor.focus_handle(cx);
window.focus(&focus_handle); window.focus(&focus_handle);
} }
#[cfg(test)] #[cfg(test)]
this.check_invariants(cx); self.check_invariants(cx);
cx.notify(); cx.notify();
})
})
} }
#[cfg(test)] #[cfg(test)]
@@ -1050,30 +978,29 @@ fn compare_diagnostics(
const DIAGNOSTIC_EXPANSION_ROW_LIMIT: u32 = 32; const DIAGNOSTIC_EXPANSION_ROW_LIMIT: u32 = 32;
fn context_range_for_entry( fn context_range_for_entry(
range: Range<Point>, entry: &DiagnosticEntry<Point>,
context: u32, context: u32,
snapshot: BufferSnapshot, snapshot: &BufferSnapshot,
cx: AsyncApp, cx: &App,
) -> Task<Range<Point>> { ) -> Range<Point> {
cx.spawn(move |cx| async move {
if let Some(rows) = heuristic_syntactic_expand( if let Some(rows) = heuristic_syntactic_expand(
range.clone(), entry.range.clone(),
DIAGNOSTIC_EXPANSION_ROW_LIMIT, DIAGNOSTIC_EXPANSION_ROW_LIMIT,
snapshot.clone(), snapshot,
cx, cx,
) ) {
.await
{
return Range { return Range {
start: Point::new(*rows.start(), 0), start: Point::new(*rows.start(), 0),
end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left), end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left),
}; };
} }
Range { Range {
start: Point::new(range.start.row.saturating_sub(context), 0), start: Point::new(entry.range.start.row.saturating_sub(context), 0),
end: snapshot.clip_point(Point::new(range.end.row + context, u32::MAX), Bias::Left), end: snapshot.clip_point(
Point::new(entry.range.end.row + context, u32::MAX),
Bias::Left,
),
} }
})
} }
/// Expands the input range using syntax information from TreeSitter. This expansion will be limited /// Expands the input range using syntax information from TreeSitter. This expansion will be limited
@@ -1081,11 +1008,11 @@ fn context_range_for_entry(
/// ///
/// If there is a containing outline item that is less than `max_row_count`, it will be returned. /// If there is a containing outline item that is less than `max_row_count`, it will be returned.
/// Otherwise fairly arbitrary heuristics are applied to attempt to return a logical block of code. /// Otherwise fairly arbitrary heuristics are applied to attempt to return a logical block of code.
async fn heuristic_syntactic_expand( fn heuristic_syntactic_expand<'a>(
input_range: Range<Point>, input_range: Range<Point>,
max_row_count: u32, max_row_count: u32,
snapshot: BufferSnapshot, snapshot: &'a BufferSnapshot,
cx: AsyncApp, cx: &'a App,
) -> Option<RangeInclusive<BufferRow>> { ) -> Option<RangeInclusive<BufferRow>> {
let input_row_count = input_range.end.row - input_range.start.row; let input_row_count = input_range.end.row - input_range.start.row;
if input_row_count > max_row_count { if input_row_count > max_row_count {
@@ -1115,18 +1042,12 @@ async fn heuristic_syntactic_expand(
} }
let mut node = snapshot.syntax_ancestor(input_range.clone())?; let mut node = snapshot.syntax_ancestor(input_range.clone())?;
loop { loop {
let node_start = Point::from_ts_point(node.start_position()); let node_start = Point::from_ts_point(node.start_position());
let node_end = Point::from_ts_point(node.end_position()); let node_end = Point::from_ts_point(node.end_position());
let node_range = node_start..node_end; let node_range = node_start..node_end;
let row_count = node_end.row - node_start.row + 1; let row_count = node_end.row - node_start.row + 1;
let mut ancestor_range = None;
let reached_outline_node = cx.background_executor().scoped({
let node_range = node_range.clone();
let outline_range = outline_range.clone();
let ancestor_range = &mut ancestor_range;
|scope| {scope.spawn(async move {
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval // Stop if we've exceeded the row count or reached an outline node. Then, find the interval
// of node children which contains the query range. For example, this allows just returning // of node children which contains the query range. For example, this allows just returning
// the header of a declaration rather than the entire declaration. // the header of a declaration rather than the entire declaration.
@@ -1155,8 +1076,7 @@ async fn heuristic_syntactic_expand(
if let Some(start) = included_child_start { if let Some(start) = included_child_start {
let row_count = end.row - start.row; let row_count = end.row - start.row;
if row_count < max_row_count { if row_count < max_row_count {
*ancestor_range = Some(Some(RangeInclusive::new(start.row, end.row))); return Some(RangeInclusive::new(start.row, end.row));
return;
} }
} }
@@ -1164,13 +1084,7 @@ async fn heuristic_syntactic_expand(
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.", "Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
node.grammar_name() node.grammar_name()
); );
*ancestor_range = Some(None); return None;
}
})
}});
reached_outline_node.await;
if let Some(node) = ancestor_range {
return node;
} }
let node_name = node.grammar_name(); let node_name = node.grammar_name();
@@ -1179,9 +1093,7 @@ async fn heuristic_syntactic_expand(
return Some(node_row_range); return Some(node_row_range);
} else if node_name.ends_with("statement") || node_name.ends_with("declaration") { } else if node_name.ends_with("statement") || node_name.ends_with("declaration") {
// Expand to the nearest dedent or blank line for statements and declarations. // Expand to the nearest dedent or blank line for statements and declarations.
let tab_size = cx let tab_size = snapshot.settings_at(node_range.start, cx).tab_size.get();
.update(|cx| snapshot.settings_at(node_range.start, cx).tab_size.get())
.ok()?;
let indent_level = snapshot let indent_level = snapshot
.line_indent_for_row(node_range.start.row) .line_indent_for_row(node_range.start.row)
.len(tab_size); .len(tab_size);
@@ -1189,9 +1101,7 @@ async fn heuristic_syntactic_expand(
let Some(start_row) = (node_range.start.row.saturating_sub(rows_remaining) let Some(start_row) = (node_range.start.row.saturating_sub(rows_remaining)
..node_range.start.row) ..node_range.start.row)
.rev() .rev()
.find(|row| { .find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
is_line_blank_or_indented_less(indent_level, *row, tab_size, &snapshot.clone())
})
else { else {
return Some(node_row_range); return Some(node_row_range);
}; };
@@ -1201,9 +1111,7 @@ async fn heuristic_syntactic_expand(
node_range.end.row + rows_remaining + 1, node_range.end.row + rows_remaining + 1,
snapshot.row_count(), snapshot.row_count(),
)) ))
.find(|row| { .find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
is_line_blank_or_indented_less(indent_level, *row, tab_size, &snapshot.clone())
})
else { else {
return Some(node_row_range); return Some(node_row_range);
}; };

View File

@@ -0,0 +1,28 @@
use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Deserialize, Debug)]
pub struct ProjectDiagnosticsSettings {
pub include_warnings: bool,
}
/// Diagnostics configuration.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct ProjectDiagnosticsSettingsContent {
/// Whether to show warnings or not by default.
///
/// Default: true
include_warnings: Option<bool>,
}
impl Settings for ProjectDiagnosticsSettings {
const KEY: Option<&'static str> = Some("diagnostics");
type FileContent = ProjectDiagnosticsSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
}
}

View File

@@ -22,8 +22,6 @@ pub struct SelectPrevious {
pub struct MoveToBeginningOfLine { pub struct MoveToBeginningOfLine {
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub stop_at_soft_wraps: bool, pub stop_at_soft_wraps: bool,
#[serde(default)]
pub stop_at_indent: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
@@ -31,8 +29,6 @@ pub struct MoveToBeginningOfLine {
pub struct SelectToBeginningOfLine { pub struct SelectToBeginningOfLine {
#[serde(default)] #[serde(default)]
pub(super) stop_at_soft_wraps: bool, pub(super) stop_at_soft_wraps: bool,
#[serde(default)]
pub stop_at_indent: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
@@ -284,6 +280,7 @@ gpui::actions!(
DuplicateLineDown, DuplicateLineDown,
DuplicateLineUp, DuplicateLineUp,
DuplicateSelection, DuplicateSelection,
ExpandAllHunkDiffs,
ExpandMacroRecursively, ExpandMacroRecursively,
FindAllReferences, FindAllReferences,
Fold, Fold,
@@ -332,8 +329,6 @@ gpui::actions!(
MoveToPreviousSubwordStart, MoveToPreviousSubwordStart,
MoveToPreviousWordStart, MoveToPreviousWordStart,
MoveToStartOfParagraph, MoveToStartOfParagraph,
MoveToStartOfExcerpt,
MoveToEndOfExcerpt,
MoveUp, MoveUp,
Newline, Newline,
NewlineAbove, NewlineAbove,
@@ -369,8 +364,6 @@ gpui::actions!(
ScrollCursorTop, ScrollCursorTop,
SelectAll, SelectAll,
SelectAllMatches, SelectAllMatches,
SelectToStartOfExcerpt,
SelectToEndOfExcerpt,
SelectDown, SelectDown,
SelectEnclosingSymbol, SelectEnclosingSymbol,
SelectLargerSyntaxNode, SelectLargerSyntaxNode,
@@ -404,7 +397,6 @@ gpui::actions!(
ToggleGitBlameInline, ToggleGitBlameInline,
ToggleIndentGuides, ToggleIndentGuides,
ToggleInlayHints, ToggleInlayHints,
ToggleInlineDiagnostics,
ToggleEditPrediction, ToggleEditPrediction,
ToggleLineNumbers, ToggleLineNumbers,
SwapSelectionEnds, SwapSelectionEnds,
@@ -428,4 +420,3 @@ action_as!(go_to_line, ToggleGoToLine as Toggle);
action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]); action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]);
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]); action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]);
action_with_deprecated_aliases!(editor, ExpandAllDiffHunks, ["editor::ExpandAllHunkDiffs"]);

View File

@@ -210,7 +210,7 @@ impl Render for CommitTooltip {
.as_ref() .as_ref()
.and_then(|details| details.pull_request.clone()); .and_then(|details| details.pull_request.clone());
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx); let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let message_max_height = window.line_height() * 12 + (ui_font_size / 0.4); let message_max_height = window.line_height() * 12 + (ui_font_size / 0.4);
tooltip_container(window, cx, move |this, _, cx| { tooltip_container(window, cx, move |this, _, cx| {

View File

@@ -330,11 +330,7 @@ impl DisplayMap {
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive); block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
} }
pub fn fold_buffers( pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) {
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner(); let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx); let tab_size = Self::tab_size(&self.buffer, cx);
@@ -345,14 +341,10 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); 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( pub fn unfold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) {
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner(); let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx); let tab_size = Self::tab_size(&self.buffer, cx);
@@ -363,7 +355,7 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); 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 { pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
@@ -1919,67 +1911,6 @@ pub mod tests {
); );
} }
#[gpui::test]
fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
cx.update(|cx| init_test(cx, |_| {}));
let buffer = cx.new(|cx| Buffer::local("a", cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let font_size = px(14.0);
let map = cx.new(|cx| {
DisplayMap::new(
buffer.clone(),
font("Helvetica"),
font_size,
None,
true,
1,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
map.update(cx, |map, cx| {
map.insert_blocks(
[BlockProperties {
placement: BlockPlacement::Above(
buffer_snapshot.anchor_before(Point::new(0, 0)),
),
height: 2,
style: BlockStyle::Sticky,
render: Arc::new(|_| div().into_any()),
priority: 0,
}],
cx,
);
});
map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
map.update(cx, |map, cx| {
map.splice_inlays(
&[],
vec![Inlay {
id: InlayId::InlineCompletion(0),
position: buffer_snapshot.anchor_after(0),
text: "\n".into(),
}],
cx,
);
});
map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
// Regression test: updating the display map does not crash when a
// block is immediately followed by a multi-line inlay.
buffer.update(cx, |buffer, cx| {
buffer.edit([(1..1, "b")], None, cx);
});
map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
}
#[gpui::test] #[gpui::test]
async fn test_chunks(cx: &mut gpui::TestAppContext) { async fn test_chunks(cx: &mut gpui::TestAppContext) {
let text = r#" let text = r#"

Some files were not shown because too many files have changed in this diff Show More