Compare commits
226 Commits
fix-hard-c
...
fix-git-ht
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c2dffc792 | ||
|
|
e4e758db3a | ||
|
|
fc52b43159 | ||
|
|
b445e4ce24 | ||
|
|
508b581215 | ||
|
|
c9aba6c10a | ||
|
|
5740fec9d5 | ||
|
|
22220ed32e | ||
|
|
7440833ff1 | ||
|
|
bb3aef15eb | ||
|
|
ece1818301 | ||
|
|
604eb91a6c | ||
|
|
472dde509f | ||
|
|
212c8f4c31 | ||
|
|
6092918be8 | ||
|
|
a5f96909cb | ||
|
|
b15aa5e018 | ||
|
|
62fb555e18 | ||
|
|
c0ecf8684e | ||
|
|
df7beb4217 | ||
|
|
6f30d5da71 | ||
|
|
b8387c6077 | ||
|
|
c05ce882e9 | ||
|
|
eaf3949614 | ||
|
|
c47305dd7b | ||
|
|
482a45feac | ||
|
|
7ec3702b47 | ||
|
|
91862ddc9f | ||
|
|
eb4fad52df | ||
|
|
541a5c01a4 | ||
|
|
82f793144e | ||
|
|
6eb2ffe77a | ||
|
|
c5632f8c31 | ||
|
|
6856e869fc | ||
|
|
cc3b5c729e | ||
|
|
4e60ebab5e | ||
|
|
e8ef36edcc | ||
|
|
2e98bc17cb | ||
|
|
5c400dac8d | ||
|
|
635b80ed51 | ||
|
|
73ab5abee1 | ||
|
|
1f52aab7c7 | ||
|
|
1732cdb90a | ||
|
|
0625006a9e | ||
|
|
0b96690446 | ||
|
|
bc22690620 | ||
|
|
4eb82c0731 | ||
|
|
fa91379119 | ||
|
|
da2320fb40 | ||
|
|
7bc31a69c3 | ||
|
|
1e4752972f | ||
|
|
91148a72a3 | ||
|
|
878b50c991 | ||
|
|
e7df5ce61c | ||
|
|
da22f21dec | ||
|
|
3505a17452 | ||
|
|
81badd1fe6 | ||
|
|
6dacc751fc | ||
|
|
a0d1555470 | ||
|
|
8ba7b349a5 | ||
|
|
11838cf89e | ||
|
|
84ded96cb2 | ||
|
|
afc61b9527 | ||
|
|
672a472a23 | ||
|
|
9822d9673c | ||
|
|
f0dec2f576 | ||
|
|
9f7c65df44 | ||
|
|
372e485ba8 | ||
|
|
b8fb416892 | ||
|
|
6a1c104522 | ||
|
|
b06da7f7fd | ||
|
|
f80035e0ff | ||
|
|
7664c1cef5 | ||
|
|
967792119b | ||
|
|
be1ac78e11 | ||
|
|
b5a1ae6526 | ||
|
|
d2b49de0e4 | ||
|
|
d694458659 | ||
|
|
7a34dd9888 | ||
|
|
add7ae8052 | ||
|
|
c53020ceaf | ||
|
|
eeac1a9287 | ||
|
|
e83ebd1fab | ||
|
|
afb0fd609b | ||
|
|
b2a685f00a | ||
|
|
089ea5da50 | ||
|
|
b6e8db244c | ||
|
|
6267ab0396 | ||
|
|
d105f04be5 | ||
|
|
5edded5c02 | ||
|
|
78da39e19b | ||
|
|
d82a132477 | ||
|
|
f11357db7c | ||
|
|
6d17546b1a | ||
|
|
60a96ab799 | ||
|
|
1f80f58104 | ||
|
|
bab65011b4 | ||
|
|
c0b6d86c41 | ||
|
|
39728cfc59 | ||
|
|
ebccef1aa4 | ||
|
|
33754f8eac | ||
|
|
dd1ff9b998 | ||
|
|
7f214ed25a | ||
|
|
08539b32d0 | ||
|
|
88baf171c3 | ||
|
|
2f34af7811 | ||
|
|
2978be95d7 | ||
|
|
30568e6dd1 | ||
|
|
a5698a430d | ||
|
|
57659b5552 | ||
|
|
3db18ff053 | ||
|
|
198f56c763 | ||
|
|
d68d858a10 | ||
|
|
7f166298db | ||
|
|
0066071a89 | ||
|
|
e5b6194914 | ||
|
|
23f61d5954 | ||
|
|
0559e1f348 | ||
|
|
014d9dfce1 | ||
|
|
a0aea6ef62 | ||
|
|
278620df33 | ||
|
|
75dbe189bd | ||
|
|
3d7ba7c1c0 | ||
|
|
eebee4ab18 | ||
|
|
3a3621f2d8 | ||
|
|
524e813d20 | ||
|
|
b12b8340de | ||
|
|
7075bd700f | ||
|
|
21fc3c07b6 | ||
|
|
c90f87898a | ||
|
|
796e87ecbc | ||
|
|
3a041cac72 | ||
|
|
86283f4e3d | ||
|
|
8e1003ef59 | ||
|
|
8e891c16f8 | ||
|
|
cea06bc0ce | ||
|
|
45146b6f30 | ||
|
|
bcbb19e06e | ||
|
|
3f168e85c2 | ||
|
|
20440f83e9 | ||
|
|
2d63f7628f | ||
|
|
52f73e0c2d | ||
|
|
980e1b533f | ||
|
|
2ea332421c | ||
|
|
4edecfed3e | ||
|
|
113c471bb0 | ||
|
|
10fef92eea | ||
|
|
3ee4edc404 | ||
|
|
eda233344c | ||
|
|
17a483cb03 | ||
|
|
7b277d2efd | ||
|
|
63cfcc26fb | ||
|
|
53a5145dc8 | ||
|
|
def342e35c | ||
|
|
bbb8d63de0 | ||
|
|
e5b97a5e48 | ||
|
|
e06666759a | ||
|
|
a78f3cfea2 | ||
|
|
0acd556106 | ||
|
|
2f7a62780a | ||
|
|
f1e6b144e8 | ||
|
|
10a4760f90 | ||
|
|
cdd07fdf29 | ||
|
|
8a3fb890b0 | ||
|
|
30af8d0a81 | ||
|
|
ceb7fc2cb2 | ||
|
|
d8694510b5 | ||
|
|
ec7ce41324 | ||
|
|
b2921bd3fd | ||
|
|
64756fa96f | ||
|
|
ff6844300e | ||
|
|
b1b6401ce7 | ||
|
|
07ba7c8c44 | ||
|
|
6516249302 | ||
|
|
fc8218d728 | ||
|
|
a8d56877ee | ||
|
|
dd0de3cfa9 | ||
|
|
198c36811e | ||
|
|
133704a419 | ||
|
|
d1302a7a08 | ||
|
|
2b28b5969f | ||
|
|
bc941bfc97 | ||
|
|
72a9429ef6 | ||
|
|
f020291039 | ||
|
|
4b3a2a33a8 | ||
|
|
1f257f4704 | ||
|
|
f517050548 | ||
|
|
535ba75bc7 | ||
|
|
ee280b0d05 | ||
|
|
a43793493a | ||
|
|
3b3c379852 | ||
|
|
147f407b7a | ||
|
|
822f42e8fe | ||
|
|
7e097d529a | ||
|
|
65f76e6f4d | ||
|
|
918cba4cce | ||
|
|
4067ae4b37 | ||
|
|
084a0233b6 | ||
|
|
04732b23fb | ||
|
|
abc027558a | ||
|
|
4d106a4b0b | ||
|
|
ec4d9ec111 | ||
|
|
10053e2566 | ||
|
|
7a55da58d9 | ||
|
|
5043eaedc4 | ||
|
|
328e4d6278 | ||
|
|
83513bab59 | ||
|
|
b9ca240242 | ||
|
|
5d751cd407 | ||
|
|
ef53f7af22 | ||
|
|
4118f42d61 | ||
|
|
ec56755f9e | ||
|
|
aba89ba12a | ||
|
|
7ff40091d8 | ||
|
|
dabc35b257 | ||
|
|
8f40bcc86c | ||
|
|
3e75a661dd | ||
|
|
a9e753fc07 | ||
|
|
2d6592dcaa | ||
|
|
59a153b2e1 | ||
|
|
10a6cd00e7 | ||
|
|
f6f6980463 | ||
|
|
2e302b1a7c | ||
|
|
17323ed7b2 | ||
|
|
3759e0bf55 | ||
|
|
144d8a1db6 |
25
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
25
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -10,16 +10,39 @@ body:
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
<!-- 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:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
Actual 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:
|
||||
required: true
|
||||
|
||||
|
||||
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@@ -109,8 +109,16 @@ jobs:
|
||||
- name: cargo 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
|
||||
uses: bnjbvr/cargo-machete@main
|
||||
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
|
||||
with:
|
||||
command: machete
|
||||
|
||||
- name: Check licenses
|
||||
run: |
|
||||
@@ -232,7 +240,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: hosted-windows-1
|
||||
runs-on: hosted-windows-2
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
@@ -265,8 +273,7 @@ jobs:
|
||||
|
||||
- name: cargo clippy
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||
run: cargo xtask clippy
|
||||
run: ./script/clippy.ps1
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests_windows
|
||||
@@ -298,8 +305,9 @@ jobs:
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
@@ -350,14 +358,14 @@ jobs:
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -408,7 +416,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -456,7 +464,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
|
||||
25
.github/workflows/community_update_all_top_ranking_issues.yml
vendored
Normal file
25
.github/workflows/community_update_all_top_ranking_issues.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
25
.github/workflows/community_update_weekly_top_ranking_issues.yml
vendored
Normal file
25
.github/workflows/community_update_weekly_top_ranking_issues.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
2
.github/workflows/deploy_cloudflare.yml
vendored
2
.github/workflows/deploy_cloudflare.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||
|
||||
- name: Preserve Wrangler logs
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
if: always()
|
||||
with:
|
||||
name: wrangler_logs
|
||||
|
||||
5
.github/workflows/release_nightly.yml
vendored
5
.github/workflows/release_nightly.yml
vendored
@@ -62,8 +62,9 @@ jobs:
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
|
||||
4
.mailmap
4
.mailmap
@@ -21,6 +21,8 @@ Andrei Zvonimir Crnković <andrei@0x7f.dev>
|
||||
Andrei Zvonimir Crnković <andrei@0x7f.dev> <andreicek@0x7f.dev>
|
||||
Antonio Scandurra <me@as-cii.com>
|
||||
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> <53836821+bennetbo@users.noreply.github.com>
|
||||
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
|
||||
@@ -112,6 +114,8 @@ Sebastijan Kelnerič <sebastijan.kelneric@sebba.dev> <sebastijan.kelneric@vichav
|
||||
Sergey Onufrienko <sergey@onufrienko.com>
|
||||
Shish <webmaster@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.kroeger@hexagon.com>
|
||||
Thorsten Ball <thorsten@zed.dev>
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
},
|
||||
"JSON": {
|
||||
"tab_size": 2,
|
||||
"preferred_line_length": 100,
|
||||
"preferred_line_length": 120,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"JSONC": {
|
||||
"tab_size": 2,
|
||||
"preferred_line_length": 100,
|
||||
"preferred_line_length": 120,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"JavaScript": {
|
||||
|
||||
282
Cargo.lock
generated
282
Cargo.lock
generated
@@ -395,7 +395,6 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"languages",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -463,7 +462,6 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
@@ -492,6 +490,7 @@ dependencies = [
|
||||
"ui",
|
||||
"util",
|
||||
"uuid",
|
||||
"vim_mode_setting",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
@@ -518,7 +517,6 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"languages",
|
||||
"log",
|
||||
"multi_buffer",
|
||||
@@ -1178,9 +1176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.16"
|
||||
version = "1.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915"
|
||||
checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1270,10 +1268,34 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.61.0"
|
||||
name = "aws-sdk-bedrockruntime"
|
||||
version = "1.75.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89f2163d8704e8fdcd51ec6c2e0441c418471e422ee9690451b17a1c46344e1a"
|
||||
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]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31622345afd0c35d33c1cbba73ccf9fb88e09857413d8963dea2c493e00704d"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1293,9 +1315,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.76.0"
|
||||
version = "1.77.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66e83401ad7287ad15244d557e35502c2a94105ca5b41d656c391f1a4fc04ca2"
|
||||
checksum = "34e87342432a3de0e94e82c99a7cbd9042f99de029ae1f4e368160f9e9929264"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1327,9 +1349,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.58.0"
|
||||
version = "1.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770"
|
||||
checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1349,9 +1371,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.59.0"
|
||||
version = "1.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809"
|
||||
checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1371,9 +1393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.59.0"
|
||||
version = "1.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00"
|
||||
checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1434,9 +1456,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-checksums"
|
||||
version = "0.62.0"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f45a1c384d7a393026bc5f5c177105aa9fa68e4749653b985707ac27d77295"
|
||||
checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c"
|
||||
dependencies = [
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-types",
|
||||
@@ -1598,6 +1620,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "axum"
|
||||
version = "0.6.20"
|
||||
@@ -1727,6 +1760,22 @@ version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "bigdecimal"
|
||||
version = "0.4.7"
|
||||
@@ -1759,7 +1808,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
@@ -1782,7 +1831,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -2352,25 +2401,6 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"heck 0.4.1",
|
||||
"indexmap",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.90",
|
||||
"tempfile",
|
||||
"toml 0.8.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.28.0"
|
||||
@@ -2468,9 +2498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -2478,7 +2508,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2538,9 +2568,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.30"
|
||||
version = "4.5.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2548,9 +2578,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.30"
|
||||
version = "4.5.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2605,6 +2635,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tempfile",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2785,6 +2816,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"git_ui",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"hex",
|
||||
@@ -2971,7 +3003,6 @@ dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
"linkme",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"theme",
|
||||
]
|
||||
@@ -3052,6 +3083,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"context_server_settings",
|
||||
@@ -3092,9 +3124,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -3112,7 +3144,9 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger 0.11.6",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -3120,6 +3154,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"inline_completion",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"menu",
|
||||
"node_runtime",
|
||||
@@ -3470,11 +3505,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc64fast-nvme"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5e2ee08013e3f228d6d2394116c4549a6df77708442c62d887d83f68ef2ee37"
|
||||
checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3"
|
||||
dependencies = [
|
||||
"cbindgen 0.27.0",
|
||||
"crc",
|
||||
]
|
||||
|
||||
@@ -3611,9 +3645,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bba74424191d99c617a172ef126d429f62fe54aba1002c4d36e49403ce4b00a2"
|
||||
checksum = "a7747ac3a66a06f4ee6718686c8ea976d2d05fb30ada93ebd76b3f9aef97257c"
|
||||
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]]
|
||||
name = "ctrlc"
|
||||
@@ -3989,6 +4033,21 @@ dependencies = [
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bf39a0bfd1f94d62ffdb2802a7e6244c0f34f6ebacf5d4c26547d08cd1d67a5"
|
||||
dependencies = [
|
||||
"dtor-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor-proc-macro"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
@@ -4043,7 +4102,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"convert_case 0.7.1",
|
||||
"convert_case 0.8.0",
|
||||
"ctor",
|
||||
"db",
|
||||
"emojis",
|
||||
@@ -5286,10 +5345,12 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"rope",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"tempfile",
|
||||
"text",
|
||||
"time",
|
||||
"unindent",
|
||||
@@ -5359,6 +5420,7 @@ dependencies = [
|
||||
"theme",
|
||||
"time",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"workspace",
|
||||
@@ -5493,7 +5555,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cbindgen 0.28.0",
|
||||
"cbindgen",
|
||||
"cocoa 0.26.0",
|
||||
"collections",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -6950,17 +7012,14 @@ dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"client",
|
||||
"collections",
|
||||
"deepseek",
|
||||
"futures 0.3.31",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"image",
|
||||
"lmstudio",
|
||||
"log",
|
||||
"mistral",
|
||||
"ollama",
|
||||
"open_ai",
|
||||
"parking_lot",
|
||||
"proto",
|
||||
@@ -6969,6 +7028,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"smol",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"thiserror 1.0.69",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -6995,6 +7055,10 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws_http_client",
|
||||
"bedrock",
|
||||
"client",
|
||||
"collections",
|
||||
"copilot",
|
||||
@@ -7006,6 +7070,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"language_model",
|
||||
"lmstudio",
|
||||
@@ -7021,10 +7086,9 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"theme",
|
||||
"thiserror 1.0.69",
|
||||
"tiktoken-rs",
|
||||
"tokio",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
@@ -7164,9 +7228,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
version = "0.2.170"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -7510,9 +7574,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"value-bag",
|
||||
@@ -7883,7 +7947,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"convert_case 0.7.1",
|
||||
"convert_case 0.8.0",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"streaming-iterator",
|
||||
@@ -9262,7 +9326,7 @@ name = "perplexity"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.2.0",
|
||||
"zed_extension_api 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9693,6 +9757,15 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pgvector"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0e8871b6d7ca78348c6cd29b911b94851f3429f0cd403130ca17f26c1fb91a6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
@@ -10317,7 +10390,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.10.0",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
"once_cell",
|
||||
@@ -10350,7 +10423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
@@ -11378,9 +11451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.5.0"
|
||||
version = "8.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
|
||||
checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
@@ -11389,9 +11462,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.5.0"
|
||||
version = "8.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
|
||||
checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11402,9 +11475,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.5.0"
|
||||
version = "8.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
|
||||
checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2",
|
||||
@@ -11679,9 +11752,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.21"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"indexmap",
|
||||
@@ -11692,9 +11765,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.21"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11757,17 +11830,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm"
|
||||
version = "1.1.5"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00733e5418e8ae3758cdb988c3654174e716230cc53ee2cb884207cf86a23029"
|
||||
checksum = "13fba7b2c749b2d0a00303d5cb13e6761e39a4172554bdf930852cac4e7aeabd"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"futures 0.3.31",
|
||||
"futures-util",
|
||||
"log",
|
||||
"ouroboros",
|
||||
"pgvector",
|
||||
"rust_decimal",
|
||||
"sea-orm-macros",
|
||||
"sea-query",
|
||||
@@ -11785,9 +11859,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm-macros"
|
||||
version = "1.1.5"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a98408f82fb4875d41ef469a79944a7da29767c7b3e4028e22188a3dd613b10f"
|
||||
checksum = "2568cff8d35d5150b4276cc0dd766192a587f64b6ece60ae3706e0872c4eb209"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -11980,18 +12054,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -14504,7 +14578,7 @@ dependencies = [
|
||||
name = "ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"convert_case 0.7.1",
|
||||
"convert_case 0.8.0",
|
||||
"linkme",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -14707,9 +14781,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.13.2"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
|
||||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"serde",
|
||||
@@ -15832,6 +15906,12 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
@@ -16667,7 +16747,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.176.0"
|
||||
version = "0.177.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -16771,6 +16851,7 @@ dependencies = [
|
||||
"tasks_ui",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"tempfile",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"theme_extension",
|
||||
@@ -16787,6 +16868,7 @@ dependencies = [
|
||||
"vim",
|
||||
"vim_mode_setting",
|
||||
"welcome",
|
||||
"which 6.0.3",
|
||||
"windows 0.58.0",
|
||||
"winresource",
|
||||
"workspace",
|
||||
@@ -16821,7 +16903,7 @@ dependencies = [
|
||||
name = "zed_elixir"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zed_extension_api 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16852,6 +16934,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -16860,9 +16944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -16885,7 +16967,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_html"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -16948,7 +17030,7 @@ dependencies = [
|
||||
name = "zed_test_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.2.0",
|
||||
"zed_extension_api 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -17130,7 +17212,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"inline_completion",
|
||||
"language",
|
||||
"language_models",
|
||||
"language_model",
|
||||
"log",
|
||||
"menu",
|
||||
"migrator",
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -15,6 +15,8 @@ members = [
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
"crates/auto_update_ui",
|
||||
"crates/aws_http_client",
|
||||
"crates/bedrock",
|
||||
"crates/breadcrumbs",
|
||||
"crates/buffer_diff",
|
||||
"crates/call",
|
||||
@@ -218,6 +220,8 @@ assistant_tools = { path = "crates/assistant_tools" }
|
||||
audio = { path = "crates/audio" }
|
||||
auto_update = { path = "crates/auto_update" }
|
||||
auto_update_ui = { path = "crates/auto_update_ui" }
|
||||
aws_http_client = { path = "crates/aws_http_client" }
|
||||
bedrock = { path = "crates/bedrock" }
|
||||
breadcrumbs = { path = "crates/breadcrumbs" }
|
||||
call = { path = "crates/call" }
|
||||
channel = { path = "crates/channel" }
|
||||
@@ -366,7 +370,7 @@ zeta = { path = "crates/zeta" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e"}
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" }
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
@@ -382,6 +386,11 @@ async-trait = "0.1"
|
||||
async-tungstenite = "0.28"
|
||||
async-watch = "0.3.1"
|
||||
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"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
||||
@@ -396,10 +405,10 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
cocoa = "0.26"
|
||||
cocoa-foundation = "0.2.0"
|
||||
convert_case = "0.7.0"
|
||||
convert_case = "0.8.0"
|
||||
core-foundation = "0.9.3"
|
||||
core-foundation-sys = "0.8.6"
|
||||
ctor = "0.3.0"
|
||||
ctor = "0.4.0"
|
||||
dashmap = "6.0"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
@@ -446,7 +455,6 @@ nanoid = "0.4"
|
||||
nbformat = { version = "0.10.0" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.20"
|
||||
ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
@@ -535,7 +543,7 @@ tree-sitter-cpp = "0.23"
|
||||
tree-sitter-css = "0.23"
|
||||
tree-sitter-elixir = "0.3"
|
||||
tree-sitter-embedded-template = "0.23.0"
|
||||
tree-sitter-gitcommit = {git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9"}
|
||||
tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
|
||||
tree-sitter-go = "0.23"
|
||||
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
|
||||
@@ -610,6 +618,7 @@ features = [
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_DataExchange",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
@@ -630,7 +639,7 @@ features = [
|
||||
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
||||
[patch.crates-io]
|
||||
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
||||
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls"}
|
||||
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
@@ -700,6 +709,9 @@ debug = "full"
|
||||
lto = false
|
||||
codegen-units = 16
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "allow" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
dbg_macro = "deny"
|
||||
todo = "deny"
|
||||
|
||||
4
assets/icons/ai_bedrock.svg
Normal file
4
assets/icons/ai_bedrock.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -84,12 +84,12 @@
|
||||
"pageup": "editor::MovePageUp",
|
||||
"alt-pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"down": "editor::MoveDown",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"alt-pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-left": "editor::MoveToPreviousWordStart",
|
||||
@@ -107,9 +107,9 @@
|
||||
"ctrl-a": "editor::SelectAll",
|
||||
"ctrl-l": "editor::SelectLine",
|
||||
"ctrl-shift-i": "editor::Format",
|
||||
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
// "cmd-shift-right": ["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 }],
|
||||
@@ -118,7 +118,7 @@
|
||||
"ctrl-;": "editor::ToggleLineNumbers",
|
||||
"ctrl-k ctrl-r": "git::Restore",
|
||||
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
||||
"ctrl-\"": "editor::ExpandAllHunkDiffs",
|
||||
"ctrl-\"": "editor::ExpandAllDiffHunks",
|
||||
"ctrl-i": "editor::ShowSignatureHelp",
|
||||
"alt-g b": "editor::ToggleGitBlame",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
@@ -184,9 +184,9 @@
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-k h": "assistant::DeployHistory",
|
||||
"ctrl-k l": "assistant::DeployPromptLibrary",
|
||||
"new": "assistant::NewContext",
|
||||
"ctrl-t": "assistant::NewContext",
|
||||
"ctrl-n": "assistant::NewContext"
|
||||
"new": "assistant::NewChat",
|
||||
"ctrl-t": "assistant::NewChat",
|
||||
"ctrl-n": "assistant::NewChat"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -368,7 +368,12 @@
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -601,7 +606,7 @@
|
||||
"ctrl-n": "assistant2::NewThread",
|
||||
"new": "assistant2::NewThread",
|
||||
"ctrl-shift-h": "assistant2::OpenHistory",
|
||||
"ctrl-alt-/": "assistant2::ToggleModelSelector",
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-shift-a": "assistant2::ToggleContextPicker",
|
||||
"ctrl-e": "assistant2::ChatMode",
|
||||
"ctrl-alt-e": "assistant2::RemoveAllContext"
|
||||
@@ -705,12 +710,6 @@
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && !CommitEditor",
|
||||
"bindings": {
|
||||
"escape": "git_panel::Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"bindings": {
|
||||
@@ -722,19 +721,42 @@
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"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",
|
||||
"bindings": {
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"bindings": {
|
||||
@@ -813,6 +835,7 @@
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
|
||||
@@ -91,12 +91,12 @@
|
||||
"ctrl-l": "editor::ScrollCursorCenter",
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
"cmd-left": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"cmd-right": "editor::MoveToEndOfLine",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"cmd-left": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"cmd-up": "editor::MoveToStartOfExcerpt",
|
||||
"cmd-down": "editor::MoveToEndOfExcerpt",
|
||||
"cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left`
|
||||
@@ -118,9 +118,9 @@
|
||||
"cmd-a": "editor::SelectAll",
|
||||
"cmd-l": "editor::SelectLine",
|
||||
"cmd-shift-i": "editor::Format",
|
||||
"cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
|
||||
"cmd-shift-right": ["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 }],
|
||||
@@ -133,7 +133,7 @@
|
||||
"cmd-y": "git::StageAndNext",
|
||||
"cmd-shift-y": "git::UnstageAndNext",
|
||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||
"cmd-\"": "editor::ExpandAllHunkDiffs",
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
@@ -211,8 +211,8 @@
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-k h": "assistant::DeployHistory",
|
||||
"cmd-k l": "assistant::DeployPromptLibrary",
|
||||
"cmd-t": "assistant::NewContext",
|
||||
"cmd-n": "assistant::NewContext"
|
||||
"cmd-t": "assistant::NewChat",
|
||||
"cmd-n": "assistant::NewChat"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -238,7 +238,7 @@
|
||||
"cmd-n": "assistant2::NewThread",
|
||||
"cmd-alt-p": "assistant2::NewPromptEditor",
|
||||
"cmd-shift-h": "assistant2::OpenHistory",
|
||||
"cmd-alt-/": "assistant2::ToggleModelSelector",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-e": "assistant2::ChatMode",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext"
|
||||
@@ -658,7 +658,7 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-alt-/": "assistant2::ToggleModelSelector",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext",
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
@@ -739,14 +739,14 @@
|
||||
"alt-down": "git_panel::FocusEditor",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus"
|
||||
"escape": "git_panel::ToggleFocus",
|
||||
"cmd-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"context": "GitDiff > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"cmd-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
@@ -761,6 +761,14 @@
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"cmd-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"use_key_equivalents": true,
|
||||
@@ -831,6 +839,7 @@
|
||||
"cmd-k": "terminal::Clear",
|
||||
"cmd-n": "workspace::NewTerminal",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-_": null, // emacs undo
|
||||
// Some nice conveniences
|
||||
"cmd-backspace": ["terminal::SendText", "\u0015"],
|
||||
"cmd-right": ["terminal::SendText", "\u0005"],
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"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-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-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
@@ -48,6 +49,8 @@
|
||||
"ctrl-_": "editor::Undo", // undo
|
||||
"ctrl-/": "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
|
||||
"alt-v": "editor::MovePageUp", // scroll-down
|
||||
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"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-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-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
@@ -48,6 +49,8 @@
|
||||
"ctrl-_": "editor::Undo", // undo
|
||||
"ctrl-/": "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
|
||||
"alt-v": "editor::MovePageUp", // scroll-down
|
||||
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
|
||||
@@ -437,6 +437,7 @@
|
||||
"context": "vim_operator == c",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"x": "vim::Exchange",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushChangeSurrounds", {}]
|
||||
}
|
||||
@@ -523,6 +524,19 @@
|
||||
"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",
|
||||
"bindings": {
|
||||
|
||||
@@ -150,8 +150,6 @@
|
||||
//
|
||||
// Default: not set, defaults to "bar"
|
||||
"cursor_shape": null,
|
||||
// Determines whether the mouse cursor is hidden when typing in an editor or input box.
|
||||
"hide_mouse_while_typing": true,
|
||||
// How to highlight the current line in the editor.
|
||||
//
|
||||
// 1. Don't highlight the current line:
|
||||
@@ -178,8 +176,8 @@
|
||||
"show_completion_documentation": true,
|
||||
// Show method signatures in the editor, when inside parentheses.
|
||||
"auto_signature_help": false,
|
||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
"show_signature_help_after_edits": false,
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
@@ -300,11 +298,11 @@
|
||||
// - "information": show only errors, warnings, and information
|
||||
// - "all" or true: show all diagnostics
|
||||
"diagnostics": "all",
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
// Forcefully enable or disable the scrollbar for each axis
|
||||
"axes": {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
"horizontal": true,
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
"vertical": true
|
||||
}
|
||||
},
|
||||
@@ -330,24 +328,24 @@
|
||||
"folds": true
|
||||
},
|
||||
"indent_guides": {
|
||||
/// Whether to show indent guides in the editor.
|
||||
// Whether to show indent guides in the editor.
|
||||
"enabled": true,
|
||||
/// The width of the indent guides in pixels, between 1 and 10.
|
||||
// The width of the indent guides in pixels, between 1 and 10.
|
||||
"line_width": 1,
|
||||
/// The width of the active indent guide in pixels, between 1 and 10.
|
||||
// The width of the active indent guide in pixels, between 1 and 10.
|
||||
"active_line_width": 1,
|
||||
/// Determines how indent guides are colored.
|
||||
/// This setting can take the following three values:
|
||||
// Determines how indent guides are colored.
|
||||
// This setting can take the following three values:
|
||||
///
|
||||
/// 1. "disabled"
|
||||
/// 2. "fixed"
|
||||
/// 3. "indent_aware"
|
||||
// 1. "disabled"
|
||||
// 2. "fixed"
|
||||
// 3. "indent_aware"
|
||||
"coloring": "fixed",
|
||||
/// Determines how indent guide backgrounds are colored.
|
||||
/// This setting can take the following two values:
|
||||
// Determines how indent guide backgrounds are colored.
|
||||
// This setting can take the following two values:
|
||||
///
|
||||
/// 1. "disabled"
|
||||
/// 2. "indent_aware"
|
||||
// 1. "disabled"
|
||||
// 2. "indent_aware"
|
||||
"background_coloring": "disabled"
|
||||
},
|
||||
// Whether the editor will scroll beyond the last line.
|
||||
@@ -381,6 +379,9 @@
|
||||
// 3. Never populate the search query
|
||||
// "never"
|
||||
"seed_search_query_from_cursor": "always",
|
||||
// When enabled, automatically adjusts search case sensitivity based on your query.
|
||||
// If your search query contains any uppercase letters, the search becomes case-sensitive;
|
||||
// if it contains only lowercase letters, the search becomes case-insensitive.
|
||||
"use_smartcase_search": false,
|
||||
// Inlay hint related settings
|
||||
"inlay_hints": {
|
||||
@@ -400,7 +401,16 @@
|
||||
"edit_debounce_ms": 700,
|
||||
// Time to wait after scrolling the buffer, before requesting the hints,
|
||||
// set to 0 to disable debouncing.
|
||||
"scroll_debounce_ms": 50
|
||||
"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": {
|
||||
// Whether to show the project panel button in the status bar
|
||||
@@ -426,32 +436,32 @@
|
||||
// Whether to fold directories automatically and show compact folders
|
||||
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
|
||||
"auto_fold_dirs": true,
|
||||
/// Scrollbar-related settings
|
||||
// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the project panel.
|
||||
/// This setting can take five values:
|
||||
// When to show the scrollbar in the project panel.
|
||||
// This setting can take five values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
// 1. null (default): Inherit editor settings
|
||||
// 2. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 3. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 4. Always show the scrollbar:
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null
|
||||
},
|
||||
/// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
/// This setting can take the following three values:
|
||||
// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
// This setting can take the following three values:
|
||||
///
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
// 1. Do not mark any files:
|
||||
// "off"
|
||||
// 2. Only mark files with errors:
|
||||
// "errors"
|
||||
// 3. Mark files with errors and warnings:
|
||||
// "all"
|
||||
"show_diagnostics": "all",
|
||||
// Settings related to indent guides in the project panel.
|
||||
"indent_guides": {
|
||||
@@ -484,8 +494,8 @@
|
||||
// when a corresponding outline entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
"auto_reveal_entries": true,
|
||||
/// Whether to fold directories automatically
|
||||
/// when a directory has only one directory inside.
|
||||
// Whether to fold directories automatically
|
||||
// when a directory has only one directory inside.
|
||||
"auto_fold_dirs": true,
|
||||
// Settings related to indent guides in the outline panel.
|
||||
"indent_guides": {
|
||||
@@ -498,21 +508,21 @@
|
||||
// "never"
|
||||
"show": "always"
|
||||
},
|
||||
/// Scrollbar-related settings
|
||||
// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the project panel.
|
||||
/// This setting can take five values:
|
||||
// When to show the scrollbar in the project panel.
|
||||
// This setting can take five values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
// 1. null (default): Inherit editor settings
|
||||
// 2. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 3. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 4. Always show the scrollbar:
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null
|
||||
}
|
||||
},
|
||||
@@ -583,7 +593,7 @@
|
||||
// The provider to use.
|
||||
"provider": "zed.dev",
|
||||
// The model to use.
|
||||
"model": "claude-3-5-sonnet"
|
||||
"model": "claude-3-5-sonnet-latest"
|
||||
}
|
||||
},
|
||||
// The settings for slash commands.
|
||||
@@ -630,7 +640,7 @@
|
||||
"show": true,
|
||||
// Whether or not to show the navigation history buttons.
|
||||
"show_nav_history_buttons": true,
|
||||
/// Whether or not to show the tab bar buttons.
|
||||
// Whether or not to show the tab bar buttons.
|
||||
"show_tab_bar_buttons": true
|
||||
},
|
||||
// Settings related to the editor's tabs
|
||||
@@ -652,16 +662,16 @@
|
||||
// 3. Activate the left neighbour tab if present
|
||||
// "left_neighbour"
|
||||
"activate_on_close": "history",
|
||||
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
/// Diagnostics are only shown when file icons are also active.
|
||||
/// This setting only works when can take the following three values:
|
||||
// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
// Diagnostics are only shown when file icons are also active.
|
||||
// This setting only works when can take the following three values:
|
||||
///
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
// 1. Do not mark any files:
|
||||
// "off"
|
||||
// 2. Only mark files with errors:
|
||||
// "errors"
|
||||
// 3. Mark files with errors and warnings:
|
||||
// "all"
|
||||
"show_diagnostics": "off"
|
||||
},
|
||||
// Settings related to preview tabs.
|
||||
@@ -831,6 +841,8 @@
|
||||
// A list of globs representing files that edit predictions should be disabled for.
|
||||
// There's a sensible default list of globs already included.
|
||||
// Any addition to this list will be merged with the default list.
|
||||
// Globs are matched relative to the worktree root,
|
||||
// except when starting with a slash (/) or equivalent in Windows.
|
||||
"disabled_globs": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
@@ -842,11 +854,14 @@
|
||||
],
|
||||
// When to show edit predictions previews in buffer.
|
||||
// This setting takes two possible values:
|
||||
// 1. Display inline when there are no language server completions available.
|
||||
// "mode": "eager_preview"
|
||||
// 2. Display inline when holding modifier key (alt by default).
|
||||
// "mode": "auto"
|
||||
"mode": "eager_preview"
|
||||
// 1. Display predictions inline when there are no language server completions available.
|
||||
// "mode": "eager"
|
||||
// 2. Display predictions inline only when holding a modifier key (alt by default).
|
||||
// "mode": "subtle"
|
||||
"mode": "eager",
|
||||
// Whether edit predictions are enabled in the assistant panel.
|
||||
// This setting has no effect if globally disabled.
|
||||
"enabled_in_assistant": true
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
@@ -982,21 +997,21 @@
|
||||
// Example: `echo -e "\e]2;New Title\007";`
|
||||
"breadcrumbs": true
|
||||
},
|
||||
/// Scrollbar-related settings
|
||||
// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the terminal.
|
||||
/// This setting can take five values:
|
||||
// When to show the scrollbar in the terminal.
|
||||
// This setting can take five values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
// 1. null (default): Inherit editor settings
|
||||
// 2. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 3. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 4. Always show the scrollbar:
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
@@ -1015,7 +1030,7 @@
|
||||
// "max_scroll_history_lines": 10000,
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
/// Settings related to running tasks.
|
||||
// Settings related to running tasks.
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
@@ -1036,20 +1051,20 @@
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
||||
"Shell Script": [".env.*"]
|
||||
},
|
||||
/// By default use a recent system version of node, or install our own.
|
||||
/// You can override this to use a version of node that is not in $PATH with:
|
||||
/// {
|
||||
/// "node": {
|
||||
/// "path": "/path/to/node"
|
||||
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
||||
/// }
|
||||
/// }
|
||||
/// or to ensure Zed always downloads and installs an isolated version of node:
|
||||
/// {
|
||||
/// "node": {
|
||||
/// "ignore_system_version": true,
|
||||
/// }
|
||||
/// NOTE: changing this setting currently requires restarting Zed.
|
||||
// By default use a recent system version of node, or install our own.
|
||||
// You can override this to use a version of node that is not in $PATH with:
|
||||
// {
|
||||
// "node": {
|
||||
// "path": "/path/to/node"
|
||||
// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
||||
// }
|
||||
// }
|
||||
// or to ensure Zed always downloads and installs an isolated version of node:
|
||||
// {
|
||||
// "node": {
|
||||
// "ignore_system_version": true,
|
||||
// }
|
||||
// NOTE: changing this setting currently requires restarting Zed.
|
||||
"node": {},
|
||||
// The extensions that Zed should automatically install on startup.
|
||||
//
|
||||
@@ -1095,6 +1110,7 @@
|
||||
"tab_size": 2
|
||||
},
|
||||
"Diff": {
|
||||
"show_edit_predictions": false,
|
||||
"remove_trailing_whitespace_on_save": false,
|
||||
"ensure_final_newline_on_save": false
|
||||
},
|
||||
@@ -1297,6 +1313,7 @@
|
||||
},
|
||||
// Vim settings
|
||||
"vim": {
|
||||
"default_mode": "normal",
|
||||
"toggle_relative_line_numbers": false,
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false,
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#83a598ff",
|
||||
"color": "#ebdbb2ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -767,7 +767,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#83a598ff",
|
||||
"color": "#ebdbb2ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -1155,7 +1155,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#83a598ff",
|
||||
"color": "#ebdbb2ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -1543,7 +1543,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#066578ff",
|
||||
"color": "#282828ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -1931,7 +1931,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#066578ff",
|
||||
"color": "#282828ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -2319,7 +2319,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#066578ff",
|
||||
"color": "#282828ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#dce0e5ff",
|
||||
"color": "#acb2beff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -30,6 +30,8 @@ pub enum Model {
|
||||
#[default]
|
||||
#[serde(rename = "claude-3-5-sonnet", 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-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||
Claude3_5Haiku,
|
||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||
@@ -59,6 +61,8 @@ impl Model {
|
||||
pub fn from_id(id: &str) -> Result<Self> {
|
||||
if id.starts_with("claude-3-5-sonnet") {
|
||||
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") {
|
||||
Ok(Self::Claude3_5Haiku)
|
||||
} else if id.starts_with("claude-3-opus") {
|
||||
@@ -75,6 +79,7 @@ impl Model {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
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::Claude3Opus => "claude-3-opus-latest",
|
||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||
@@ -85,6 +90,7 @@ impl Model {
|
||||
|
||||
pub fn display_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
||||
Self::Claude3Opus => "Claude 3 Opus",
|
||||
@@ -98,13 +104,14 @@ impl Model {
|
||||
|
||||
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
|
||||
Some(AnthropicModelCacheConfiguration {
|
||||
min_total_token: 2_048,
|
||||
should_speculate: true,
|
||||
max_cache_anchors: 4,
|
||||
})
|
||||
}
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
|
||||
min_total_token: 2_048,
|
||||
should_speculate: true,
|
||||
max_cache_anchors: 4,
|
||||
}),
|
||||
Self::Custom {
|
||||
cache_configuration,
|
||||
..
|
||||
@@ -117,6 +124,7 @@ impl Model {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3Haiku => 200_000,
|
||||
@@ -127,7 +135,7 @@ impl Model {
|
||||
pub fn max_output_tokens(&self) -> u32 {
|
||||
match self {
|
||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
|
||||
Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
|
||||
Self::Claude3_5Sonnet | Self::Claude3_7Sonnet | Self::Claude3_5Haiku => 8_192,
|
||||
Self::Custom {
|
||||
max_output_tokens, ..
|
||||
} => max_output_tokens.unwrap_or(4_096),
|
||||
@@ -137,6 +145,7 @@ impl Model {
|
||||
pub fn default_temperature(&self) -> f32 {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
|
||||
@@ -43,7 +43,6 @@ indoc.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -33,7 +33,7 @@ actions!(
|
||||
[
|
||||
InsertActivePrompt,
|
||||
DeployHistory,
|
||||
NewContext,
|
||||
NewChat,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
|
||||
use crate::{
|
||||
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewContext,
|
||||
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewChat,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::{
|
||||
@@ -129,7 +129,7 @@ impl AssistantPanel {
|
||||
workspace.project().clone(),
|
||||
Default::default(),
|
||||
None,
|
||||
NewContext.boxed_clone(),
|
||||
NewChat.boxed_clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -228,12 +228,12 @@ impl AssistantPanel {
|
||||
IconButton::new("new-chat", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(NewContext.boxed_clone(), cx)
|
||||
window.dispatch_action(NewChat.boxed_clone(), cx)
|
||||
}))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Chat",
|
||||
&NewContext,
|
||||
&NewChat,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -256,7 +256,7 @@ impl AssistantPanel {
|
||||
let focus_handle = _pane.focus_handle(cx);
|
||||
Some(ContextMenu::build(window, cx, move |menu, _, _| {
|
||||
menu.context(focus_handle.clone())
|
||||
.action("New Chat", Box::new(NewContext))
|
||||
.action("New Chat", Box::new(NewChat))
|
||||
.action("History", Box::new(DeployHistory))
|
||||
.action("Prompt Library", Box::new(DeployPromptLibrary))
|
||||
.action("Configure", Box::new(ShowConfiguration))
|
||||
@@ -760,7 +760,7 @@ impl AssistantPanel {
|
||||
|
||||
pub fn create_new_context(
|
||||
workspace: &mut Workspace,
|
||||
_: &NewContext,
|
||||
_: &NewChat,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
@@ -978,7 +978,7 @@ impl AssistantPanel {
|
||||
.active_provider()
|
||||
.map_or(true, |p| p.id() != provider.id())
|
||||
{
|
||||
if let Some(model) = provider.provided_models(cx).first().cloned() {
|
||||
if let Some(model) = provider.default_model(cx) {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
this.fs.clone(),
|
||||
cx,
|
||||
@@ -1206,7 +1206,7 @@ impl Render for AssistantPanel {
|
||||
v_flex()
|
||||
.key_context("AssistantPanel")
|
||||
.size_full()
|
||||
.on_action(cx.listener(|this, _: &NewContext, window, cx| {
|
||||
.on_action(cx.listener(|this, _: &NewChat, window, cx| {
|
||||
this.new_context(window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| {
|
||||
|
||||
@@ -32,11 +32,10 @@ use gpui::{
|
||||
};
|
||||
use language::{line_diff, Buffer, IndentKind, Point, Selection, TransactionId};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role,
|
||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_models::report_assistant_event;
|
||||
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
@@ -1590,29 +1589,10 @@ impl Render for PromptEditor {
|
||||
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
))
|
||||
.child(
|
||||
InlineLanguageModelSelector::new(self.language_model_selector.clone())
|
||||
.render(window, cx),
|
||||
)
|
||||
.map(|el| {
|
||||
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
|
||||
return el;
|
||||
|
||||
@@ -16,10 +16,10 @@ use gpui::{
|
||||
};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_models::report_assistant_event;
|
||||
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
|
||||
use prompt_library::PromptBuilder;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::{
|
||||
@@ -506,7 +506,7 @@ struct PromptEditor {
|
||||
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let buttons = match status {
|
||||
CodegenStatus::Idle => {
|
||||
@@ -641,29 +641,10 @@ impl Render for PromptEditor {
|
||||
.w_12()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
))
|
||||
.child(
|
||||
InlineLanguageModelSelector::new(self.language_model_selector.clone())
|
||||
.render(window, cx),
|
||||
)
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
@@ -1073,7 +1054,10 @@ pub enum CodegenEvent {
|
||||
|
||||
impl EventEmitter<CodegenEvent> for Codegen {}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const CLEAR_INPUT: &str = "\x15";
|
||||
#[cfg(target_os = "windows")]
|
||||
const CLEAR_INPUT: &str = "\x03";
|
||||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
|
||||
@@ -46,7 +46,6 @@ itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
@@ -74,6 +73,7 @@ time_format.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
|
||||
@@ -8,15 +8,16 @@ use gpui::{
|
||||
UnderlineStyle, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::Role;
|
||||
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use settings::Settings as _;
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, Disclosure};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::tool_use::{ToolUse, ToolUseStatus};
|
||||
use crate::ui::ContextPill;
|
||||
|
||||
pub struct ActiveThread {
|
||||
@@ -28,6 +29,7 @@ pub struct ActiveThread {
|
||||
messages: Vec<MessageId>,
|
||||
list_state: ListState,
|
||||
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
|
||||
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
|
||||
last_error: Option<ThreadError>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -55,6 +57,7 @@ impl ActiveThread {
|
||||
thread: thread.clone(),
|
||||
messages: Vec::new(),
|
||||
rendered_messages_by_id: HashMap::default(),
|
||||
expanded_tool_uses: HashMap::default(),
|
||||
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||
let this = cx.entity().downgrade();
|
||||
move |ix, _: &mut Window, cx: &mut App| {
|
||||
@@ -251,17 +254,29 @@ impl ActiveThread {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_tool_output(
|
||||
tool_use.assistant_message_id,
|
||||
tool_use.id.clone(),
|
||||
task,
|
||||
cx,
|
||||
);
|
||||
thread.insert_tool_output(tool_use.id.clone(), task, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ThreadEvent::ToolFinished { .. } => {
|
||||
let all_tools_finished = self
|
||||
.thread
|
||||
.read(cx)
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.all(|tool_use| tool_use.status.is_error());
|
||||
if all_tools_finished {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
if let Some(model) = model_registry.active_model() {
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
// Insert an empty user message to contain the tool results.
|
||||
thread.insert_user_message("", Vec::new(), cx);
|
||||
thread.send_to_model(model, RequestKind::Chat, true, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ThreadEvent::ToolFinished { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +291,17 @@ impl ActiveThread {
|
||||
};
|
||||
|
||||
let context = self.thread.read(cx).context_for_message(message_id);
|
||||
let tool_uses = self.thread.read(cx).tool_uses_for_message(message_id);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
// Don't render user messages that are just there for returning tool results.
|
||||
if message.role == Role::User
|
||||
&& message.text.is_empty()
|
||||
&& self.thread.read(cx).message_has_tool_results(message_id)
|
||||
{
|
||||
return Empty.into_any();
|
||||
}
|
||||
|
||||
let message_content = v_flex()
|
||||
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
@@ -332,7 +356,22 @@ impl ActiveThread {
|
||||
)
|
||||
.child(message_content),
|
||||
),
|
||||
Role::Assistant => div().id(("message-container", ix)).child(message_content),
|
||||
Role::Assistant => div()
|
||||
.id(("message-container", ix))
|
||||
.child(message_content)
|
||||
.map(|parent| {
|
||||
if tool_uses.is_empty() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent.child(
|
||||
v_flex().children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, cx)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
@@ -343,6 +382,102 @@ impl ActiveThread {
|
||||
|
||||
styled_message.into_any()
|
||||
}
|
||||
|
||||
fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_open = self
|
||||
.expanded_tool_uses
|
||||
.get(&tool_use.id)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
div().px_2p5().child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.py_0p5()
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.bg(cx.theme().colors().editor_foreground.opacity(0.02))
|
||||
.when(is_open, |element| element.border_b_1().rounded_t(px(6.)))
|
||||
.when(!is_open, |element| element.rounded(px(6.)))
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Disclosure::new("tool-use-disclosure", is_open).on_click(
|
||||
cx.listener({
|
||||
let tool_use_id = tool_use.id.clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_open = this
|
||||
.expanded_tool_uses
|
||||
.entry(tool_use_id.clone())
|
||||
.or_insert(false);
|
||||
|
||||
*is_open = !*is_open;
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(Label::new(tool_use.name)),
|
||||
)
|
||||
.child(
|
||||
Label::new(match tool_use.status {
|
||||
ToolUseStatus::Pending => "Pending",
|
||||
ToolUseStatus::Running => "Running",
|
||||
ToolUseStatus::Finished(_) => "Finished",
|
||||
ToolUseStatus::Error(_) => "Error",
|
||||
})
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
if !is_open {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent.child(
|
||||
v_flex()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Label::new("Input:"))
|
||||
.child(Label::new(
|
||||
serde_json::to_string_pretty(&tool_use.input)
|
||||
.unwrap_or_default(),
|
||||
)),
|
||||
)
|
||||
.map(|parent| match tool_use.status {
|
||||
ToolUseStatus::Finished(output) => parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Label::new("Result:"))
|
||||
.child(Label::new(output)),
|
||||
),
|
||||
ToolUseStatus::Error(err) => parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Label::new("Error:"))
|
||||
.child(Label::new(err)),
|
||||
),
|
||||
ToolUseStatus::Pending | ToolUseStatus::Running => parent,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ActiveThread {
|
||||
|
||||
@@ -16,6 +16,7 @@ mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
mod thread_history;
|
||||
mod thread_store;
|
||||
mod tool_use;
|
||||
mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -38,7 +39,6 @@ actions!(
|
||||
NewThread,
|
||||
NewPromptEditor,
|
||||
ToggleContextPicker,
|
||||
ToggleModelSelector,
|
||||
RemoveAllContext,
|
||||
OpenHistory,
|
||||
OpenConfiguration,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
|
||||
use gpui::{Action, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
|
||||
use zed_actions::assistant::DeployPromptLibrary;
|
||||
@@ -158,8 +158,16 @@ impl Render for AssistantConfiguration {
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.gap_1()
|
||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
||||
.gap_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
||||
.child(
|
||||
Label::new("Create reusable prompts and tag which ones you want sent in every LLM interaction.")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("open-prompt-library", "Open Prompt Library")
|
||||
.style(ButtonStyle::Filled)
|
||||
@@ -168,8 +176,8 @@ impl Render for AssistantConfiguration {
|
||||
.icon(IconName::Book)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, _window, cx| {
|
||||
cx.dispatch_action(&DeployPromptLibrary)
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(DeployPromptLibrary.boxed_clone(), cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
use assistant_settings::AssistantSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Entity, FocusHandle, SharedString};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use gpui::{Entity, FocusHandle};
|
||||
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
||||
|
||||
use crate::ToggleModelSelector;
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct AssistantModelSelector {
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
pub selector: Entity<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl AssistantModelSelector {
|
||||
pub(crate) fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -38,50 +33,14 @@ impl AssistantModelSelector {
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
menu_handle,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantModelSelector {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
_ => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
AssistantLanguageModelSelector::new(self.focus_handle.clone(), self.selector.clone())
|
||||
.render(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use client::zed_urls;
|
||||
use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
|
||||
prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
@@ -431,7 +431,7 @@ impl AssistantPanel {
|
||||
active_provider.id() != provider.id()
|
||||
})
|
||||
{
|
||||
if let Some(model) = provider.provided_models(cx).first().cloned() {
|
||||
if let Some(model) = provider.default_model(cx) {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
@@ -458,6 +458,12 @@ impl AssistantPanel {
|
||||
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
|
||||
self.context_editor.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
|
||||
self.context_store
|
||||
.update(cx, |this, cx| this.delete_local_context(path, cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AssistantPanel {
|
||||
@@ -590,7 +596,6 @@ impl AssistantPanel {
|
||||
|
||||
h_flex()
|
||||
.id("assistant-toolbar")
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.h(Tab::container_height(cx))
|
||||
.flex_none()
|
||||
.justify_between()
|
||||
@@ -598,72 +603,86 @@ impl AssistantPanel {
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
div()
|
||||
.id("title")
|
||||
.overflow_x_scroll()
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.child(Label::new(title).text_ellipsis()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(Label::new(title))
|
||||
.h_full()
|
||||
.pl_2()
|
||||
.gap_2()
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.children(if matches!(self.active_view, ActiveView::PromptEditor) {
|
||||
self.context_editor
|
||||
.as_ref()
|
||||
.and_then(|editor| render_remaining_tokens(editor, cx))
|
||||
} else {
|
||||
None
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.pl_1p5()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
})
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("New…"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.action("New Thread", NewThread.boxed_clone())
|
||||
.action("New Prompt Editor", NewPromptEditor.boxed_clone())
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
h_flex()
|
||||
.h_full()
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("New…"),
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("Assistant Settings"))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
|
||||
}),
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(
|
||||
window,
|
||||
cx,
|
||||
|menu, _window, _cx| {
|
||||
menu.action("New Thread", NewThread.boxed_clone())
|
||||
.action(
|
||||
"New Prompt Editor",
|
||||
NewPromptEditor.boxed_clone(),
|
||||
)
|
||||
},
|
||||
))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("Assistant Settings"))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -705,12 +724,11 @@ impl AssistantPanel {
|
||||
) -> impl IntoElement {
|
||||
let recent_history = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.recent_entries(3, cx));
|
||||
.update(cx, |this, cx| this.recent_entries(6, cx));
|
||||
|
||||
let create_welcome_heading = || {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_center()
|
||||
.child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
|
||||
};
|
||||
|
||||
@@ -718,36 +736,27 @@ impl AssistantPanel {
|
||||
let no_error = configuration_error.is_none();
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
v_flex().w_full().child(
|
||||
svg()
|
||||
.path("icons/logo_96.svg")
|
||||
.text_color(cx.theme().colors().text)
|
||||
.w(px(40.))
|
||||
.h(px(40.))
|
||||
.mx_auto()
|
||||
.mb_4(),
|
||||
),
|
||||
)
|
||||
.p_1p5()
|
||||
.size_full()
|
||||
.justify_end()
|
||||
.gap_1()
|
||||
.map(|parent| {
|
||||
match configuration_error {
|
||||
Some(ConfigurationError::ProviderNotAuthenticated)
|
||||
| Some(ConfigurationError::NoProvider) => {
|
||||
parent.child(
|
||||
v_flex()
|
||||
.px_1p5()
|
||||
.gap_0p5()
|
||||
.child(create_welcome_heading())
|
||||
.child(
|
||||
h_flex().mb_2().w_full().justify_center().child(
|
||||
Label::new(
|
||||
"To start using the assistant, configure at least one LLM provider.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
Label::new(
|
||||
"To start using the assistant, configure at least one LLM provider.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
h_flex().mt_1().w_full().child(
|
||||
Button::new("open-configuration", "Configure a Provider")
|
||||
.size(ButtonSize::Compact)
|
||||
.icon(Some(IconName::Sliders))
|
||||
@@ -761,7 +770,7 @@ impl AssistantPanel {
|
||||
)
|
||||
}
|
||||
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
|
||||
.child(v_flex().gap_0p5().child(create_welcome_heading()).children(
|
||||
.child(v_flex().px_1p5().gap_0p5().child(create_welcome_heading()).children(
|
||||
provider.render_accept_terms(
|
||||
LanguageModelProviderTosView::ThreadEmptyState,
|
||||
cx,
|
||||
@@ -772,21 +781,40 @@ impl AssistantPanel {
|
||||
})
|
||||
.when(recent_history.is_empty() && no_error, |parent| {
|
||||
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
||||
),
|
||||
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
||||
))
|
||||
})
|
||||
.when(!recent_history.is_empty(), |parent| {
|
||||
parent
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Label::new("Recent Threads:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
h_flex()
|
||||
.pl_1p5()
|
||||
.pb_1()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Label::new("Past Interactions")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("view-history", "View All")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
.child(v_flex().gap_1().children(
|
||||
recent_history.into_iter().map(|entry| {
|
||||
// TODO: Add keyboard navigation.
|
||||
match entry {
|
||||
@@ -801,22 +829,6 @@ impl AssistantPanel {
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Button::new("view-all-past-threads", "View All Past Threads")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,9 @@ use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, Stre
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{line_diff, Buffer, IndentKind, Point, TransactionId};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role,
|
||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||
};
|
||||
use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use prompt_library::PromptBuilder;
|
||||
|
||||
@@ -24,8 +24,7 @@ use gpui::{
|
||||
UpdateGlobal, WeakEntity, Window,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_models::report_assistant_event;
|
||||
use language_model::{report_assistant_event, LanguageModelRegistry};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker};
|
||||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use editor::{
|
||||
@@ -20,7 +20,6 @@ use gpui::{
|
||||
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
|
||||
};
|
||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use parking_lot::Mutex;
|
||||
use settings::Settings;
|
||||
use std::cmp;
|
||||
@@ -40,7 +39,6 @@ pub struct PromptEditor<T> {
|
||||
context_strip: Entity<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_history_ix: Option<usize>,
|
||||
@@ -104,7 +102,12 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.items_start()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(|this, action, window, cx| {
|
||||
let selector = this.model_selector.read(cx).selector.clone();
|
||||
selector.update(cx, |selector, cx| {
|
||||
selector.toggle_model_selector(action, window, cx);
|
||||
})
|
||||
}))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
@@ -347,15 +350,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
self.context_picker_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.model_selector_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
pub fn remove_all_context(
|
||||
&mut self,
|
||||
_: &RemoveAllContext,
|
||||
@@ -864,7 +858,6 @@ impl PromptEditor<BufferCodegen> {
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
@@ -888,15 +881,8 @@ impl PromptEditor<BufferCodegen> {
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
@@ -1020,7 +1006,6 @@ impl PromptEditor<TerminalCodegen> {
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
@@ -1044,15 +1029,8 @@ impl PromptEditor<TerminalCodegen> {
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
|
||||
@@ -7,16 +7,17 @@ use gpui::{
|
||||
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
|
||||
TextStyle, WeakEntity,
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use rope::Point;
|
||||
use settings::Settings;
|
||||
use std::time::Duration;
|
||||
use text::Bias;
|
||||
use theme::{get_ui_font_size, ThemeSettings};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
|
||||
prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch,
|
||||
TintColor, Tooltip,
|
||||
};
|
||||
use vim_mode_setting::VimModeSetting;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
@@ -25,7 +26,7 @@ use crate::context_store::{refresh_context_store_text, ContextStore};
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker};
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Entity<Thread>,
|
||||
@@ -36,7 +37,6 @@ pub struct MessageEditor {
|
||||
inline_context_picker: Entity<ContextPicker>,
|
||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
use_tools: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -53,7 +53,6 @@ impl MessageEditor {
|
||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(10, window, cx);
|
||||
@@ -106,30 +105,13 @@ impl MessageEditor {
|
||||
context_picker_menu_handle,
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
model_selector: cx
|
||||
.new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)),
|
||||
use_tools: false,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.model_selector_menu_handle.toggle(window, cx)
|
||||
}
|
||||
|
||||
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.use_tools = !self.use_tools;
|
||||
cx.notify();
|
||||
@@ -205,22 +187,7 @@ impl MessageEditor {
|
||||
.update(&mut cx, |thread, cx| {
|
||||
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
|
||||
thread.insert_user_message(user_message, context, cx);
|
||||
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)
|
||||
thread.send_to_model(model, request_kind, use_tools, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -309,7 +276,6 @@ impl Render for MessageEditor {
|
||||
let inline_context_picker = self.inline_context_picker.clone();
|
||||
let bg_color = cx.theme().colors().editor_background;
|
||||
let is_streaming_completion = self.thread.read(cx).is_streaming();
|
||||
let button_width = px(64.);
|
||||
let is_model_selected = self.is_model_selected(cx);
|
||||
let is_editor_empty = self.is_editor_empty(cx);
|
||||
let submit_label_color = if is_editor_empty {
|
||||
@@ -318,10 +284,25 @@ impl Render for MessageEditor {
|
||||
Color::Default
|
||||
};
|
||||
|
||||
let vim_mode_enabled = VimModeSetting::get_global(cx).0;
|
||||
let platform = PlatformStyle::platform();
|
||||
let linux = platform == PlatformStyle::Linux;
|
||||
let windows = platform == PlatformStyle::Windows;
|
||||
let button_width = if linux || windows || vim_mode_enabled {
|
||||
px(92.)
|
||||
} else {
|
||||
px(64.)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.key_context("MessageEditor")
|
||||
.on_action(cx.listener(Self::chat))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(|this, action, window, cx| {
|
||||
let selector = this.model_selector.read(cx).selector.clone();
|
||||
selector.update(cx, |this, cx| {
|
||||
this.toggle_model_selector(action, window, cx);
|
||||
})
|
||||
}))
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::remove_all_context))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
@@ -333,7 +314,7 @@ impl Render for MessageEditor {
|
||||
.child(self.context_strip.clone())
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.gap_5()
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
@@ -369,7 +350,7 @@ impl Render for MessageEditor {
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: (-get_ui_font_size(cx) * 2) - px(4.0),
|
||||
y: (-ThemeSettings::get_global(cx).ui_font_size(cx) * 2) - px(4.0),
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
|
||||
@@ -2,8 +2,7 @@ use crate::inline_prompt_editor::CodegenStatus;
|
||||
use client::telemetry::Telemetry;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequest};
|
||||
use language_models::report_assistant_event;
|
||||
use language_model::{report_assistant_event, LanguageModelRegistry, LanguageModelRequest};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
@@ -156,7 +155,10 @@ pub enum CodegenEvent {
|
||||
Finished,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub const CLEAR_INPUT: &str = "\x15";
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const CLEAR_INPUT: &str = "\x03";
|
||||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
|
||||
@@ -13,9 +13,9 @@ use fs::Fs;
|
||||
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_models::report_assistant_event;
|
||||
use prompt_library::PromptBuilder;
|
||||
use std::sync::Arc;
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
|
||||
@@ -4,25 +4,26 @@ use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{App, Context, EventEmitter, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId,
|
||||
MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason,
|
||||
};
|
||||
use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
||||
use crate::thread_store::SavedThread;
|
||||
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RequestKind {
|
||||
Chat,
|
||||
/// Used when summarizing a thread.
|
||||
Summarize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
||||
@@ -41,7 +42,7 @@ impl std::fmt::Display for ThreadId {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct MessageId(usize);
|
||||
pub struct MessageId(pub(crate) usize);
|
||||
|
||||
impl MessageId {
|
||||
fn post_inc(&mut self) -> Self {
|
||||
@@ -70,9 +71,7 @@ pub struct Thread {
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
tool_uses_by_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||
tool_results_by_message: HashMap<MessageId, Vec<LanguageModelToolResult>>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
tool_use: ToolUseState,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
@@ -89,9 +88,7 @@ impl Thread {
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
tools,
|
||||
tool_uses_by_message: HashMap::default(),
|
||||
tool_results_by_message: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
tool_use: ToolUseState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +120,7 @@ impl Thread {
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
tools,
|
||||
tool_uses_by_message: HashMap::default(),
|
||||
tool_results_by_message: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
tool_use: ToolUseState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +182,15 @@ impl Thread {
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
self.tool_use.pending_tool_uses()
|
||||
}
|
||||
|
||||
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
|
||||
self.tool_use.tool_uses_for_message(id)
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.tool_use.message_has_tool_results(message_id)
|
||||
}
|
||||
|
||||
pub fn insert_user_message(
|
||||
@@ -241,9 +244,34 @@ impl Thread {
|
||||
text
|
||||
}
|
||||
|
||||
pub fn send_to_model(
|
||||
&mut self,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
request_kind: RequestKind,
|
||||
use_tools: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut request = self.to_completion_request(request_kind, cx);
|
||||
|
||||
if use_tools {
|
||||
request.tools = self
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
self.stream_completion(request, model, cx);
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
&self,
|
||||
_request_kind: RequestKind,
|
||||
request_kind: RequestKind,
|
||||
_cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
let mut request = LanguageModelRequest {
|
||||
@@ -265,12 +293,13 @@ impl Thread {
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
};
|
||||
|
||||
if let Some(tool_results) = self.tool_results_by_message.get(&message.id) {
|
||||
for tool_result in tool_results {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolResult(tool_result.clone()));
|
||||
match request_kind {
|
||||
RequestKind::Chat => {
|
||||
self.tool_use
|
||||
.attach_tool_results(message.id, &mut request_message);
|
||||
}
|
||||
RequestKind::Summarize => {
|
||||
// We don't care about tool use during summarization.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,11 +309,13 @@ impl Thread {
|
||||
.push(MessageContent::Text(message.text.clone()));
|
||||
}
|
||||
|
||||
if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) {
|
||||
for tool_use in tool_uses {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolUse(tool_use.clone()));
|
||||
match request_kind {
|
||||
RequestKind::Chat => {
|
||||
self.tool_use
|
||||
.attach_tool_uses(message.id, &mut request_message);
|
||||
}
|
||||
RequestKind::Summarize => {
|
||||
// We don't care about tool use during summarization.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,21 +391,8 @@ impl Thread {
|
||||
.rfind(|message| message.role == Role::Assistant)
|
||||
{
|
||||
thread
|
||||
.tool_uses_by_message
|
||||
.entry(last_assistant_message.id)
|
||||
.or_default()
|
||||
.push(tool_use.clone());
|
||||
|
||||
thread.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
assistant_message_id: last_assistant_message.id,
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
},
|
||||
);
|
||||
.tool_use
|
||||
.request_tool_use(last_assistant_message.id, tool_use);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,7 +469,7 @@ impl Thread {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut request = self.to_completion_request(RequestKind::Chat, cx);
|
||||
let mut request = self.to_completion_request(RequestKind::Summarize, cx);
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
@@ -494,7 +512,6 @@ impl Thread {
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
assistant_message_id: MessageId,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -505,50 +522,18 @@ impl Thread {
|
||||
let output = output.await;
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| {
|
||||
// The tool use was requested by an Assistant message,
|
||||
// so we want to attach the tool results to the next
|
||||
// user message.
|
||||
let next_user_message = MessageId(assistant_message_id.0 + 1);
|
||||
thread
|
||||
.tool_use
|
||||
.insert_tool_output(tool_use_id.clone(), output);
|
||||
|
||||
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 });
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: insert_output_task.shared(),
|
||||
};
|
||||
}
|
||||
self.tool_use
|
||||
.run_pending_tool(tool_use_id, insert_output_task);
|
||||
}
|
||||
|
||||
/// Cancels the last pending completion, if there are any pending.
|
||||
@@ -590,26 +575,3 @@ struct PendingCompletion {
|
||||
id: usize,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
/// The ID of the Assistant message in which the tool use was requested.
|
||||
pub assistant_message_id: MessageId,
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PendingToolUseStatus {
|
||||
Idle,
|
||||
Running { _task: Shared<Task<()>> },
|
||||
Error(#[allow(unused)] String),
|
||||
}
|
||||
|
||||
impl PendingToolUseStatus {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Idle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,13 @@ impl ThreadHistory {
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
HistoryEntry::Context(_context) => {}
|
||||
HistoryEntry::Context(context) => {
|
||||
self.assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_context(context.path.clone(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -248,18 +254,28 @@ impl RenderOnce for PastThread {
|
||||
);
|
||||
|
||||
ListItem::new(SharedString::from(self.thread.id.to_string()))
|
||||
.outlined()
|
||||
.rounded()
|
||||
.toggle_state(self.selected)
|
||||
.start_slot(
|
||||
Icon::new(IconName::MessageCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||
.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("Thread")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size(px(3.))
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().text_disabled),
|
||||
)
|
||||
.child(
|
||||
Label::new(thread_timestamp)
|
||||
.color(Color::Muted)
|
||||
@@ -334,21 +350,50 @@ impl RenderOnce for PastContext {
|
||||
ListItem::new(SharedString::from(
|
||||
self.context.path.to_string_lossy().to_string(),
|
||||
))
|
||||
.outlined()
|
||||
.rounded()
|
||||
.toggle_state(self.selected)
|
||||
.start_slot(
|
||||
Icon::new(IconName::Code)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||
.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(context_timestamp)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
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();
|
||||
|
||||
221
crates/assistant2/src/tool_use.rs
Normal file
221
crates/assistant2/src/tool_use.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
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(_))
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
open_ai.workspace = true
|
||||
|
||||
@@ -19,13 +19,10 @@ use gpui::{
|
||||
};
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||
};
|
||||
use language_models::{
|
||||
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
|
||||
report_assistant_event,
|
||||
report_assistant_event, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelCompletionEvent, LanguageModelImage, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolUseId, MaxMonthlySpendReachedError,
|
||||
MessageContent, PaymentRequiredError, Role, StopReason,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::contexts_dir;
|
||||
|
||||
@@ -29,19 +29,22 @@ use gpui::{
|
||||
WeakEntity,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
|
||||
use language::{
|
||||
language_settings::{all_language_settings, SoftWrap},
|
||||
BufferSnapshot, LspAdapterDelegate, ToOffset,
|
||||
};
|
||||
use language_model::{
|
||||
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
|
||||
Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::Picker;
|
||||
use project::lsp_store::LocalLspAdapterDelegate;
|
||||
use project::{Project, Worktree};
|
||||
use rope::Point;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||
use text::SelectionGoal;
|
||||
use ui::{
|
||||
@@ -77,7 +80,6 @@ actions!(
|
||||
InsertIntoEditor,
|
||||
QuoteSelection,
|
||||
Split,
|
||||
ToggleModelSelector,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -194,7 +196,6 @@ pub struct ContextEditor {
|
||||
// context editor, we keep a reference here.
|
||||
dragged_file_worktrees: Vec<Entity<Worktree>>,
|
||||
language_model_selector: Entity<LanguageModelSelector>,
|
||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TAB_TITLE: &str = "New Chat";
|
||||
@@ -230,6 +231,13 @@ impl ContextEditor {
|
||||
editor.set_completion_provider(Some(Box::new(completion_provider)));
|
||||
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
|
||||
editor.set_collaboration_hub(Box::new(project.clone()));
|
||||
|
||||
let show_edit_predictions = all_language_settings(None, cx)
|
||||
.edit_predictions
|
||||
.enabled_in_assistant;
|
||||
|
||||
editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
|
||||
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -238,6 +246,7 @@ impl ContextEditor {
|
||||
cx.subscribe_in(&context, window, Self::handle_context_event),
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_event),
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
|
||||
];
|
||||
|
||||
let fs_clone = fs.clone();
|
||||
@@ -255,7 +264,6 @@ impl ContextEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let language_model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
let sections = context.read(cx).slash_command_output_sections().to_vec();
|
||||
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
|
||||
let slash_commands = context.read(cx).slash_commands().clone();
|
||||
@@ -281,7 +289,6 @@ impl ContextEditor {
|
||||
slash_menu_handle: Default::default(),
|
||||
dragged_file_worktrees: Vec::new(),
|
||||
language_model_selector,
|
||||
language_model_selector_menu_handle,
|
||||
};
|
||||
this.update_message_headers(cx);
|
||||
this.update_image_blocks(cx);
|
||||
@@ -290,6 +297,16 @@ impl ContextEditor {
|
||||
this
|
||||
}
|
||||
|
||||
fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let show_edit_predictions = all_language_settings(None, cx)
|
||||
.edit_predictions
|
||||
.enabled_in_assistant;
|
||||
|
||||
editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Entity<AssistantContext> {
|
||||
&self.context
|
||||
}
|
||||
@@ -1087,7 +1104,7 @@ impl ContextEditor {
|
||||
patch: AssistantPatch,
|
||||
mut cx: AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let project = this.update(&mut cx, |this, _| this.project.clone())?;
|
||||
let project = this.read_with(&cx, |this, _| this.project.clone())?;
|
||||
let resolved_patch = patch.resolve(project.clone(), &mut cx).await;
|
||||
|
||||
let editor = cx.new_window_entity(|window, cx| {
|
||||
@@ -1112,7 +1129,7 @@ impl ContextEditor {
|
||||
editor
|
||||
})?;
|
||||
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.update(&mut cx, |this, _| {
|
||||
if let Some(patch_state) = this.patches.get_mut(&patch.range) {
|
||||
patch_state.editor = Some(PatchEditorState {
|
||||
editor: editor.downgrade(),
|
||||
@@ -1120,19 +1137,12 @@ impl ContextEditor {
|
||||
});
|
||||
patch_state.update_task.take();
|
||||
}
|
||||
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(editor.clone()),
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.log_err();
|
||||
})?;
|
||||
this.read_with(&cx, |this, _| this.workspace.clone())?
|
||||
.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, window, cx)
|
||||
})
|
||||
.log_err();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1234,8 +1244,8 @@ impl ContextEditor {
|
||||
.px_1()
|
||||
.mr_0p5()
|
||||
.border_1()
|
||||
.border_color(theme::color_alpha(colors.border_variant, 0.6))
|
||||
.bg(theme::color_alpha(colors.element_background, 0.6))
|
||||
.border_color(colors.border_variant.alpha(0.6))
|
||||
.bg(colors.element_background.alpha(0.6))
|
||||
.child("esc"),
|
||||
)
|
||||
.child("to cancel")
|
||||
@@ -1514,15 +1524,11 @@ impl ContextEditor {
|
||||
|
||||
(!text.is_empty()).then_some((text, true))
|
||||
} else {
|
||||
let anchor = context_editor.selections.newest_anchor();
|
||||
let text = context_editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.read(cx)
|
||||
.text_for_range(anchor.range())
|
||||
.collect::<String>();
|
||||
let selection = context_editor.selections.newest_adjusted(cx);
|
||||
let buffer = context_editor.buffer().read(cx).snapshot(cx);
|
||||
let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
|
||||
|
||||
(!text.is_empty()).then_some((text, false))
|
||||
(!selected_text.is_empty()).then_some((selected_text, false))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1777,23 +1783,16 @@ impl ContextEditor {
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
|
||||
let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
|
||||
let mut selection = editor.selections.newest::<Point>(cx);
|
||||
let (selection, creases) = self.editor.update(cx, |editor, cx| {
|
||||
let mut selection = editor.selections.newest_adjusted(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);
|
||||
|
||||
(
|
||||
snapshot.clone(),
|
||||
selection.clone(),
|
||||
selection.map(|point| snapshot.point_to_offset(point)),
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map
|
||||
.snapshot(cx)
|
||||
@@ -1833,7 +1832,6 @@ impl ContextEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let selection = selection.map(|point| snapshot.point_to_offset(point));
|
||||
let context = self.context.read(cx);
|
||||
|
||||
let mut text = String::new();
|
||||
@@ -2043,15 +2041,6 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.language_model_selector_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
@@ -2399,46 +2388,6 @@ impl ContextEditor {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
None => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomLeft,
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let last_error = self.last_error.as_ref()?;
|
||||
|
||||
@@ -2883,6 +2832,7 @@ impl Render for ContextEditor {
|
||||
None
|
||||
};
|
||||
|
||||
let language_model_selector = self.language_model_selector.clone();
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
.capture_action(cx.listener(ContextEditor::cancel))
|
||||
@@ -2895,7 +2845,11 @@ impl Render for ContextEditor {
|
||||
.on_action(cx.listener(ContextEditor::edit))
|
||||
.on_action(cx.listener(ContextEditor::assist))
|
||||
.on_action(cx.listener(ContextEditor::split))
|
||||
.on_action(cx.listener(ContextEditor::toggle_model_selector))
|
||||
.on_action(move |action, window, cx| {
|
||||
language_model_selector.update(cx, |this, cx| {
|
||||
this.toggle_model_selector(action, window, cx);
|
||||
})
|
||||
})
|
||||
.size_full()
|
||||
.children(self.render_notice(cx))
|
||||
.child(
|
||||
@@ -2933,11 +2887,14 @@ impl Render for ContextEditor {
|
||||
.gap_1()
|
||||
.child(self.render_inject_context_menu(cx))
|
||||
.child(ui::Divider::vertical())
|
||||
.child(
|
||||
div()
|
||||
.pl_0p5()
|
||||
.child(self.render_language_model_selector(cx)),
|
||||
),
|
||||
.child(div().pl_0p5().child({
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
AssistantLanguageModelSelector::new(
|
||||
focus_handle,
|
||||
self.language_model_selector.clone(),
|
||||
)
|
||||
.render(window, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -9,7 +9,7 @@ use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use context_server::manager::ContextServerManager;
|
||||
use context_server::ContextServerFactoryRegistry;
|
||||
use fs::Fs;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
|
||||
@@ -475,6 +475,38 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_local_context(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
fs.remove_file(
|
||||
&path,
|
||||
RemoveOptions {
|
||||
recursive: false,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.contexts.retain(|context| {
|
||||
context
|
||||
.upgrade()
|
||||
.and_then(|context| context.read(cx).path())
|
||||
!= Some(&path)
|
||||
});
|
||||
this.contexts_metadata
|
||||
.retain(|context| context.path != path);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
|
||||
@@ -359,6 +359,7 @@ fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema:
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
@@ -512,7 +513,7 @@ mod tests {
|
||||
AssistantSettings::get_global(cx).default_model,
|
||||
LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
model: "claude-3-5-sonnet".into(),
|
||||
model: "claude-3-5-sonnet-latest".into(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -21,11 +21,11 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"insert default prompt".into()
|
||||
"Insert default prompt".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Insert Default Prompt".into()
|
||||
self.description()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub enum Timezone {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FileToolInput {
|
||||
pub struct NowToolInput {
|
||||
/// The timezone to use for the datetime.
|
||||
timezone: Timezone,
|
||||
}
|
||||
@@ -34,7 +34,7 @@ impl Tool for NowTool {
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
let schema = schemars::schema_for!(FileToolInput);
|
||||
let schema = schemars::schema_for!(NowToolInput);
|
||||
serde_json::to_value(&schema).unwrap()
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl Tool for NowTool {
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
let input: FileToolInput = match serde_json::from_value(input) {
|
||||
let input: NowToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
};
|
||||
|
||||
22
crates/aws_http_client/Cargo.toml
Normal file
22
crates/aws_http_client/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[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"] }
|
||||
1
crates/aws_http_client/LICENSE-GPL
Symbolic link
1
crates/aws_http_client/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
118
crates/aws_http_client/src/aws_http_client.rs
Normal file
118
crates/aws_http_client/src/aws_http_client.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
28
crates/bedrock/Cargo.toml
Normal file
28
crates/bedrock/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[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"] }
|
||||
1
crates/bedrock/LICENSE-GPL
Symbolic link
1
crates/bedrock/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
166
crates/bedrock/src/bedrock.rs
Normal file
166
crates/bedrock/src/bedrock.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
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),
|
||||
}
|
||||
207
crates/bedrock/src/models.rs
Normal file
207
crates/bedrock/src/models.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ impl Render for Breadcrumbs {
|
||||
}
|
||||
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())
|
||||
.into_any()
|
||||
});
|
||||
|
||||
@@ -3,7 +3,8 @@ use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use rope::Rope;
|
||||
use std::{cmp, future::Future, iter, ops::Range, sync::Arc};
|
||||
use std::cmp::Ordering;
|
||||
use std::{future::Future, iter, ops::Range, sync::Arc};
|
||||
use sum_tree::SumTree;
|
||||
use text::ToOffset as _;
|
||||
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point};
|
||||
@@ -68,7 +69,6 @@ pub struct DiffHunk {
|
||||
/// The range in the buffer's diff base text to which this hunk corresponds.
|
||||
pub diff_base_byte_range: Range<usize>,
|
||||
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.
|
||||
@@ -109,13 +109,18 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, DiffHunkSummary, DiffHunkSummary> for Anchor {
|
||||
fn cmp(
|
||||
&self,
|
||||
cursor_location: &DiffHunkSummary,
|
||||
buffer: &text::BufferSnapshot,
|
||||
) -> cmp::Ordering {
|
||||
self.cmp(&cursor_location.buffer_range.end, buffer)
|
||||
impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
|
||||
fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
|
||||
if self
|
||||
.cmp(&cursor_location.buffer_range.start, buffer)
|
||||
.is_lt()
|
||||
{
|
||||
Ordering::Less
|
||||
} else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,97 +176,110 @@ impl BufferDiffSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_range_to_unchanged_diff_base_range(
|
||||
&self,
|
||||
buffer_range: Range<Anchor>,
|
||||
buffer: &text::BufferSnapshot,
|
||||
) -> Option<Range<usize>> {
|
||||
let mut hunks = self.inner.hunks.iter();
|
||||
let mut start = 0;
|
||||
let mut pos = buffer.anchor_before(0);
|
||||
while let Some(hunk) = hunks.next() {
|
||||
assert!(buffer_range.start.cmp(&pos, buffer).is_ge());
|
||||
assert!(hunk.buffer_range.start.cmp(&pos, buffer).is_ge());
|
||||
if hunk
|
||||
.buffer_range
|
||||
.start
|
||||
.cmp(&buffer_range.end, buffer)
|
||||
.is_ge()
|
||||
{
|
||||
// target buffer range is contained in the unchanged stretch leading up to this next hunk,
|
||||
// so do a final adjustment based on that
|
||||
break;
|
||||
}
|
||||
|
||||
// if the target buffer range intersects this hunk at all, no dice
|
||||
if buffer_range
|
||||
.start
|
||||
.cmp(&hunk.buffer_range.end, buffer)
|
||||
.is_lt()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
start += hunk.buffer_range.start.to_offset(buffer) - pos.to_offset(buffer);
|
||||
start += hunk.diff_base_byte_range.end - hunk.diff_base_byte_range.start;
|
||||
pos = hunk.buffer_range.end;
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn secondary_edits_for_stage_or_unstage(
|
||||
pub fn new_secondary_text_for_stage_or_unstage(
|
||||
&self,
|
||||
stage: bool,
|
||||
hunks: impl Iterator<Item = (Range<usize>, Option<Range<usize>>, Range<Anchor>)>,
|
||||
hunks: impl Iterator<Item = (Range<Anchor>, Range<usize>)>,
|
||||
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");
|
||||
let mut replacement_text = String::new();
|
||||
let Some(index_byte_range) = secondary_diff_base_byte_range.clone() else {
|
||||
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);
|
||||
cx: &mut App,
|
||||
) -> Option<Rope> {
|
||||
let secondary_diff = self.secondary_diff()?;
|
||||
let head_text = self.base_text().map(|text| text.as_rope().clone());
|
||||
let index_text = secondary_diff
|
||||
.base_text()
|
||||
.map(|text| text.as_rope().clone());
|
||||
let (index_text, head_text) = match (index_text, head_text) {
|
||||
(Some(index_text), Some(head_text)) => (index_text, head_text),
|
||||
// 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)
|
||||
}
|
||||
(index_byte_range, replacement_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
|
||||
.cmp(&buffer_range.end, buffer)
|
||||
.is_le()
|
||||
}) {
|
||||
let secondary_hunk_offset_range = secondary_hunk.buffer_range.to_offset(buffer);
|
||||
prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;
|
||||
prev_secondary_hunk_buffer_offset = secondary_hunk_offset_range.end;
|
||||
|
||||
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
|
||||
.end
|
||||
.saturating_sub(prev_secondary_hunk_buffer_offset);
|
||||
let secondary_base_text_end = prev_secondary_hunk_base_text_offset + end_overshoot;
|
||||
|
||||
let secondary_base_text_range = secondary_base_text_start..secondary_base_text_end;
|
||||
buffer_offset_range.end = buffer_offset_range
|
||||
.end
|
||||
.max(prev_secondary_hunk_buffer_offset);
|
||||
|
||||
let replacement_text = if stage {
|
||||
log::debug!("staging");
|
||||
buffer
|
||||
.text_for_range(buffer_offset_range)
|
||||
.collect::<String>()
|
||||
} else {
|
||||
log::debug!("unstaging");
|
||||
let mut replacement_text = String::new();
|
||||
let Some(index_byte_range) = secondary_diff
|
||||
.buffer_range_to_unchanged_diff_base_range(buffer_range.clone(), &buffer)
|
||||
else {
|
||||
log::debug!("not an unstageable hunk");
|
||||
continue;
|
||||
};
|
||||
for chunk in head_base.chunks_in_range(diff_base_byte_range.clone()) {
|
||||
replacement_text.push_str(chunk);
|
||||
}
|
||||
(index_byte_range, replacement_text)
|
||||
head_text
|
||||
.chunks_in_range(diff_base_byte_range.clone())
|
||||
.collect::<String>()
|
||||
};
|
||||
edits.push((index_byte_range, replacement_text));
|
||||
edits.push((secondary_base_text_range, replacement_text));
|
||||
}
|
||||
log::debug!("edits: {edits:?}");
|
||||
edits
|
||||
|
||||
let buffer = cx.new(|cx| {
|
||||
language::Buffer::local_normalized(index_text, text::LineEnding::default(), cx)
|
||||
});
|
||||
let new_text = buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
buffer.as_rope().clone()
|
||||
});
|
||||
Some(new_text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,13 +340,12 @@ impl BufferDiffInner {
|
||||
}
|
||||
|
||||
let mut secondary_status = DiffHunkSecondaryStatus::None;
|
||||
let mut secondary_diff_base_byte_range = None;
|
||||
if let Some(secondary_cursor) = secondary_cursor.as_mut() {
|
||||
if start_anchor
|
||||
.cmp(&secondary_cursor.start().buffer_range.start, buffer)
|
||||
.is_gt()
|
||||
{
|
||||
secondary_cursor.seek_forward(&end_anchor, Bias::Left, buffer);
|
||||
secondary_cursor.seek_forward(&start_anchor, Bias::Left, buffer);
|
||||
}
|
||||
|
||||
if let Some(secondary_hunk) = secondary_cursor.item() {
|
||||
@@ -339,12 +356,12 @@ impl BufferDiffInner {
|
||||
}
|
||||
if secondary_range == (start_point..end_point) {
|
||||
secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
|
||||
secondary_diff_base_byte_range =
|
||||
Some(secondary_hunk.diff_base_byte_range.clone());
|
||||
} else if secondary_range.start <= end_point {
|
||||
secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("no secondary cursor!!");
|
||||
}
|
||||
|
||||
return Some(DiffHunk {
|
||||
@@ -352,7 +369,6 @@ impl BufferDiffInner {
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
buffer_range: start_anchor..end_anchor,
|
||||
secondary_status,
|
||||
secondary_diff_base_byte_range,
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -387,7 +403,6 @@ impl BufferDiffInner {
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
// The secondary status is not used by callers of this method.
|
||||
secondary_status: DiffHunkSecondaryStatus::None,
|
||||
secondary_diff_base_byte_range: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -408,12 +423,12 @@ impl BufferDiffInner {
|
||||
.start
|
||||
.cmp(&old_hunk.buffer_range.start, new_snapshot)
|
||||
{
|
||||
cmp::Ordering::Less => {
|
||||
Ordering::Less => {
|
||||
start.get_or_insert(new_hunk.buffer_range.start);
|
||||
end.replace(new_hunk.buffer_range.end);
|
||||
new_cursor.next(new_snapshot);
|
||||
}
|
||||
cmp::Ordering::Equal => {
|
||||
Ordering::Equal => {
|
||||
if new_hunk != old_hunk {
|
||||
start.get_or_insert(new_hunk.buffer_range.start);
|
||||
if old_hunk
|
||||
@@ -431,7 +446,7 @@ impl BufferDiffInner {
|
||||
new_cursor.next(new_snapshot);
|
||||
old_cursor.next(new_snapshot);
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
Ordering::Greater => {
|
||||
start.get_or_insert(old_hunk.buffer_range.start);
|
||||
end.replace(old_hunk.buffer_range.end);
|
||||
old_cursor.next(new_snapshot);
|
||||
@@ -1059,6 +1074,7 @@ mod tests {
|
||||
use rand::{rngs::StdRng, Rng as _};
|
||||
use text::{Buffer, BufferId, Rope};
|
||||
use unindent::Unindent as _;
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
@@ -1257,6 +1273,208 @@ 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]
|
||||
async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
|
||||
let base_text = "
|
||||
@@ -1382,7 +1600,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_secondary_edits_for_stage_unstage(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
fn gen_line(rng: &mut StdRng) -> String {
|
||||
if rng.gen_bool(0.2) {
|
||||
"\n".to_owned()
|
||||
@@ -1447,7 +1665,7 @@ mod tests {
|
||||
|
||||
fn uncommitted_diff(
|
||||
working_copy: &language::BufferSnapshot,
|
||||
index_text: &Entity<language::Buffer>,
|
||||
index_text: &Rope,
|
||||
head_text: String,
|
||||
cx: &mut TestAppContext,
|
||||
) -> BufferDiff {
|
||||
@@ -1456,7 +1674,7 @@ mod tests {
|
||||
buffer_id: working_copy.remote_id(),
|
||||
inner: BufferDiff::build_sync(
|
||||
working_copy.text.clone(),
|
||||
index_text.read_with(cx, |index_text, _| index_text.text()),
|
||||
index_text.to_string(),
|
||||
cx,
|
||||
),
|
||||
secondary_diff: None,
|
||||
@@ -1487,17 +1705,11 @@ mod tests {
|
||||
)
|
||||
});
|
||||
let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
|
||||
let index_text = cx.new(|cx| {
|
||||
language::Buffer::local_normalized(
|
||||
if rng.gen() {
|
||||
Rope::from(head_text.as_str())
|
||||
} else {
|
||||
working_copy.as_rope().clone()
|
||||
},
|
||||
text::LineEnding::default(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let mut index_text = if rng.gen() {
|
||||
Rope::from(head_text.as_str())
|
||||
} else {
|
||||
working_copy.as_rope().clone()
|
||||
};
|
||||
|
||||
let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
|
||||
let mut hunks = cx.update(|cx| {
|
||||
@@ -1511,37 +1723,29 @@ mod tests {
|
||||
for _ in 0..operations {
|
||||
let i = rng.gen_range(0..hunks.len());
|
||||
let hunk = &mut hunks[i];
|
||||
let hunk_fields = (
|
||||
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(_)) => {
|
||||
let stage = match hunk.secondary_status {
|
||||
DiffHunkSecondaryStatus::HasSecondaryHunk => {
|
||||
hunk.secondary_status = DiffHunkSecondaryStatus::None;
|
||||
hunk.secondary_diff_base_byte_range = None;
|
||||
true
|
||||
}
|
||||
(DiffHunkSecondaryStatus::None, None) => {
|
||||
DiffHunkSecondaryStatus::None => {
|
||||
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
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let snapshot = cx.update(|cx| diff.snapshot(cx));
|
||||
let edits = snapshot.secondary_edits_for_stage_or_unstage(
|
||||
stage,
|
||||
[hunk_fields].into_iter(),
|
||||
&working_copy,
|
||||
);
|
||||
index_text.update(cx, |index_text, cx| {
|
||||
index_text.edit(edits, None, cx);
|
||||
index_text = cx.update(|cx| {
|
||||
snapshot
|
||||
.new_secondary_text_for_stage_or_unstage(
|
||||
stage,
|
||||
[(hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone())]
|
||||
.into_iter(),
|
||||
&working_copy,
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
|
||||
@@ -1550,6 +1754,7 @@ mod tests {
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunks.len(), found_hunks.len());
|
||||
|
||||
for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
|
||||
assert_eq!(
|
||||
expected_hunk.buffer_range.to_point(&working_copy),
|
||||
@@ -1560,10 +1765,6 @@ mod tests {
|
||||
found_hunk.diff_base_byte_range
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ pub struct ChannelPathsInsertGuard<'a> {
|
||||
channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
|
||||
}
|
||||
|
||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||
impl ChannelPathsInsertGuard<'_> {
|
||||
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
||||
let mut ret = false;
|
||||
let parent_path = channel_proto
|
||||
@@ -86,7 +86,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||
impl Drop for ChannelPathsInsertGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.channels_ordered.sort_by(|a, b| {
|
||||
let a = channel_path_sorting_key(*a, self.channels_by_id);
|
||||
|
||||
@@ -33,10 +33,13 @@ util.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
exec.workspace = true
|
||||
exec.workspace = true
|
||||
fork.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation.workspace = true
|
||||
core-services = "0.2"
|
||||
plist = "1.3"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -521,30 +521,108 @@ mod flatpak {
|
||||
}
|
||||
}
|
||||
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows {
|
||||
use anyhow::Context;
|
||||
use release_channel::app_identifier;
|
||||
use windows::{
|
||||
core::HSTRING,
|
||||
Win32::{
|
||||
Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, GENERIC_WRITE},
|
||||
Storage::FileSystem::{
|
||||
CreateFileW, WriteFile, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, OPEN_EXISTING,
|
||||
},
|
||||
System::Threading::CreateMutexW,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{Detect, InstalledApp};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitStatus;
|
||||
|
||||
struct App;
|
||||
fn check_single_instance() -> bool {
|
||||
let mutex = unsafe {
|
||||
CreateMutexW(
|
||||
None,
|
||||
false,
|
||||
&HSTRING::from(format!("{}-Instance-Mutex", app_identifier())),
|
||||
)
|
||||
.expect("Unable to create instance sync event")
|
||||
};
|
||||
let last_err = unsafe { GetLastError() };
|
||||
let _ = unsafe { CloseHandle(mutex) };
|
||||
last_err != ERROR_ALREADY_EXISTS
|
||||
}
|
||||
|
||||
struct App(PathBuf);
|
||||
|
||||
impl InstalledApp for App {
|
||||
fn zed_version_string(&self) -> String {
|
||||
unimplemented!()
|
||||
format!(
|
||||
"Zed {}{}{} – {}",
|
||||
if *release_channel::RELEASE_CHANNEL_NAME == "stable" {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{} ", *release_channel::RELEASE_CHANNEL_NAME)
|
||||
},
|
||||
option_env!("RELEASE_VERSION").unwrap_or_default(),
|
||||
match option_env!("ZED_COMMIT_SHA") {
|
||||
Some(commit_sha) => format!(" {commit_sha} "),
|
||||
None => "".to_string(),
|
||||
},
|
||||
self.0.display(),
|
||||
)
|
||||
}
|
||||
fn launch(&self, _ipc_url: String) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
|
||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||
if check_single_instance() {
|
||||
std::process::Command::new(self.0.clone())
|
||||
.arg(ipc_url)
|
||||
.spawn()?;
|
||||
} else {
|
||||
unsafe {
|
||||
let pipe = CreateFileW(
|
||||
&HSTRING::from(format!("\\\\.\\pipe\\{}-Named-Pipe", app_identifier())),
|
||||
GENERIC_WRITE.0,
|
||||
FILE_SHARE_MODE::default(),
|
||||
None,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAGS_AND_ATTRIBUTES::default(),
|
||||
None,
|
||||
)?;
|
||||
let message = ipc_url.as_bytes();
|
||||
let mut bytes_written = 0;
|
||||
WriteFile(pipe, Some(message), Some(&mut bytes_written), None)?;
|
||||
CloseHandle(pipe)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn run_foreground(&self, _ipc_url: String) -> io::Result<ExitStatus> {
|
||||
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 {
|
||||
pub fn detect(_path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
|
||||
Ok(App)
|
||||
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
|
||||
let path = if let Some(path) = path {
|
||||
path.to_path_buf().canonicalize()?
|
||||
} else {
|
||||
std::env::current_exe()?
|
||||
.parent()
|
||||
.context("no parent path for cli")?
|
||||
.parent()
|
||||
.context("no parent path for cli folder")?
|
||||
.join("Zed.exe")
|
||||
};
|
||||
|
||||
Ok(App(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ extension.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
git = { workspace = true, features = ["test-support"] }
|
||||
git_ui = { workspace = true, features = ["test-support"] }
|
||||
git_hosting_providers.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
hyper.workspace = true
|
||||
|
||||
@@ -202,7 +202,7 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_known_extension_versions<'a>(&self) -> Result<HashMap<String, Vec<String>>> {
|
||||
pub async fn get_known_extension_versions(&self) -> Result<HashMap<String, Vec<String>>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut extension_external_ids_by_id = HashMap::default();
|
||||
|
||||
|
||||
@@ -256,6 +256,7 @@ async fn perform_completion(
|
||||
// so that users can use the new version, without having to update Zed.
|
||||
request.model = match model.as_str() {
|
||||
"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-haiku" => anthropic::Model::Claude3Haiku.id().to_string(),
|
||||
"claude-3-sonnet" => anthropic::Model::Claude3Sonnet.id().to_string(),
|
||||
@@ -450,19 +451,15 @@ async fn check_usage_limit(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let user_id = UserId::from_proto(claims.user_id);
|
||||
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();
|
||||
|
||||
if usage.spending_this_month >= free_tier {
|
||||
let spending_this_month = state
|
||||
.db
|
||||
.get_user_spending_for_month(user_id, Utc::now())
|
||||
.await?;
|
||||
if spending_this_month >= free_tier {
|
||||
if !claims.has_llm_subscription {
|
||||
return Err(Error::http(
|
||||
StatusCode::PAYMENT_REQUIRED,
|
||||
@@ -470,7 +467,8 @@ async fn check_usage_limit(
|
||||
));
|
||||
}
|
||||
|
||||
if (usage.spending_this_month - free_tier) >= Cents(claims.max_monthly_spend_in_cents) {
|
||||
let monthly_spend = spending_this_month.saturating_sub(free_tier);
|
||||
if monthly_spend >= Cents(claims.max_monthly_spend_in_cents) {
|
||||
return Err(Error::Http(
|
||||
StatusCode::FORBIDDEN,
|
||||
"Maximum spending limit reached for this month.".to_string(),
|
||||
@@ -495,6 +493,11 @@ async fn check_usage_limit(
|
||||
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 usage = state
|
||||
.db
|
||||
.get_usage(user_id, provider, model_name, Utc::now())
|
||||
.await?;
|
||||
|
||||
let checks = [
|
||||
(
|
||||
usage.requests_this_minute,
|
||||
|
||||
@@ -27,7 +27,7 @@ fn authorize_access_to_model(
|
||||
}
|
||||
|
||||
if provider == LanguageModelProvider::Anthropic {
|
||||
if model == "claude-3-5-sonnet" {
|
||||
if model == "claude-3-5-sonnet" || model == "claude-3-7-sonnet" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -392,9 +392,13 @@ impl Server {
|
||||
.add_request_handler(forward_mutating_project_request::<proto::OpenContext>)
|
||||
.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::Push>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Pull>)
|
||||
.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::Unstage>)
|
||||
.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::GitReset>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
|
||||
@@ -971,7 +975,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for ConnectionPoolGuard<'a> {
|
||||
impl Deref for ConnectionPoolGuard<'_> {
|
||||
type Target = ConnectionPool;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -979,13 +983,13 @@ impl<'a> Deref for ConnectionPoolGuard<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for ConnectionPoolGuard<'a> {
|
||||
impl DerefMut for ConnectionPoolGuard<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for ConnectionPoolGuard<'a> {
|
||||
impl Drop for ConnectionPoolGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(test)]
|
||||
self.check_invariants();
|
||||
|
||||
@@ -13,6 +13,7 @@ mod channel_message_tests;
|
||||
mod channel_tests;
|
||||
mod editor_tests;
|
||||
mod following_tests;
|
||||
mod git_tests;
|
||||
mod integration_tests;
|
||||
mod notification_tests;
|
||||
mod random_channel_buffer_tests;
|
||||
|
||||
@@ -1537,6 +1537,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1552,6 +1553,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1770,6 +1772,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1785,6 +1788,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
130
crates/collab/src/tests/git_tests.rs
Normal file
130
crates/collab/src/tests/git_tests.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
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"]
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -6354,7 +6354,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 1 as preview
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_1.clone(), None, true, true, window, cx)
|
||||
workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6375,7 +6375,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 2 as preview
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6507,7 +6507,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 2 as preview in right pane
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6545,7 +6545,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 2 as preview in left pane
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -27,7 +27,7 @@ impl<'de> Deserialize<'de> for ChatPanelButton {
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
impl serde::de::Visitor<'_> for Visitor {
|
||||
type Value = ChatPanelButton;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
|
||||
@@ -15,7 +15,6 @@ path = "src/component.rs"
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
linkme.workspace = true
|
||||
once_cell.workspace = true
|
||||
parking_lot.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
|
||||
use linkme::distributed_slice;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
@@ -27,8 +27,8 @@ pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
#[distributed_slice]
|
||||
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
|
||||
Lazy::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
|
||||
LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
|
||||
pub struct ComponentRegistry {
|
||||
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
|
||||
|
||||
@@ -14,6 +14,7 @@ path = "src/context_server.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
context_server_settings.workspace = true
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::HashMap;
|
||||
use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, FutureExt};
|
||||
use futures::{channel::oneshot, select, FutureExt, StreamExt};
|
||||
use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Task};
|
||||
use parking_lot::Mutex;
|
||||
use postage::barrier;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde_json::{value::RawValue, Value};
|
||||
use smol::{
|
||||
channel,
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
process::Child,
|
||||
};
|
||||
use smol::channel;
|
||||
use std::{
|
||||
fmt,
|
||||
path::PathBuf,
|
||||
@@ -22,6 +18,8 @@ use std::{
|
||||
};
|
||||
use util::TryFutureExt;
|
||||
|
||||
use crate::transport::{StdioTransport, Transport};
|
||||
|
||||
const JSON_RPC_VERSION: &str = "2.0";
|
||||
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
@@ -55,7 +53,8 @@ pub struct Client {
|
||||
#[allow(dead_code)]
|
||||
output_done_rx: Mutex<Option<barrier::Receiver>>,
|
||||
executor: BackgroundExecutor,
|
||||
server: Arc<Mutex<Option<Child>>>,
|
||||
#[allow(dead_code)]
|
||||
transport: Arc<dyn Transport>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -152,25 +151,13 @@ impl Client {
|
||||
&binary.args
|
||||
);
|
||||
|
||||
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 server_name = binary
|
||||
.executable
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_else(String::new);
|
||||
|
||||
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 transport = Arc::new(StdioTransport::new(binary, &cx)?);
|
||||
|
||||
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
|
||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
||||
@@ -183,18 +170,22 @@ impl Client {
|
||||
let stdout_input_task = cx.spawn({
|
||||
let notification_handlers = notification_handlers.clone();
|
||||
let response_handlers = response_handlers.clone();
|
||||
let transport = transport.clone();
|
||||
move |cx| {
|
||||
Self::handle_input(stdout, notification_handlers, response_handlers, cx).log_err()
|
||||
Self::handle_input(transport, notification_handlers, response_handlers, cx)
|
||||
.log_err()
|
||||
}
|
||||
});
|
||||
let stderr_input_task = cx.spawn(|_| Self::handle_stderr(stderr).log_err());
|
||||
let stderr_input_task = cx.spawn(|_| Self::handle_stderr(transport.clone()).log_err());
|
||||
let input_task = cx.spawn(|_| async move {
|
||||
let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
|
||||
stdout.or(stderr)
|
||||
});
|
||||
|
||||
let output_task = cx.background_spawn({
|
||||
let transport = transport.clone();
|
||||
Self::handle_output(
|
||||
stdin,
|
||||
transport,
|
||||
outbound_rx,
|
||||
output_done_tx,
|
||||
response_handlers.clone(),
|
||||
@@ -202,24 +193,18 @@ impl Client {
|
||||
.log_err()
|
||||
});
|
||||
|
||||
let mut context_server = Self {
|
||||
Ok(Self {
|
||||
server_id,
|
||||
notification_handlers,
|
||||
response_handlers,
|
||||
name: "".into(),
|
||||
name: server_name.into(),
|
||||
next_id: Default::default(),
|
||||
outbound_tx,
|
||||
executor: cx.background_executor().clone(),
|
||||
io_tasks: Mutex::new(Some((input_task, output_task))),
|
||||
output_done_rx: Mutex::new(Some(output_done_rx)),
|
||||
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)
|
||||
transport,
|
||||
})
|
||||
}
|
||||
|
||||
/// Handles input from the server's stdout.
|
||||
@@ -228,79 +213,53 @@ impl Client {
|
||||
/// parses them as JSON-RPC responses or notifications, and dispatches them
|
||||
/// to the appropriate handlers. It processes both responses (which are matched
|
||||
/// to pending requests) and notifications (which trigger registered handlers).
|
||||
async fn handle_input<Stdout>(
|
||||
stdout: Stdout,
|
||||
async fn handle_input(
|
||||
transport: Arc<dyn Transport>,
|
||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
||||
cx: AsyncApp,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||
{
|
||||
let mut stdout = BufReader::new(stdout);
|
||||
let mut buffer = String::new();
|
||||
) -> anyhow::Result<()> {
|
||||
let mut receiver = transport.receive();
|
||||
|
||||
loop {
|
||||
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(handler) = handlers.remove(&response.id) {
|
||||
handler(Ok(content.to_string()));
|
||||
}
|
||||
}
|
||||
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(content) {
|
||||
let mut notification_handlers = notification_handlers.lock();
|
||||
if let Some(handler) =
|
||||
notification_handlers.get_mut(notification.method.as_str())
|
||||
{
|
||||
handler(notification.params.unwrap_or(Value::Null), cx.clone());
|
||||
while let Some(message) = receiver.next().await {
|
||||
if let Ok(response) = serde_json::from_str::<AnyResponse>(&message) {
|
||||
if let Some(handlers) = response_handlers.lock().as_mut() {
|
||||
if let Some(handler) = handlers.remove(&response.id) {
|
||||
handler(Ok(message.to_string()));
|
||||
}
|
||||
}
|
||||
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(&message) {
|
||||
let mut notification_handlers = notification_handlers.lock();
|
||||
if let Some(handler) = notification_handlers.get_mut(notification.method.as_str()) {
|
||||
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.
|
||||
/// Continuously reads and logs any error messages from the server.
|
||||
async fn handle_stderr<Stderr>(stderr: Stderr) -> anyhow::Result<()>
|
||||
where
|
||||
Stderr: AsyncRead + Unpin + Send + 'static,
|
||||
{
|
||||
let mut stderr = BufReader::new(stderr);
|
||||
let mut buffer = String::new();
|
||||
|
||||
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;
|
||||
async fn handle_stderr(transport: Arc<dyn Transport>) -> anyhow::Result<()> {
|
||||
while let Some(err) = transport.receive_err().next().await {
|
||||
log::warn!("context server stderr: {}", err.trim());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles the output to the context server's stdin.
|
||||
/// This function continuously receives messages from the outbound channel,
|
||||
/// writes them to the server's stdin, and manages the lifecycle of response handlers.
|
||||
async fn handle_output<Stdin>(
|
||||
stdin: Stdin,
|
||||
async fn handle_output(
|
||||
transport: Arc<dyn Transport>,
|
||||
outbound_rx: channel::Receiver<String>,
|
||||
output_done_tx: barrier::Sender,
|
||||
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
Stdin: AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
let mut stdin = BufWriter::new(stdin);
|
||||
) -> anyhow::Result<()> {
|
||||
let _clear_response_handlers = util::defer({
|
||||
let response_handlers = response_handlers.clone();
|
||||
move || {
|
||||
@@ -309,10 +268,7 @@ impl Client {
|
||||
});
|
||||
while let Ok(message) = outbound_rx.recv().await {
|
||||
log::trace!("outgoing message: {}", message);
|
||||
|
||||
stdin.write_all(message.as_bytes()).await?;
|
||||
stdin.write_all(b"\n").await?;
|
||||
stdin.flush().await?;
|
||||
transport.send(message).await?;
|
||||
}
|
||||
drop(output_done_tx);
|
||||
Ok(())
|
||||
@@ -416,14 +372,6 @@ 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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
|
||||
@@ -4,6 +4,7 @@ mod extension_context_server;
|
||||
pub mod manager;
|
||||
pub mod protocol;
|
||||
mod registry;
|
||||
mod transport;
|
||||
pub mod types;
|
||||
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
|
||||
16
crates/context_server/src/transport.rs
Normal file
16
crates/context_server/src/transport.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
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>>;
|
||||
}
|
||||
140
crates/context_server/src/transport/stdio_transport.rs
Normal file
140
crates/context_server/src/transport/stdio_transport.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
inline_completion.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
menu.workspace = true
|
||||
node_runtime.workspace = true
|
||||
@@ -62,7 +63,9 @@ async-std = { version = "1.12.0", features = ["unstable"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -16,6 +16,7 @@ use gpui::{
|
||||
};
|
||||
use http_client::github::get_release_by_tag_name;
|
||||
use http_client::HttpClient;
|
||||
use language::language_settings::CopilotSettings;
|
||||
use language::{
|
||||
language_settings::{all_language_settings, language_settings, EditPredictionProvider},
|
||||
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||
@@ -367,13 +368,13 @@ impl Copilot {
|
||||
let server_id = self.server_id;
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
if all_language_settings(None, cx).edit_predictions.provider
|
||||
== EditPredictionProvider::Copilot
|
||||
{
|
||||
let language_settings = all_language_settings(None, cx);
|
||||
if language_settings.edit_predictions.provider == EditPredictionProvider::Copilot {
|
||||
if matches!(self.server, CopilotServer::Disabled) {
|
||||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||
let start_task = cx
|
||||
.spawn(move |this, cx| {
|
||||
Self::start_language_server(server_id, http, node_runtime, this, cx)
|
||||
Self::start_language_server(server_id, http, node_runtime, env, this, cx)
|
||||
})
|
||||
.shared();
|
||||
self.server = CopilotServer::Starting { task: start_task };
|
||||
@@ -385,6 +386,30 @@ 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"))]
|
||||
pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
|
||||
use lsp::FakeLanguageServer;
|
||||
@@ -422,6 +447,7 @@ impl Copilot {
|
||||
new_server_id: LanguageServerId,
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
env: Option<HashMap<String, String>>,
|
||||
this: WeakEntity<Self>,
|
||||
mut cx: AsyncApp,
|
||||
) {
|
||||
@@ -432,8 +458,7 @@ impl Copilot {
|
||||
let binary = LanguageServerBinary {
|
||||
path: node_path,
|
||||
arguments,
|
||||
// TODO: We could set HTTP_PROXY etc here and fix the copilot issue.
|
||||
env: None,
|
||||
env,
|
||||
};
|
||||
|
||||
let root_path = if cfg!(target_os = "windows") {
|
||||
@@ -450,6 +475,7 @@ impl Copilot {
|
||||
binary,
|
||||
root_path,
|
||||
None,
|
||||
Default::default(),
|
||||
cx.clone(),
|
||||
)?;
|
||||
|
||||
@@ -611,6 +637,8 @@ impl Copilot {
|
||||
}
|
||||
|
||||
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
|
||||
.spawn({
|
||||
let http = self.http.clone();
|
||||
@@ -618,7 +646,7 @@ impl Copilot {
|
||||
let server_id = self.server_id;
|
||||
move |this, cx| async move {
|
||||
clear_copilot_dir().await;
|
||||
Self::start_language_server(server_id, http, node_runtime, this, cx).await
|
||||
Self::start_language_server(server_id, http, node_runtime, env, this, cx).await
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
@@ -1279,3 +1307,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ pub enum Model {
|
||||
O3Mini,
|
||||
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
|
||||
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")]
|
||||
Gemini20Flash,
|
||||
}
|
||||
@@ -47,7 +49,11 @@ pub enum Model {
|
||||
impl Model {
|
||||
pub fn uses_streaming(&self) -> bool {
|
||||
match self {
|
||||
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
|
||||
Self::Gpt4o
|
||||
| Self::Gpt4
|
||||
| Self::Gpt3_5Turbo
|
||||
| Self::Claude3_5Sonnet
|
||||
| Self::Claude3_7Sonnet => true,
|
||||
Self::O3Mini | Self::O1 | Self::Gemini20Flash => false,
|
||||
}
|
||||
}
|
||||
@@ -60,6 +66,7 @@ impl Model {
|
||||
"o1" => Ok(Self::O1),
|
||||
"o3-mini" => Ok(Self::O3Mini),
|
||||
"claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet),
|
||||
"claude-3-7-sonnet" => Ok(Self::Claude3_7Sonnet),
|
||||
"gemini-2.0-flash-001" => Ok(Self::Gemini20Flash),
|
||||
_ => Err(anyhow!("Invalid model id: {}", id)),
|
||||
}
|
||||
@@ -73,6 +80,7 @@ impl Model {
|
||||
Self::O3Mini => "o3-mini",
|
||||
Self::O1 => "o1",
|
||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
|
||||
Self::Claude3_7Sonnet => "claude-3-7-sonnet",
|
||||
Self::Gemini20Flash => "gemini-2.0-flash-001",
|
||||
}
|
||||
}
|
||||
@@ -85,6 +93,7 @@ impl Model {
|
||||
Self::O3Mini => "o3-mini",
|
||||
Self::O1 => "o1",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||
Self::Gemini20Flash => "Gemini 2.0 Flash",
|
||||
}
|
||||
}
|
||||
@@ -96,7 +105,8 @@ impl Model {
|
||||
Self::Gpt3_5Turbo => 12_288,
|
||||
Self::O3Mini => 64_000,
|
||||
Self::O1 => 20_000,
|
||||
Self::Claude3_5Sonnet => 128_000,
|
||||
Self::Claude3_5Sonnet => 200_000,
|
||||
Self::Claude3_7Sonnet => 90_000,
|
||||
Model::Gemini20Flash => 128_000,
|
||||
}
|
||||
}
|
||||
@@ -401,7 +411,7 @@ async fn stream_completion(
|
||||
|
||||
match serde_json::from_str::<ResponseEvent>(line) {
|
||||
Ok(response) => {
|
||||
if response.choices.first().is_none()
|
||||
if response.choices.is_empty()
|
||||
|| response.choices.first().unwrap().finish_reason.is_some()
|
||||
{
|
||||
None
|
||||
|
||||
@@ -192,7 +192,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
|
||||
fn discard(&mut self, cx: &mut Context<Self>) {
|
||||
let settings = AllLanguageSettings::get_global(cx);
|
||||
|
||||
let copilot_enabled = settings.show_inline_completions(None, cx);
|
||||
let copilot_enabled = settings.show_edit_predictions(None, cx);
|
||||
|
||||
if !copilot_enabled {
|
||||
return;
|
||||
|
||||
@@ -88,15 +88,46 @@ const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
impl Render for ProjectDiagnosticsEditor {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let child = if self.path_states.is_empty() {
|
||||
div()
|
||||
let warning_count = if self.include_warnings {
|
||||
self.summary.warning_count
|
||||
} 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")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.child(Label::new("No problems in workspace"))
|
||||
.gap_1()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.text_center()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(Label::new(label).color(Color::Muted))
|
||||
.when(self.summary.warning_count > 0, |this| {
|
||||
let plural_suffix = if self.summary.warning_count > 1 {
|
||||
"s"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
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 {
|
||||
div().size_full().child(self.editor.clone())
|
||||
};
|
||||
@@ -531,9 +562,7 @@ impl ProjectDiagnosticsEditor {
|
||||
)),
|
||||
height: diagnostic.message.matches('\n').count() as u32 + 1,
|
||||
style: BlockStyle::Fixed,
|
||||
render: diagnostic_block_renderer(
|
||||
diagnostic, None, true, true,
|
||||
),
|
||||
render: diagnostic_block_renderer(diagnostic, None, true),
|
||||
priority: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ pub struct SelectPrevious {
|
||||
pub struct MoveToBeginningOfLine {
|
||||
#[serde(default = "default_true")]
|
||||
pub stop_at_soft_wraps: bool,
|
||||
#[serde(default)]
|
||||
pub stop_at_indent: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
@@ -29,6 +31,8 @@ pub struct MoveToBeginningOfLine {
|
||||
pub struct SelectToBeginningOfLine {
|
||||
#[serde(default)]
|
||||
pub(super) stop_at_soft_wraps: bool,
|
||||
#[serde(default)]
|
||||
pub stop_at_indent: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
@@ -280,7 +284,6 @@ gpui::actions!(
|
||||
DuplicateLineDown,
|
||||
DuplicateLineUp,
|
||||
DuplicateSelection,
|
||||
ExpandAllHunkDiffs,
|
||||
ExpandMacroRecursively,
|
||||
FindAllReferences,
|
||||
Fold,
|
||||
@@ -425,3 +428,4 @@ action_as!(go_to_line, ToggleGoToLine as Toggle);
|
||||
|
||||
action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]);
|
||||
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]);
|
||||
action_with_deprecated_aliases!(editor, ExpandAllDiffHunks, ["editor::ExpandAllHunkDiffs"]);
|
||||
|
||||
@@ -330,7 +330,11 @@ impl DisplayMap {
|
||||
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
|
||||
}
|
||||
|
||||
pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) {
|
||||
pub fn fold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = language::BufferId>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
@@ -341,10 +345,14 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits);
|
||||
block_map.fold_buffer(buffer_id, self.buffer.read(cx), cx)
|
||||
block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx)
|
||||
}
|
||||
|
||||
pub fn unfold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) {
|
||||
pub fn unfold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = language::BufferId>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
@@ -355,7 +363,7 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits);
|
||||
block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx)
|
||||
block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx)
|
||||
}
|
||||
|
||||
pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
|
||||
@@ -1911,6 +1919,67 @@ 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]
|
||||
async fn test_chunks(cx: &mut gpui::TestAppContext) {
|
||||
let text = r#"
|
||||
|
||||
@@ -638,10 +638,13 @@ impl BlockMap {
|
||||
self.custom_blocks[start_block_ix..end_block_ix]
|
||||
.iter()
|
||||
.filter_map(|block| {
|
||||
Some((
|
||||
block.placement.to_wrap_row(wrap_snapshot)?,
|
||||
Block::Custom(block.clone()),
|
||||
))
|
||||
let placement = block.placement.to_wrap_row(wrap_snapshot)?;
|
||||
if let BlockPlacement::Above(row) = placement {
|
||||
if row < new_start {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some((placement, Block::Custom(block.clone())))
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -996,7 +999,7 @@ impl std::ops::DerefMut for BlockPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for BlockMapReader<'a> {
|
||||
impl Deref for BlockMapReader<'_> {
|
||||
type Target = BlockSnapshot;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -1004,13 +1007,13 @@ impl<'a> Deref for BlockMapReader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for BlockMapReader<'a> {
|
||||
impl DerefMut for BlockMapReader<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.snapshot
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BlockMapReader<'a> {
|
||||
impl BlockMapReader<'_> {
|
||||
pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
|
||||
let block = self.blocks.iter().find(|block| block.id == block_id)?;
|
||||
let buffer_row = block
|
||||
@@ -1050,7 +1053,7 @@ impl<'a> BlockMapReader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BlockMapWriter<'a> {
|
||||
impl BlockMapWriter<'_> {
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||
@@ -1236,26 +1239,45 @@ impl<'a> BlockMapWriter<'a> {
|
||||
self.remove(blocks_to_remove);
|
||||
}
|
||||
|
||||
pub fn fold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
|
||||
self.0.folded_buffers.insert(buffer_id);
|
||||
self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
|
||||
self.0.folded_buffers.remove(&buffer_id);
|
||||
self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
|
||||
}
|
||||
|
||||
fn recompute_blocks_for_buffer(
|
||||
pub fn fold_buffers(
|
||||
&mut self,
|
||||
buffer_id: BufferId,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
multi_buffer: &MultiBuffer,
|
||||
cx: &App,
|
||||
) {
|
||||
let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
|
||||
self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
multi_buffer: &MultiBuffer,
|
||||
cx: &App,
|
||||
) {
|
||||
self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
|
||||
}
|
||||
|
||||
fn fold_or_unfold_buffers(
|
||||
&mut self,
|
||||
fold: bool,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
multi_buffer: &MultiBuffer,
|
||||
cx: &App,
|
||||
) {
|
||||
let mut ranges = Vec::new();
|
||||
for buffer_id in buffer_ids {
|
||||
if fold {
|
||||
self.0.folded_buffers.insert(buffer_id);
|
||||
} else {
|
||||
self.0.folded_buffers.remove(&buffer_id);
|
||||
}
|
||||
ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
|
||||
}
|
||||
ranges.sort_unstable_by_key(|range| range.start);
|
||||
|
||||
let mut edits = Patch::default();
|
||||
for range in multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx) {
|
||||
let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
|
||||
for range in ranges {
|
||||
let last_edit_row = cmp::min(
|
||||
wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
|
||||
wrap_snapshot.max_point().row(),
|
||||
@@ -1718,7 +1740,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BlockChunks<'a> {
|
||||
impl BlockChunks<'_> {
|
||||
/// Go to the next transform
|
||||
fn advance(&mut self) {
|
||||
self.input_chunk = Chunk::default();
|
||||
@@ -1834,7 +1856,7 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockRows<'a> {
|
||||
impl Iterator for BlockRows<'_> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1930,7 +1952,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for BlockContext<'a, '_> {
|
||||
impl Deref for BlockContext<'_, '_> {
|
||||
type Target = App;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -2727,7 +2749,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffer(buffer_id_1, buffer, cx);
|
||||
writer.fold_buffers([buffer_id_1], buffer, cx);
|
||||
});
|
||||
let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -2802,7 +2824,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffer(buffer_id_2, buffer, cx);
|
||||
writer.fold_buffers([buffer_id_2], buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -2858,7 +2880,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.unfold_buffer(buffer_id_1, buffer, cx);
|
||||
writer.unfold_buffers([buffer_id_1], buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -2919,7 +2941,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffer(buffer_id_3, buffer, cx);
|
||||
writer.fold_buffers([buffer_id_3], buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -2997,7 +3019,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffer(buffer_id, buffer, cx);
|
||||
writer.fold_buffers([buffer_id], buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -3247,7 +3269,7 @@ mod tests {
|
||||
);
|
||||
folded_count += 1;
|
||||
unfolded_count -= 1;
|
||||
block_map.fold_buffer(buffer_to_fold, buffer, cx);
|
||||
block_map.fold_buffers([buffer_to_fold], buffer, cx);
|
||||
}
|
||||
if unfold {
|
||||
let buffer_to_unfold =
|
||||
@@ -3255,7 +3277,7 @@ mod tests {
|
||||
log::info!("Unfolding {buffer_to_unfold:?}");
|
||||
unfolded_count += 1;
|
||||
folded_count -= 1;
|
||||
block_map.unfold_buffer(buffer_to_unfold, buffer, cx);
|
||||
block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
|
||||
}
|
||||
log::info!(
|
||||
"Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
|
||||
|
||||
@@ -132,7 +132,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||
|
||||
pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
|
||||
|
||||
impl<'a> FoldMapWriter<'a> {
|
||||
impl FoldMapWriter<'_> {
|
||||
pub(crate) fn fold<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||
@@ -1121,7 +1121,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
|
||||
impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
|
||||
fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
|
||||
AnchorRangeExt::cmp(&self.0, &other.0, buffer)
|
||||
}
|
||||
@@ -1144,7 +1144,7 @@ pub struct FoldRows<'a> {
|
||||
fold_point: FoldPoint,
|
||||
}
|
||||
|
||||
impl<'a> FoldRows<'a> {
|
||||
impl FoldRows<'_> {
|
||||
pub(crate) fn seek(&mut self, row: u32) {
|
||||
let fold_point = FoldPoint::new(row, 0);
|
||||
self.cursor.seek(&fold_point, Bias::Left, &());
|
||||
@@ -1155,7 +1155,7 @@ impl<'a> FoldRows<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FoldRows<'a> {
|
||||
impl Iterator for FoldRows<'_> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1190,7 +1190,7 @@ pub struct FoldChunks<'a> {
|
||||
max_output_offset: FoldOffset,
|
||||
}
|
||||
|
||||
impl<'a> FoldChunks<'a> {
|
||||
impl FoldChunks<'_> {
|
||||
pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
|
||||
self.transform_cursor.seek(&range.start, Bias::Right, &());
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ pub struct InlayChunks<'a> {
|
||||
snapshot: &'a InlaySnapshot,
|
||||
}
|
||||
|
||||
impl<'a> InlayChunks<'a> {
|
||||
impl InlayChunks<'_> {
|
||||
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
|
||||
self.transforms.seek(&new_range.start, Bias::Right, &());
|
||||
|
||||
@@ -341,7 +341,7 @@ impl<'a> Iterator for InlayChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InlayBufferRows<'a> {
|
||||
impl InlayBufferRows<'_> {
|
||||
pub fn seek(&mut self, row: u32) {
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
self.transforms.seek(&inlay_point, Bias::Left, &());
|
||||
@@ -363,7 +363,7 @@ impl<'a> InlayBufferRows<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InlayBufferRows<'a> {
|
||||
impl Iterator for InlayBufferRows<'_> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
|
||||
@@ -498,7 +498,7 @@ pub struct TabChunks<'a> {
|
||||
inside_leading_tab: bool,
|
||||
}
|
||||
|
||||
impl<'a> TabChunks<'a> {
|
||||
impl TabChunks<'_> {
|
||||
pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
|
||||
let (input_start, expanded_char_column, to_next_stop) =
|
||||
self.snapshot.to_fold_point(range.start, Bias::Left);
|
||||
|
||||
@@ -69,7 +69,7 @@ pub struct WrapRows<'a> {
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
impl<'a> WrapRows<'a> {
|
||||
impl WrapRows<'_> {
|
||||
pub(crate) fn seek(&mut self, start_row: u32) {
|
||||
self.transforms
|
||||
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
@@ -872,7 +872,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> WrapChunks<'a> {
|
||||
impl WrapChunks<'_> {
|
||||
pub(crate) fn seek(&mut self, rows: Range<u32>) {
|
||||
let output_start = WrapPoint::new(rows.start, 0);
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
@@ -955,7 +955,7 @@ impl<'a> Iterator for WrapChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WrapRows<'a> {
|
||||
impl Iterator for WrapRows<'_> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1120,7 +1120,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
|
||||
impl sum_tree::SeekTarget<'_, TransformSummary, TransformSummary> for TabPoint {
|
||||
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
|
||||
Ord::cmp(&self.0, &cursor_location.input.lines)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,6 @@ pub struct EditorSettings {
|
||||
pub auto_signature_help: bool,
|
||||
pub show_signature_help_after_edits: bool,
|
||||
pub jupyter: Jupyter,
|
||||
pub hide_mouse_while_typing: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -178,7 +177,7 @@ impl<'de> Deserialize<'de> for ScrollbarDiagnostics {
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
impl serde::de::Visitor<'_> for Visitor {
|
||||
type Value = ScrollbarDiagnostics;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
@@ -271,10 +270,6 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: None
|
||||
pub cursor_shape: Option<CursorShape>,
|
||||
/// Determines whether the mouse cursor should be hidden while typing in an editor or input box.
|
||||
///
|
||||
/// Default: true
|
||||
pub hide_mouse_while_typing: Option<bool>,
|
||||
/// How to highlight the current line in the editor.
|
||||
///
|
||||
/// Default: all
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
},
|
||||
JoinLines,
|
||||
};
|
||||
use buffer_diff::{BufferDiff, DiffHunkStatus};
|
||||
use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
@@ -24,7 +24,7 @@ use language::{
|
||||
Override, Point,
|
||||
};
|
||||
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
||||
use multi_buffer::IndentGuide;
|
||||
use multi_buffer::{IndentGuide, PathKey};
|
||||
use parking_lot::Mutex;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use project::project_settings::{LspSettings, ProjectSettings};
|
||||
@@ -36,6 +36,7 @@ use std::{
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
|
||||
use text::ToPoint as _;
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
assert_set_eq, path,
|
||||
@@ -1510,6 +1511,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let move_to_beg = MoveToBeginningOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
stop_at_indent: true,
|
||||
};
|
||||
|
||||
let move_to_end = MoveToEndOfLine {
|
||||
@@ -1590,6 +1592,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
|
||||
editor.select_to_beginning_of_line(
|
||||
&SelectToBeginningOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
stop_at_indent: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
@@ -1607,6 +1610,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
|
||||
editor.select_to_beginning_of_line(
|
||||
&SelectToBeginningOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
stop_at_indent: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
@@ -1624,6 +1628,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
|
||||
editor.select_to_beginning_of_line(
|
||||
&SelectToBeginningOfLine {
|
||||
stop_at_soft_wraps: true,
|
||||
stop_at_indent: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
@@ -1684,6 +1689,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let move_to_beg = MoveToBeginningOfLine {
|
||||
stop_at_soft_wraps: false,
|
||||
stop_at_indent: false,
|
||||
};
|
||||
|
||||
let move_to_end = MoveToEndOfLine {
|
||||
@@ -3389,7 +3395,7 @@ async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &m
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
// Join lines
|
||||
@@ -3429,7 +3435,7 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
|
||||
cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
|
||||
cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -5811,7 +5817,7 @@ async fn test_fold_function_bodies(cx: &mut TestAppContext) {
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
cx.set_state(&text);
|
||||
cx.set_diff_base(&base_text);
|
||||
cx.set_head_text(&base_text);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&Default::default(), window, cx);
|
||||
});
|
||||
@@ -10964,6 +10970,106 @@ async fn cycle_through_same_place_diagnostics(
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn active_diagnostics_dismiss_after_invalidation(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let lsp_store =
|
||||
cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
ˇfn func(abc def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
let message = "Something's wrong!";
|
||||
cx.update(|_, cx| {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 11),
|
||||
lsp::Position::new(0, 12),
|
||||
),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
message: message.to_string(),
|
||||
..Default::default()
|
||||
}],
|
||||
},
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
assert_eq!(
|
||||
editor
|
||||
.active_diagnostics
|
||||
.as_ref()
|
||||
.map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
|
||||
Some(message),
|
||||
"Should have a diagnostics group activated"
|
||||
);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update(|_, cx| {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
assert_eq!(
|
||||
editor.active_diagnostics, None,
|
||||
"After no diagnostics set to the editor, no diagnostics should be active"
|
||||
);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
assert_eq!(
|
||||
editor.active_diagnostics, None,
|
||||
"Should be no diagnostics to go to and activate"
|
||||
);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -11039,7 +11145,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -12531,7 +12637,7 @@ async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
|
||||
three
|
||||
"#};
|
||||
|
||||
cx.set_diff_base(base_text);
|
||||
cx.set_head_text(base_text);
|
||||
cx.set_state("\nˇ\n");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _window, cx| {
|
||||
@@ -13168,7 +13274,7 @@ async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
@@ -13302,11 +13408,11 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
@@ -13330,7 +13436,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base("new diff base!");
|
||||
cx.set_head_text("new diff base!");
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
@@ -13630,11 +13736,11 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -13778,7 +13884,7 @@ async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_diff_base(indoc! { "
|
||||
cx.set_head_text(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
@@ -13901,11 +14007,11 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -14024,11 +14130,11 @@ async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &m
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&base_text);
|
||||
cx.set_head_text(&base_text);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -14106,10 +14212,10 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
cx.set_head_text(&diff_base);
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -14821,6 +14927,397 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let diff_base = r#"
|
||||
a
|
||||
b
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.set_state(
|
||||
&r#"
|
||||
ˇA
|
||||
b
|
||||
C
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.set_head_text(&diff_base);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let both_hunks_expanded = r#"
|
||||
- a
|
||||
+ ˇA
|
||||
b
|
||||
- c
|
||||
+ C
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(both_hunks_expanded.clone());
|
||||
|
||||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let buffer_id = hunks[0].buffer_id;
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 2);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let second_hunk_expanded = r#"
|
||||
ˇA
|
||||
b
|
||||
- c
|
||||
+ C
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(second_hunk_expanded);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(both_hunks_expanded.clone());
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let first_hunk_expanded = r#"
|
||||
- a
|
||||
+ ˇA
|
||||
b
|
||||
C
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(first_hunk_expanded);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(both_hunks_expanded);
|
||||
|
||||
cx.set_state(
|
||||
&r#"
|
||||
ˇA
|
||||
b
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
// TODO this cursor position seems bad
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- ˇa
|
||||
+ A
|
||||
b
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- ˇa
|
||||
+ A
|
||||
b
|
||||
- c
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let buffer_id = hunks[0].buffer_id;
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 2);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- ˇa
|
||||
+ A
|
||||
b
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_deletion_hunk_at_start_of_file(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let diff_base = r#"
|
||||
a
|
||||
b
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.set_state(
|
||||
&r#"
|
||||
ˇb
|
||||
c
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.set_head_text(&diff_base);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let hunk_expanded = r#"
|
||||
- a
|
||||
ˇb
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(hunk_expanded.clone());
|
||||
|
||||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let buffer_id = hunks[0].buffer_id;
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 1);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let hunk_collapsed = r#"
|
||||
ˇb
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(hunk_collapsed);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(hunk_expanded.clone());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_display_diff_hunks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/test"),
|
||||
json!({
|
||||
".git": {},
|
||||
"file-1": "ONE\n",
|
||||
"file-2": "TWO\n",
|
||||
"file-3": "THREE\n",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.set_head_for_repo(
|
||||
path!("/test/.git").as_ref(),
|
||||
&[
|
||||
("file-1".into(), "one\n".into()),
|
||||
("file-2".into(), "two\n".into()),
|
||||
("file-3".into(), "three\n".into()),
|
||||
],
|
||||
);
|
||||
|
||||
let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
|
||||
let mut buffers = vec![];
|
||||
for i in 1..=3 {
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
let path = format!(path!("/test/file-{}"), i);
|
||||
project.open_local_buffer(path, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
for buffer in &buffers {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
|
||||
buffer.clone(),
|
||||
vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
|
||||
DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
multibuffer
|
||||
});
|
||||
|
||||
let editor = cx.add_window(|window, cx| {
|
||||
Editor::new(
|
||||
EditorMode::Full,
|
||||
multibuffer,
|
||||
Some(project),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let snapshot = editor
|
||||
.update(cx, |editor, window, cx| editor.snapshot(window, cx))
|
||||
.unwrap();
|
||||
let hunks = snapshot
|
||||
.display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
|
||||
.map(|hunk| match hunk {
|
||||
DisplayDiffHunk::Unfolded {
|
||||
display_row_range, ..
|
||||
} => display_row_range,
|
||||
DisplayDiffHunk::Folded { .. } => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
hunks,
|
||||
[
|
||||
DisplayRow(3)..DisplayRow(5),
|
||||
DisplayRow(10)..DisplayRow(12),
|
||||
DisplayRow(17)..DisplayRow(19),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_head_text(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"
|
||||
});
|
||||
cx.set_index_text(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"
|
||||
});
|
||||
cx.set_state(indoc! {"
|
||||
one
|
||||
TWO
|
||||
ˇTHREE
|
||||
FOUR
|
||||
five
|
||||
"});
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.assert_index_text(Some(indoc! {"
|
||||
one
|
||||
TWO
|
||||
THREE
|
||||
FOUR
|
||||
five
|
||||
"}));
|
||||
cx.set_state(indoc! { "
|
||||
one
|
||||
TWO
|
||||
ˇTHREE-HUNDRED
|
||||
FOUR
|
||||
five
|
||||
"});
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(hunks.len(), 1);
|
||||
assert_eq!(
|
||||
hunks[0].status(),
|
||||
DiffHunkStatus {
|
||||
kind: DiffHunkStatusKind::Modified,
|
||||
secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
|
||||
}
|
||||
);
|
||||
|
||||
editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.assert_index_text(Some(indoc! {"
|
||||
one
|
||||
TWO
|
||||
THREE-HUNDRED
|
||||
FOUR
|
||||
five
|
||||
"}));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -15180,11 +15677,12 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
editor.tasks.insert(
|
||||
(buffer.read(cx).remote_id(), 3),
|
||||
RunnableTasks {
|
||||
templates: vec![],
|
||||
offset: MultiBufferOffset(43),
|
||||
offset: snapshot.anchor_before(43),
|
||||
column: 0,
|
||||
extra_variables: HashMap::default(),
|
||||
context_range: BufferOffset(43)..BufferOffset(85),
|
||||
@@ -15194,7 +15692,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
|
||||
(buffer.read(cx).remote_id(), 8),
|
||||
RunnableTasks {
|
||||
templates: vec![],
|
||||
offset: MultiBufferOffset(86),
|
||||
offset: snapshot.anchor_before(86),
|
||||
column: 0,
|
||||
extra_variables: HashMap::default(),
|
||||
context_range: BufferOffset(86)..BufferOffset(191),
|
||||
@@ -15218,7 +15716,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
|
||||
async fn test_folding_buffers(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
|
||||
@@ -15325,7 +15823,7 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
|
||||
let multi_buffer_editor = cx.new_window_entity(|window, cx| {
|
||||
Editor::new(
|
||||
EditorMode::Full,
|
||||
multi_buffer,
|
||||
multi_buffer.clone(),
|
||||
Some(project.clone()),
|
||||
true,
|
||||
window,
|
||||
@@ -15333,10 +15831,9 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
|
||||
)
|
||||
});
|
||||
|
||||
let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
|
||||
assert_eq!(
|
||||
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
full_text,
|
||||
"\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
|
||||
);
|
||||
|
||||
multi_buffer_editor.update(cx, |editor, cx| {
|
||||
@@ -15382,12 +15879,25 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
|
||||
"After unfolding the second buffer, its text should be displayed"
|
||||
);
|
||||
|
||||
multi_buffer_editor.update(cx, |editor, cx| {
|
||||
editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
|
||||
// Typing inside of buffer 1 causes that buffer to be unfolded.
|
||||
multi_buffer_editor.update_in(cx, |editor, window, cx| {
|
||||
assert_eq!(
|
||||
multi_buffer
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.text_for_range(Point::new(1, 0)..Point::new(1, 4))
|
||||
.collect::<String>(),
|
||||
"bbbb"
|
||||
);
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
|
||||
});
|
||||
editor.handle_input("B", window, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
|
||||
"\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
|
||||
"After unfolding the first buffer, its and 2nd buffer's text should be displayed"
|
||||
);
|
||||
|
||||
@@ -15396,13 +15906,13 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
|
||||
});
|
||||
assert_eq!(
|
||||
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
full_text,
|
||||
"\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
|
||||
"After unfolding the all buffers, all original text should be displayed"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
|
||||
async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let sample_text_1 = "1111\n2222\n3333".to_string();
|
||||
@@ -15551,7 +16061,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) {
|
||||
async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
|
||||
@@ -16183,7 +16693,8 @@ fn assert_hunk_revert(
|
||||
cx: &mut EditorLspTestContext,
|
||||
) {
|
||||
cx.set_state(not_reverted_text_with_selections);
|
||||
cx.set_diff_base(base_text);
|
||||
cx.set_head_text(base_text);
|
||||
cx.clear_index_text();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
|
||||
|
||||
@@ -12,23 +12,25 @@ use crate::{
|
||||
hover_popover::{
|
||||
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
||||
},
|
||||
inlay_hint_settings,
|
||||
items::BufferSearchHighlights,
|
||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
|
||||
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
|
||||
DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
|
||||
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayDiffHunk, DisplayPoint,
|
||||
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
|
||||
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
|
||||
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt,
|
||||
RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt,
|
||||
ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown,
|
||||
PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
|
||||
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
|
||||
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
|
||||
use client::ParticipantIndex;
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use file_icons::FileIcons;
|
||||
use git::{blame::BlameEntry, Oid};
|
||||
use git::{blame::BlameEntry, status::FileStatus, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
|
||||
relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
|
||||
@@ -50,7 +52,7 @@ use language::{
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
|
||||
RowInfo, ToOffset,
|
||||
RowInfo,
|
||||
};
|
||||
use project::project_settings::{self, GitGutterSetting, ProjectSettings};
|
||||
use settings::Settings;
|
||||
@@ -73,25 +75,12 @@ use ui::{
|
||||
POPOVER_Y_PADDING,
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use util::{debug_panic, RangeExt, ResultExt};
|
||||
use util::{debug_panic, maybe, RangeExt, ResultExt};
|
||||
use workspace::{item::Item, notifications::NotifyTaskExt};
|
||||
|
||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
|
||||
const MIN_SCROLL_THUMB_SIZE: f32 = 25.;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum DisplayDiffHunk {
|
||||
Folded {
|
||||
display_row: DisplayRow,
|
||||
},
|
||||
Unfolded {
|
||||
diff_base_byte_range: Range<usize>,
|
||||
display_row_range: Range<DisplayRow>,
|
||||
multi_buffer_range: Range<Anchor>,
|
||||
status: DiffHunkStatus,
|
||||
},
|
||||
}
|
||||
|
||||
struct SelectionLayout {
|
||||
head: DisplayPoint,
|
||||
cursor_shape: CursorShape,
|
||||
@@ -406,7 +395,7 @@ impl EditorElement {
|
||||
register_action(editor, window, Editor::toggle_relative_line_numbers);
|
||||
register_action(editor, window, Editor::toggle_indent_guides);
|
||||
register_action(editor, window, Editor::toggle_inlay_hints);
|
||||
register_action(editor, window, Editor::toggle_inline_completions);
|
||||
register_action(editor, window, Editor::toggle_edit_predictions);
|
||||
register_action(editor, window, Editor::toggle_inline_diagnostics);
|
||||
register_action(editor, window, hover_popover::hover);
|
||||
register_action(editor, window, Editor::reveal_in_finder);
|
||||
@@ -518,6 +507,25 @@ impl EditorElement {
|
||||
return;
|
||||
}
|
||||
editor.update(cx, |editor, cx| {
|
||||
let inlay_hint_settings = inlay_hint_settings(
|
||||
editor.selections.newest_anchor().head(),
|
||||
&editor.buffer.read(cx).snapshot(cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Some(inlay_modifiers) = inlay_hint_settings
|
||||
.toggle_on_modifiers_press
|
||||
.as_ref()
|
||||
.filter(|modifiers| modifiers.modified())
|
||||
{
|
||||
editor.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::ModifiersChanged(
|
||||
inlay_modifiers == &event.modifiers,
|
||||
),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
if editor.hover_state.focused(window, cx) {
|
||||
return;
|
||||
}
|
||||
@@ -547,7 +555,7 @@ impl EditorElement {
|
||||
let mut modifiers = event.modifiers;
|
||||
|
||||
if let Some(hovered_hunk) = hovered_hunk {
|
||||
editor.toggle_diff_hunks_in_ranges_narrow(vec![hovered_hunk], cx);
|
||||
editor.toggle_single_diff_hunk(hovered_hunk, cx);
|
||||
cx.notify();
|
||||
return;
|
||||
} else if gutter_hitbox.is_hovered(window) {
|
||||
@@ -853,7 +861,6 @@ impl EditorElement {
|
||||
let modifiers = event.modifiers;
|
||||
let gutter_hovered = gutter_hitbox.is_hovered(window);
|
||||
editor.set_gutter_hovered(gutter_hovered, cx);
|
||||
editor.mouse_cursor_hidden = false;
|
||||
|
||||
// Don't trigger hover popover if mouse is hovering over context menu
|
||||
if text_hitbox.is_hovered(window) {
|
||||
@@ -1554,50 +1561,11 @@ impl EditorElement {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
|
||||
let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(snapshot);
|
||||
let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(snapshot);
|
||||
|
||||
let mut display_hunks = Vec::<(DisplayDiffHunk, Option<Hitbox>)>::new();
|
||||
let folded_buffers = self.editor.read(cx).folded_buffers(cx);
|
||||
|
||||
for hunk in snapshot
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(buffer_start..buffer_end)
|
||||
{
|
||||
if folded_buffers.contains(&hunk.buffer_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
|
||||
let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
|
||||
|
||||
let hunk_display_start = snapshot.point_to_display_point(hunk_start_point, Bias::Left);
|
||||
let hunk_display_end = snapshot.point_to_display_point(hunk_end_point, Bias::Right);
|
||||
|
||||
let display_hunk = if hunk_display_start.column() != 0 {
|
||||
DisplayDiffHunk::Folded {
|
||||
display_row: hunk_display_start.row(),
|
||||
}
|
||||
} else {
|
||||
let mut end_row = hunk_display_end.row();
|
||||
if hunk_display_end.column() > 0 {
|
||||
end_row.0 += 1;
|
||||
}
|
||||
DisplayDiffHunk::Unfolded {
|
||||
status: hunk.status(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range,
|
||||
display_row_range: hunk_display_start.row()..end_row,
|
||||
multi_buffer_range: Anchor::range_in_buffer(
|
||||
hunk.excerpt_id,
|
||||
hunk.buffer_id,
|
||||
hunk.buffer_range,
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
display_hunks.push((display_hunk, None));
|
||||
}
|
||||
|
||||
let mut display_hunks = snapshot
|
||||
.display_diff_hunks_for_rows(display_rows, folded_buffers)
|
||||
.map(|hunk| (hunk, None))
|
||||
.collect::<Vec<_>>();
|
||||
let git_gutter_setting = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.git_gutter
|
||||
@@ -2063,21 +2031,22 @@ impl EditorElement {
|
||||
None
|
||||
};
|
||||
|
||||
let offset_range_start = snapshot
|
||||
.display_point_to_anchor(DisplayPoint::new(range.start, 0), Bias::Left)
|
||||
.to_offset(&snapshot.buffer_snapshot);
|
||||
let offset_range_end = snapshot
|
||||
.display_point_to_anchor(DisplayPoint::new(range.end, 0), Bias::Right)
|
||||
.to_offset(&snapshot.buffer_snapshot);
|
||||
let offset_range_start =
|
||||
snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
|
||||
|
||||
let offset_range_end =
|
||||
snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
|
||||
|
||||
editor
|
||||
.tasks
|
||||
.iter()
|
||||
.filter_map(|(_, tasks)| {
|
||||
if tasks.offset.0 < offset_range_start || tasks.offset.0 >= offset_range_end {
|
||||
let multibuffer_point = tasks.offset.to_point(&snapshot.buffer_snapshot);
|
||||
if multibuffer_point < offset_range_start
|
||||
|| multibuffer_point > offset_range_end
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let multibuffer_point = tasks.offset.0.to_point(&snapshot.buffer_snapshot);
|
||||
let multibuffer_row = MultiBufferRow(multibuffer_point.row);
|
||||
let buffer_folded = snapshot
|
||||
.buffer_snapshot
|
||||
@@ -2698,6 +2667,21 @@ impl EditorElement {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Div {
|
||||
let file_status = maybe!({
|
||||
let project = self.editor.read(cx).project.as_ref()?.read(cx);
|
||||
let (repo, path) =
|
||||
project.repository_and_path_for_buffer_id(for_excerpt.buffer_id, cx)?;
|
||||
let status = repo.read(cx).repository_entry.status_for_path(&path)?;
|
||||
Some(status.status)
|
||||
})
|
||||
.filter(|_| {
|
||||
self.editor
|
||||
.read(cx)
|
||||
.buffer
|
||||
.read(cx)
|
||||
.all_diff_hunks_expanded()
|
||||
});
|
||||
|
||||
let include_root = self
|
||||
.editor
|
||||
.read(cx)
|
||||
@@ -2801,12 +2785,36 @@ impl EditorElement {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
Label::new(
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
)
|
||||
.single_line()
|
||||
.when_some(
|
||||
file_status,
|
||||
|el, status| {
|
||||
el.color(if status.is_conflicted() {
|
||||
Color::Conflict
|
||||
} else if status.is_modified() {
|
||||
Color::Modified
|
||||
} else if status.is_deleted() {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Created
|
||||
})
|
||||
.when(status.is_deleted(), |el| el.strikethrough())
|
||||
},
|
||||
),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(div().child(path).text_color(colors.text_muted))
|
||||
then.child(div().child(path).text_color(
|
||||
if file_status.is_some_and(FileStatus::is_deleted) {
|
||||
colors.text_disabled
|
||||
} else {
|
||||
colors.text_muted
|
||||
},
|
||||
))
|
||||
}),
|
||||
)
|
||||
.when(is_selected, |el| {
|
||||
@@ -3712,391 +3720,6 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_edit_prediction_popover(
|
||||
&self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
scroll_top: f32,
|
||||
scroll_bottom: f32,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
editor_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
const PADDING_X: Pixels = Pixels(24.);
|
||||
const PADDING_Y: Pixels = Pixels(2.);
|
||||
|
||||
let editor = self.editor.read(cx);
|
||||
let active_inline_completion = editor.active_inline_completion.as_ref()?;
|
||||
|
||||
if editor.edit_prediction_visible_in_cursor_popover(true) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Adjust text origin for horizontal scrolling (in some cases here)
|
||||
let start_point = content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
|
||||
|
||||
// Clamp left offset after extreme scrollings
|
||||
let clamp_start = |point: gpui::Point<Pixels>| gpui::Point {
|
||||
x: point.x.max(content_origin.x),
|
||||
y: point.y,
|
||||
};
|
||||
|
||||
match &active_inline_completion.completion {
|
||||
InlineCompletion::Move { target, .. } => {
|
||||
let target_display_point = target.to_display_point(editor_snapshot);
|
||||
|
||||
if editor.edit_prediction_requires_modifier() {
|
||||
if !editor.edit_prediction_preview_is_active() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if target_display_point.row() < visible_row_range.start {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Scroll",
|
||||
Some(IconName::ArrowUp),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let cursor = newest_selection_head?;
|
||||
let cursor_row_layout = line_layouts
|
||||
.get(cursor.row().minus(visible_row_range.start) as usize)?;
|
||||
let cursor_column = cursor.column() as usize;
|
||||
|
||||
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
||||
|
||||
const PADDING_Y: Pixels = px(12.);
|
||||
|
||||
let origin = start_point + point(cursor_character_x, PADDING_Y);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
return Some((element, origin));
|
||||
} else if target_display_point.row() >= visible_row_range.end {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Scroll",
|
||||
Some(IconName::ArrowDown),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let cursor = newest_selection_head?;
|
||||
let cursor_row_layout = line_layouts
|
||||
.get(cursor.row().minus(visible_row_range.start) as usize)?;
|
||||
let cursor_column = cursor.column() as usize;
|
||||
|
||||
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
||||
const PADDING_Y: Pixels = px(12.);
|
||||
|
||||
let origin = start_point
|
||||
+ point(
|
||||
cursor_character_x,
|
||||
text_bounds.size.height - size.height - PADDING_Y,
|
||||
);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
return Some((element, origin));
|
||||
} else {
|
||||
const POLE_WIDTH: Pixels = px(2.);
|
||||
|
||||
let mut element = v_flex()
|
||||
.items_end()
|
||||
.child(
|
||||
editor
|
||||
.render_edit_prediction_line_popover("Jump", None, window, cx)?
|
||||
.rounded_br(px(0.))
|
||||
.rounded_tr(px(0.))
|
||||
.border_r_2(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(POLE_WIDTH)
|
||||
.bg(Editor::edit_prediction_callout_popover_border_color(cx))
|
||||
.h(line_height),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let line_layout =
|
||||
line_layouts
|
||||
.get(target_display_point.row().minus(visible_row_range.start)
|
||||
as usize)?;
|
||||
let target_column = target_display_point.column() as usize;
|
||||
|
||||
let target_x = line_layout.x_for_index(target_column);
|
||||
let target_y = (target_display_point.row().as_f32() * line_height)
|
||||
- scroll_pixel_position.y;
|
||||
|
||||
let origin = clamp_start(
|
||||
start_point + point(target_x, target_y)
|
||||
- point(size.width - POLE_WIDTH, size.height - line_height),
|
||||
);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
|
||||
return Some((element, origin));
|
||||
}
|
||||
}
|
||||
|
||||
if target_display_point.row().as_f32() < scroll_top {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Jump to Edit",
|
||||
Some(IconName::ArrowUp),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
|
||||
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Jump to Edit",
|
||||
Some(IconName::ArrowDown),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point(
|
||||
(text_bounds.size.width - size.width) / 2.,
|
||||
text_bounds.size.height - size.height - PADDING_Y,
|
||||
);
|
||||
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover("Jump to Edit", None, window, cx)?
|
||||
.into_any();
|
||||
let target_line_end = DisplayPoint::new(
|
||||
target_display_point.row(),
|
||||
editor_snapshot.line_len(target_display_point.row()),
|
||||
);
|
||||
let origin = self.editor.update(cx, |editor, _cx| {
|
||||
editor.display_to_pixel_point(target_line_end, editor_snapshot, window)
|
||||
})?;
|
||||
|
||||
let origin = clamp_start(start_point + origin + point(PADDING_X, px(0.)));
|
||||
element.prepaint_as_root(origin, AvailableSpace::min_size(), window, cx);
|
||||
Some((element, origin))
|
||||
}
|
||||
}
|
||||
InlineCompletion::Edit {
|
||||
edits,
|
||||
edit_preview,
|
||||
display_mode,
|
||||
snapshot,
|
||||
} => {
|
||||
if self.editor.read(cx).has_visible_completions_menu() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let edit_start = edits
|
||||
.first()
|
||||
.unwrap()
|
||||
.0
|
||||
.start
|
||||
.to_display_point(editor_snapshot);
|
||||
let edit_end = edits
|
||||
.last()
|
||||
.unwrap()
|
||||
.0
|
||||
.end
|
||||
.to_display_point(editor_snapshot);
|
||||
|
||||
let is_visible = visible_row_range.contains(&edit_start.row())
|
||||
|| visible_row_range.contains(&edit_end.row());
|
||||
if !is_visible {
|
||||
return None;
|
||||
}
|
||||
|
||||
match display_mode {
|
||||
EditDisplayMode::TabAccept => {
|
||||
let range = &edits.first()?.0;
|
||||
let target_display_point = range.end.to_display_point(editor_snapshot);
|
||||
|
||||
let target_line_end = DisplayPoint::new(
|
||||
target_display_point.row(),
|
||||
editor_snapshot.line_len(target_display_point.row()),
|
||||
);
|
||||
let (mut element, origin) = self.editor.update(cx, |editor, cx| {
|
||||
Some((
|
||||
editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Accept", None, window, cx,
|
||||
)?
|
||||
.into_any(),
|
||||
editor.display_to_pixel_point(
|
||||
target_line_end,
|
||||
editor_snapshot,
|
||||
window,
|
||||
)?,
|
||||
))
|
||||
})?;
|
||||
|
||||
let origin = clamp_start(start_point + origin + point(PADDING_X, px(0.)));
|
||||
element.prepaint_as_root(origin, AvailableSpace::min_size(), window, cx);
|
||||
return Some((element, origin));
|
||||
}
|
||||
EditDisplayMode::Inline => return None,
|
||||
EditDisplayMode::DiffPopover => {}
|
||||
}
|
||||
|
||||
let highlighted_edits = crate::inline_completion_edit_text(
|
||||
&snapshot,
|
||||
edits,
|
||||
edit_preview.as_ref()?,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
||||
let styled_text = highlighted_edits.to_styled_text(&style.text);
|
||||
let line_count = highlighted_edits.text.lines().count();
|
||||
|
||||
const BORDER_WIDTH: Pixels = px(1.);
|
||||
|
||||
let mut element = h_flex()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border(BORDER_WIDTH)
|
||||
.shadow_sm()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_l_lg()
|
||||
.when(line_count > 1, |el| el.rounded_br_lg())
|
||||
.pr_1()
|
||||
.child(styled_text),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h(line_height + BORDER_WIDTH * px(2.))
|
||||
.px_1p5()
|
||||
.gap_1()
|
||||
// Workaround: For some reason, there's a gap if we don't do this
|
||||
.ml(-BORDER_WIDTH)
|
||||
.shadow(smallvec![gpui::BoxShadow {
|
||||
color: gpui::black().opacity(0.05),
|
||||
offset: point(px(1.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}])
|
||||
.bg(Editor::edit_prediction_line_popover_bg_color(cx))
|
||||
.border(BORDER_WIDTH)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_r_lg()
|
||||
.children(editor.render_edit_prediction_accept_keybind(window, cx)),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let longest_row =
|
||||
editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
|
||||
let longest_line_width = if visible_row_range.contains(&longest_row) {
|
||||
line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
|
||||
} else {
|
||||
layout_line(
|
||||
longest_row,
|
||||
editor_snapshot,
|
||||
style,
|
||||
editor_width,
|
||||
|_| false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.width
|
||||
};
|
||||
|
||||
let viewport_bounds = Bounds::new(Default::default(), window.viewport_size())
|
||||
.extend(Edges {
|
||||
right: -Self::SCROLLBAR_WIDTH,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let x_after_longest =
|
||||
text_bounds.origin.x + longest_line_width + PADDING_X - scroll_pixel_position.x;
|
||||
|
||||
let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
// Fully visible if it can be displayed within the window (allow overlapping other
|
||||
// panes). However, this is only allowed if the popover starts within text_bounds.
|
||||
let can_position_to_the_right = x_after_longest < text_bounds.right()
|
||||
&& x_after_longest + element_bounds.width < viewport_bounds.right();
|
||||
|
||||
let mut origin = if can_position_to_the_right {
|
||||
point(
|
||||
x_after_longest,
|
||||
text_bounds.origin.y + edit_start.row().as_f32() * line_height
|
||||
- scroll_pixel_position.y,
|
||||
)
|
||||
} else {
|
||||
let cursor_row = newest_selection_head.map(|head| head.row());
|
||||
let above_edit = edit_start
|
||||
.row()
|
||||
.0
|
||||
.checked_sub(line_count as u32)
|
||||
.map(DisplayRow);
|
||||
let below_edit = Some(edit_end.row() + 1);
|
||||
let above_cursor = cursor_row
|
||||
.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
|
||||
let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
|
||||
|
||||
// Place the edit popover adjacent to the edit if there is a location
|
||||
// available that is onscreen and does not obscure the cursor. Otherwise,
|
||||
// place it adjacent to the cursor.
|
||||
let row_target = [above_edit, below_edit, above_cursor, below_cursor]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|&start_row| {
|
||||
let end_row = start_row + line_count as u32;
|
||||
visible_row_range.contains(&start_row)
|
||||
&& visible_row_range.contains(&end_row)
|
||||
&& cursor_row.map_or(true, |cursor_row| {
|
||||
!((start_row..end_row).contains(&cursor_row))
|
||||
})
|
||||
})?;
|
||||
|
||||
content_origin
|
||||
+ point(
|
||||
-scroll_pixel_position.x,
|
||||
row_target.as_f32() * line_height - scroll_pixel_position.y,
|
||||
)
|
||||
};
|
||||
|
||||
origin.x -= BORDER_WIDTH;
|
||||
|
||||
window.defer_draw(element, origin, 1);
|
||||
|
||||
// Do not return an element, since it will already be drawn due to defer_draw.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_mouse_context_menu(
|
||||
&self,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
@@ -4969,10 +4592,9 @@ impl EditorElement {
|
||||
bounds: layout.position_map.text_hitbox.bounds,
|
||||
}),
|
||||
|window| {
|
||||
let editor = self.editor.read(cx);
|
||||
let cursor_style = if editor.mouse_cursor_hidden {
|
||||
CursorStyle::None
|
||||
} else if editor
|
||||
let cursor_style = if self
|
||||
.editor
|
||||
.read(cx)
|
||||
.hovered_link_state
|
||||
.as_ref()
|
||||
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
|
||||
@@ -6240,7 +5862,7 @@ pub(crate) struct LineWithInvisibles {
|
||||
fragments: SmallVec<[LineFragment; 1]>,
|
||||
invisibles: Vec<Invisible>,
|
||||
len: usize,
|
||||
width: Pixels,
|
||||
pub(crate) width: Pixels,
|
||||
font_size: Pixels,
|
||||
}
|
||||
|
||||
@@ -6984,7 +6606,6 @@ impl Element for EditorElement {
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
// Offset the content_bounds from the text_bounds by the gutter margin (which
|
||||
// is roughly half a character wide) to make hit testing work more like how we want.
|
||||
let content_origin =
|
||||
@@ -7389,22 +7010,25 @@ impl Element for EditorElement {
|
||||
});
|
||||
|
||||
let (inline_completion_popover, inline_completion_popover_origin) = self
|
||||
.layout_edit_prediction_popover(
|
||||
&text_hitbox.bounds,
|
||||
content_origin,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
scroll_position.y,
|
||||
scroll_position.y + height_in_lines,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.render_edit_prediction_popover(
|
||||
&text_hitbox.bounds,
|
||||
content_origin,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
scroll_position.y,
|
||||
scroll_position.y + height_in_lines,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let mut inline_diagnostics = self.layout_inline_diagnostics(
|
||||
@@ -8210,7 +7834,7 @@ struct BlockLayout {
|
||||
style: BlockStyle,
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
pub fn layout_line(
|
||||
row: DisplayRow,
|
||||
snapshot: &EditorSnapshot,
|
||||
style: &EditorStyle,
|
||||
@@ -9156,11 +8780,12 @@ fn diff_hunk_controls(
|
||||
})
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
move |_event, _, cx| {
|
||||
move |_event, window, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.stage_or_unstage_diff_hunks(
|
||||
false,
|
||||
&[hunk_range.start..hunk_range.start],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -9185,11 +8810,12 @@ fn diff_hunk_controls(
|
||||
})
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
move |_event, _, cx| {
|
||||
move |_event, window, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.stage_or_unstage_diff_hunks(
|
||||
true,
|
||||
&[hunk_range.start..hunk_range.start],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1271,6 +1271,7 @@ mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use itertools::Itertools;
|
||||
use language::{DiagnosticEntry, Language, LanguageRegistry};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use multi_buffer::ToOffset;
|
||||
use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset};
|
||||
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
|
||||
use settings::Settings;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
@@ -447,11 +447,13 @@ fn show_hover(
|
||||
})
|
||||
.or_else(|| {
|
||||
let snapshot = &snapshot.buffer_snapshot;
|
||||
let offset_range = snapshot.syntax_ancestor(anchor..anchor)?.1;
|
||||
Some(
|
||||
snapshot.anchor_before(offset_range.start)
|
||||
..snapshot.anchor_after(offset_range.end),
|
||||
)
|
||||
match snapshot.syntax_ancestor(anchor..anchor)?.1 {
|
||||
MultiOrSingleBufferOffsetRange::Multi(range) => Some(
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end),
|
||||
),
|
||||
MultiOrSingleBufferOffsetRange::Single(_) => None,
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| anchor..anchor);
|
||||
|
||||
@@ -1534,6 +1536,7 @@ mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ pub struct InlayHintCache {
|
||||
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
|
||||
version: usize,
|
||||
pub(super) enabled: bool,
|
||||
modifiers_override: bool,
|
||||
enabled_in_settings: bool,
|
||||
update_tasks: HashMap<ExcerptId, TasksForRanges>,
|
||||
refresh_task: Task<()>,
|
||||
@@ -265,6 +266,7 @@ impl InlayHintCache {
|
||||
Self {
|
||||
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
|
||||
enabled: inlay_hint_settings.enabled,
|
||||
modifiers_override: false,
|
||||
enabled_in_settings: inlay_hint_settings.enabled,
|
||||
hints: HashMap::default(),
|
||||
update_tasks: HashMap::default(),
|
||||
@@ -295,8 +297,9 @@ impl InlayHintCache {
|
||||
// visibility would not change when updating the setting if they were ever toggled.
|
||||
if new_hint_settings.enabled != self.enabled_in_settings {
|
||||
self.enabled = new_hint_settings.enabled;
|
||||
self.enabled_in_settings = new_hint_settings.enabled;
|
||||
self.modifiers_override = false;
|
||||
};
|
||||
self.enabled_in_settings = new_hint_settings.enabled;
|
||||
self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms);
|
||||
self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
|
||||
let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
|
||||
@@ -323,6 +326,7 @@ impl InlayHintCache {
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
self.modifiers_override = false;
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
if self.hints.is_empty() {
|
||||
ControlFlow::Break(None)
|
||||
@@ -335,12 +339,39 @@ impl InlayHintCache {
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
self.modifiers_override = false;
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn modifiers_override(&mut self, new_override: bool) -> Option<bool> {
|
||||
if self.modifiers_override == new_override {
|
||||
return None;
|
||||
}
|
||||
self.modifiers_override = new_override;
|
||||
if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
|
||||
{
|
||||
self.clear();
|
||||
Some(false)
|
||||
} else {
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn toggle(&mut self, enabled: bool) -> bool {
|
||||
if self.enabled == enabled {
|
||||
return false;
|
||||
}
|
||||
self.enabled = enabled;
|
||||
self.modifiers_override = false;
|
||||
if !enabled {
|
||||
self.clear();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
|
||||
/// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
|
||||
/// followed by the delayed queries of the same range above and below the visible one.
|
||||
@@ -353,7 +384,8 @@ impl InlayHintCache {
|
||||
ignore_debounce: bool,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<InlaySplice> {
|
||||
if !self.enabled {
|
||||
if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let mut invalidated_hints = Vec::new();
|
||||
@@ -1288,6 +1320,7 @@ pub mod tests {
|
||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
|
||||
@@ -1391,6 +1424,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1493,6 +1527,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1712,6 +1747,7 @@ pub mod tests {
|
||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1871,6 +1907,7 @@ pub mod tests {
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: new_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1913,6 +1950,7 @@ pub mod tests {
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: another_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1967,6 +2005,7 @@ pub mod tests {
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: final_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -2038,6 +2077,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2169,6 +2209,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2467,6 +2508,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2811,6 +2853,7 @@ pub mod tests {
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2992,6 +3035,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -3023,6 +3067,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3114,6 +3159,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3187,6 +3233,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -3246,6 +3293,7 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1739,6 +1739,7 @@ mod tests {
|
||||
let file = TestFile {
|
||||
path: Path::new("").into(),
|
||||
root_name: String::new(),
|
||||
local_root: None,
|
||||
};
|
||||
assert_eq!(path_for_file(&file, 0, false, cx), None);
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ pub fn indented_line_beginning(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
stop_at_soft_boundaries: bool,
|
||||
stop_at_indent: bool,
|
||||
) -> DisplayPoint {
|
||||
let point = display_point.to_point(map);
|
||||
let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
|
||||
@@ -229,7 +230,7 @@ pub fn indented_line_beginning(
|
||||
if stop_at_soft_boundaries && soft_line_start > indent_start && display_point != soft_line_start
|
||||
{
|
||||
soft_line_start
|
||||
} else if stop_at_soft_boundaries && display_point != indent_start {
|
||||
} else if stop_at_indent && display_point != indent_start {
|
||||
indent_start
|
||||
} else {
|
||||
line_start
|
||||
@@ -237,7 +238,7 @@ pub fn indented_line_beginning(
|
||||
}
|
||||
|
||||
/// Returns a position of the end of line.
|
||||
|
||||
///
|
||||
/// If `stop_at_soft_boundaries` is true, the returned position is that of the
|
||||
/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped).
|
||||
/// Otherwise it's always going to be the end of a logical line.
|
||||
|
||||
@@ -3,7 +3,6 @@ pub(crate) mod autoscroll;
|
||||
pub(crate) mod scroll_amount;
|
||||
|
||||
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
|
||||
use crate::EditPredictionPreview;
|
||||
use crate::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
hover_popover::hide_hover,
|
||||
@@ -496,14 +495,8 @@ impl Editor {
|
||||
hide_hover(self, cx);
|
||||
let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
|
||||
|
||||
if let EditPredictionPreview::Active {
|
||||
previous_scroll_position,
|
||||
} = &mut self.edit_prediction_preview
|
||||
{
|
||||
if !autoscroll {
|
||||
previous_scroll_position.take();
|
||||
}
|
||||
}
|
||||
self.edit_prediction_preview
|
||||
.set_previous_scroll_position(None);
|
||||
|
||||
self.scroll_manager.set_scroll_position(
|
||||
scroll_position,
|
||||
|
||||
@@ -843,14 +843,14 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for MutableSelectionsCollection<'a> {
|
||||
impl Deref for MutableSelectionsCollection<'_> {
|
||||
type Target = SelectionsCollection;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.collection
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for MutableSelectionsCollection<'a> {
|
||||
impl DerefMut for MutableSelectionsCollection<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.collection
|
||||
}
|
||||
|
||||
@@ -1,93 +1,73 @@
|
||||
use crate::Editor;
|
||||
|
||||
use gpui::{App, AppContext as _, Task as AsyncTask, Window};
|
||||
use gpui::{App, Task, Window};
|
||||
use project::Location;
|
||||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
use text::{ToOffset, ToPoint};
|
||||
use workspace::Workspace;
|
||||
|
||||
fn task_context_with_editor(
|
||||
editor: &mut Editor,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AsyncTask<Option<TaskContext>> {
|
||||
let Some(project) = editor.project.clone() else {
|
||||
return AsyncTask::ready(None);
|
||||
};
|
||||
let (selection, buffer, editor_snapshot) = {
|
||||
let selection = editor.selections.newest_adjusted(cx);
|
||||
let Some((buffer, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)
|
||||
else {
|
||||
return AsyncTask::ready(None);
|
||||
impl Editor {
|
||||
pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
|
||||
let Some(project) = self.project.clone() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
(selection, buffer, snapshot)
|
||||
};
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
let buffer = location.buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let snapshot = buffer.snapshot();
|
||||
let starting_point = location.range.start.to_point(&snapshot);
|
||||
let starting_offset = starting_point.to_offset(&snapshot);
|
||||
for (_, tasks) in editor
|
||||
.tasks
|
||||
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
|
||||
{
|
||||
if !tasks
|
||||
.context_range
|
||||
.contains(&crate::BufferOffset(starting_offset))
|
||||
let (selection, buffer, editor_snapshot) = {
|
||||
let selection = self.selections.newest_adjusted(cx);
|
||||
let Some((buffer, _)) = self
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)
|
||||
else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
(selection, buffer, snapshot)
|
||||
};
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
let buffer = location.buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let snapshot = buffer.snapshot();
|
||||
let starting_point = location.range.start.to_point(&snapshot);
|
||||
let starting_offset = starting_point.to_offset(&snapshot);
|
||||
for (_, tasks) in self
|
||||
.tasks
|
||||
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
|
||||
{
|
||||
continue;
|
||||
if !tasks
|
||||
.context_range
|
||||
.contains(&crate::BufferOffset(starting_offset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (capture_name, value) in tasks.extra_variables.iter() {
|
||||
variables.insert(
|
||||
VariableName::Custom(capture_name.to_owned().into()),
|
||||
value.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
for (capture_name, value) in tasks.extra_variables.iter() {
|
||||
variables.insert(
|
||||
VariableName::Custom(capture_name.to_owned().into()),
|
||||
value.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
variables
|
||||
};
|
||||
variables
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_store().update(cx, |task_store, cx| {
|
||||
task_store.task_context_for_location(captured_variables, location, cx)
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_store().update(cx, |task_store, cx| {
|
||||
task_store.task_context_for_location(captured_variables, location, cx)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn task_context(
|
||||
workspace: &Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AsyncTask<TaskContext> {
|
||||
let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
else {
|
||||
return AsyncTask::ready(TaskContext::default());
|
||||
};
|
||||
editor.update(cx, |editor, cx| {
|
||||
let context_task = task_context_with_editor(editor, window, cx);
|
||||
cx.background_spawn(async move { context_task.await.unwrap_or_default() })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user