Compare commits
200 Commits
dynamic-2
...
split-quer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a9e53d561 | ||
|
|
7ee2511b89 | ||
|
|
1eb90e0400 | ||
|
|
72e73544bb | ||
|
|
586cf40d63 | ||
|
|
18f2fc9aa3 | ||
|
|
b1beed4ca9 | ||
|
|
8d37c1c6ea | ||
|
|
0680f6469b | ||
|
|
57a543ebe8 | ||
|
|
d69fb469bd | ||
|
|
eb413ba404 | ||
|
|
9182bd51f2 | ||
|
|
481a554923 | ||
|
|
c129e3b668 | ||
|
|
12e3c3b502 | ||
|
|
54afa6f69f | ||
|
|
55511d1591 | ||
|
|
6c0cb9eaa3 | ||
|
|
24e7b69f8f | ||
|
|
a4cdca5141 | ||
|
|
86cd87e993 | ||
|
|
88000eb7e2 | ||
|
|
ab5a462e0c | ||
|
|
79430fc7d2 | ||
|
|
f96e4ba84f | ||
|
|
7be1ffb9ec | ||
|
|
93a5d0ca29 | ||
|
|
328d98dddc | ||
|
|
76ab9e4d66 | ||
|
|
c477c12956 | ||
|
|
1ffd87b87e | ||
|
|
df11b646da | ||
|
|
ed94bd41eb | ||
|
|
8949460bd7 | ||
|
|
c6c5907693 | ||
|
|
dea928b00c | ||
|
|
77b2da2b42 | ||
|
|
5a8c2a4a88 | ||
|
|
d46e494bd9 | ||
|
|
82435075a5 | ||
|
|
40748b0a15 | ||
|
|
3ee3c6a3bd | ||
|
|
6cc3a4d95c | ||
|
|
b58dfe502e | ||
|
|
03e2f240ee | ||
|
|
145cd798c0 | ||
|
|
9ef9baef6f | ||
|
|
d2a2faf7a2 | ||
|
|
10f7ca65cf | ||
|
|
354427413a | ||
|
|
9813297892 | ||
|
|
78bc3a9a36 | ||
|
|
73de99bee0 | ||
|
|
0ed1b29b01 | ||
|
|
5b754915e4 | ||
|
|
9298d3b525 | ||
|
|
89739d5874 | ||
|
|
d272e402ea | ||
|
|
5c93506e9f | ||
|
|
7df8b6fe10 | ||
|
|
6fba1e46a8 | ||
|
|
988ee93a81 | ||
|
|
00a505e41a | ||
|
|
ed9f6e2141 | ||
|
|
fe7d53cb96 | ||
|
|
edca195e3c | ||
|
|
d3b3e072a7 | ||
|
|
6b04b668ad | ||
|
|
4072ad2858 | ||
|
|
cb0b8b4c4b | ||
|
|
c58a8f1a04 | ||
|
|
abb46473c9 | ||
|
|
9bdb154a9b | ||
|
|
f69c8ca74e | ||
|
|
04a79780d8 | ||
|
|
4dd05a80e0 | ||
|
|
44c479c50c | ||
|
|
c8709978a1 | ||
|
|
f78f6a6e1e | ||
|
|
fefc91c6ad | ||
|
|
3076567f6b | ||
|
|
6eb537643a | ||
|
|
40eb84109d | ||
|
|
51601cf6bd | ||
|
|
2c545ce0bc | ||
|
|
58e9952d7b | ||
|
|
25c8cf0c5c | ||
|
|
d501a877a0 | ||
|
|
97abf35529 | ||
|
|
0150192e26 | ||
|
|
710c387395 | ||
|
|
5a6c55149a | ||
|
|
d5b0df6efa | ||
|
|
4e2a08edb7 | ||
|
|
c20a1ee032 | ||
|
|
f5f73efa8a | ||
|
|
d8c93e1bfd | ||
|
|
95b06097ee | ||
|
|
963b0c010a | ||
|
|
558808b97d | ||
|
|
4b19eac5c8 | ||
|
|
47174cea50 | ||
|
|
0129d4e250 | ||
|
|
3d4f275c52 | ||
|
|
cd2533de5a | ||
|
|
acc9648753 | ||
|
|
bc35235800 | ||
|
|
2ca83b2f17 | ||
|
|
ddf07253c4 | ||
|
|
aff7a83815 | ||
|
|
8524e87319 | ||
|
|
59c005b086 | ||
|
|
522692ef50 | ||
|
|
d665f28671 | ||
|
|
8c4fb34f6e | ||
|
|
d4891a62bb | ||
|
|
17bc0d1b17 | ||
|
|
db0d843fb1 | ||
|
|
ad4e52842c | ||
|
|
ca18549e02 | ||
|
|
5cbb360952 | ||
|
|
5ff7c893be | ||
|
|
99e4b3a4cf | ||
|
|
9af4b6bfc7 | ||
|
|
c84d432b5f | ||
|
|
6cea9813ad | ||
|
|
490a75aee6 | ||
|
|
4f364d6d09 | ||
|
|
89d2ace713 | ||
|
|
84a44bef8a | ||
|
|
6b9ddbfef2 | ||
|
|
8af8493da6 | ||
|
|
39edbe1c50 | ||
|
|
f6fa6600bc | ||
|
|
b55961b57a | ||
|
|
01b836a191 | ||
|
|
41180b8d81 | ||
|
|
81475ac4cd | ||
|
|
ba59e66314 | ||
|
|
5ede48337c | ||
|
|
3701e190ce | ||
|
|
5dc26c261d | ||
|
|
64d815a176 | ||
|
|
5dc54863a4 | ||
|
|
e4ba336971 | ||
|
|
195a270e18 | ||
|
|
3a26a4809d | ||
|
|
479c5df491 | ||
|
|
51404d4ea0 | ||
|
|
05c4c7872c | ||
|
|
0af6e442a7 | ||
|
|
7003b0f211 | ||
|
|
f489c8b79f | ||
|
|
be02b2faf4 | ||
|
|
258a8a37d8 | ||
|
|
78e0f71a28 | ||
|
|
59104a08fd | ||
|
|
7aa28c9b24 | ||
|
|
bb1d52b485 | ||
|
|
ca035dbdd8 | ||
|
|
71cc95d315 | ||
|
|
3707734f0a | ||
|
|
e19627d92f | ||
|
|
bb75d87285 | ||
|
|
eecbf203dc | ||
|
|
7fe5c27597 | ||
|
|
221edfc267 | ||
|
|
d95c424d18 | ||
|
|
d6d56191da | ||
|
|
2e87e1d26e | ||
|
|
e8862c45cc | ||
|
|
0c28b6a11a | ||
|
|
16fce64d3a | ||
|
|
b075ce8f04 | ||
|
|
54828ab836 | ||
|
|
6322351f00 | ||
|
|
78091fa91e | ||
|
|
d5735dab9a | ||
|
|
c793bbde84 | ||
|
|
03c54623d4 | ||
|
|
0afb3abfd2 | ||
|
|
2b46a4a0e9 | ||
|
|
bedf57db89 | ||
|
|
4855da53df | ||
|
|
15d3e54ae3 | ||
|
|
064bdab459 | ||
|
|
38cb95f427 | ||
|
|
7cc2538fe1 | ||
|
|
fc19cc0ddf | ||
|
|
e6def62c23 | ||
|
|
ff2347dff5 | ||
|
|
6319ae0b4a | ||
|
|
a8bd602334 | ||
|
|
af45db6d1e | ||
|
|
fab4b01655 | ||
|
|
2f6cb49d84 | ||
|
|
411ee7a47c | ||
|
|
831f7dbbc0 | ||
|
|
78fd378702 |
@@ -1,15 +0,0 @@
|
|||||||
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
|
|
||||||
# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
|
|
||||||
# with preference for settings from Zeds config.toml.
|
|
||||||
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
|
|
||||||
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
|
|
||||||
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
|
|
||||||
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
|
|
||||||
# would be incovenient.
|
|
||||||
# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
|
|
||||||
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
|
|
||||||
[build]
|
|
||||||
rustflags = ["-D", "warnings"]
|
|
||||||
|
|
||||||
[alias]
|
|
||||||
xtask = "run --package xtask --"
|
|
||||||
5
.cargo/collab-config.toml
Normal file
5
.cargo/collab-config.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# This file is used to build collab in a Docker image.
|
||||||
|
# In particular, we don't use clang.
|
||||||
|
[build]
|
||||||
|
# v0 mangling scheme provides more detailed backtraces around closures
|
||||||
|
rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
|
||||||
38
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
38
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -2,23 +2,23 @@ name: Feature Request
|
|||||||
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
||||||
labels: ["admin read", "triage", "enhancement"]
|
labels: ["admin read", "triage", "enhancement"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Check for existing issues
|
label: Check for existing issues
|
||||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||||
options:
|
options:
|
||||||
- label: Completed
|
- label: Completed
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Describe the feature
|
|
||||||
description: A clear and concise description of what you want to happen.
|
|
||||||
validations:
|
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: |
|
label: Describe the feature
|
||||||
If applicable, add mockups / screenshots to help present your vision of the feature
|
description: A clear and concise description of what you want to happen.
|
||||||
description: Drag images into the text input below
|
validations:
|
||||||
validations:
|
required: true
|
||||||
required: false
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: |
|
||||||
|
If applicable, add mockups / screenshots to help present your vision of the feature
|
||||||
|
description: Drag images into the text input below
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|||||||
75
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
75
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -1,40 +1,45 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: |
|
description: |
|
||||||
Use this template for **non-crash-related** bug reports.
|
Use this template for **non-crash-related** bug reports.
|
||||||
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||||
labels: ["admin read", "triage", "defect"]
|
labels: ["admin read", "triage", "defect"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Check for existing issues
|
label: Check for existing issues
|
||||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||||
options:
|
options:
|
||||||
- label: Completed
|
- label: Completed
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Describe the bug / provide steps to reproduce it
|
|
||||||
description: A clear and concise description of what the bug is.
|
|
||||||
validations:
|
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: environment
|
attributes:
|
||||||
attributes:
|
label: Describe the bug / provide steps to reproduce it
|
||||||
label: Environment
|
description: A clear and concise description of what the bug is.
|
||||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
validations:
|
||||||
validations:
|
required: true
|
||||||
required: true
|
- type: textarea
|
||||||
- type: textarea
|
id: environment
|
||||||
attributes:
|
attributes:
|
||||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
label: Environment
|
||||||
description: Drag issues into the text input below
|
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||||
description: |
|
description: Drag issues into the text input below
|
||||||
Drag Zed.log into the text input below.
|
validations:
|
||||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
required: false
|
||||||
validations:
|
- type: textarea
|
||||||
required: false
|
attributes:
|
||||||
|
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||||
|
description: |
|
||||||
|
Drag Zed.log into the text input below.
|
||||||
|
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||||
|
value: |
|
||||||
|
<details><summary>Zed.log</summary><pre>
|
||||||
|
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||||
|
|
||||||
|
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|||||||
61
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
61
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -1,33 +1,38 @@
|
|||||||
name: Crash Report
|
name: Crash Report
|
||||||
description: |
|
description: |
|
||||||
Use this template for crash reports.
|
Use this template for crash reports.
|
||||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Check for existing issues
|
label: Check for existing issues
|
||||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||||
options:
|
options:
|
||||||
- label: Completed
|
- label: Completed
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Describe the bug / provide steps to reproduce it
|
|
||||||
description: A clear and concise description of what the bug is.
|
|
||||||
validations:
|
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: environment
|
attributes:
|
||||||
attributes:
|
label: Describe the bug / provide steps to reproduce it
|
||||||
label: Environment
|
description: A clear and concise description of what the bug is.
|
||||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
validations:
|
||||||
validations:
|
required: true
|
||||||
required: true
|
- type: textarea
|
||||||
- type: textarea
|
id: environment
|
||||||
attributes:
|
attributes:
|
||||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
label: Environment
|
||||||
description: |
|
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||||
Drag Zed.log into the text input below.
|
validations:
|
||||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
required: true
|
||||||
validations:
|
- type: textarea
|
||||||
required: false
|
attributes:
|
||||||
|
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||||
|
description: |
|
||||||
|
Drag Zed.log into the text input below.
|
||||||
|
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||||
|
value: |
|
||||||
|
<details><summary>Zed.log</summary><pre>
|
||||||
|
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||||
|
|
||||||
|
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|||||||
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -38,9 +38,6 @@ jobs:
|
|||||||
- name: Remove untracked files
|
- name: Remove untracked files
|
||||||
run: git clean -df
|
run: git clean -df
|
||||||
|
|
||||||
- name: Set up default .cargo/config.toml
|
|
||||||
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
run: |
|
run: |
|
||||||
if ! which typos > /dev/null; then
|
if ! which typos > /dev/null; then
|
||||||
@@ -54,6 +51,9 @@ jobs:
|
|||||||
- name: Check unused dependencies
|
- name: Check unused dependencies
|
||||||
uses: bnjbvr/cargo-machete@main
|
uses: bnjbvr/cargo-machete@main
|
||||||
|
|
||||||
|
- name: Check licenses are present
|
||||||
|
run: script/check-licenses
|
||||||
|
|
||||||
- name: Check license generation
|
- name: Check license generation
|
||||||
run: script/generate-licenses /tmp/zed_licenses_output
|
run: script/generate-licenses /tmp/zed_licenses_output
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: cargo xtask clippy
|
run: ./script/clippy
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
@@ -117,7 +117,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: cargo xtask clippy
|
run: ./script/clippy
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: cargo xtask clippy
|
run: ./script/clippy
|
||||||
|
|
||||||
- name: Build Zed
|
- name: Build Zed
|
||||||
run: cargo build -p zed
|
run: cargo build -p zed
|
||||||
@@ -307,7 +307,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create and upload Linux .tar.gz bundle
|
- name: Create Linux .tar.gz bundle
|
||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||||
@@ -315,7 +315,7 @@ jobs:
|
|||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||||
with:
|
with:
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||||
path: zed-*.tar.gz
|
path: target/release/zed-*.tar.gz
|
||||||
|
|
||||||
- name: Upload app bundle to release
|
- name: Upload app bundle to release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -348,7 +348,7 @@ jobs:
|
|||||||
- name: Set up Clang
|
- name: Set up Clang
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libzstd-dev libvulkan1 libgit2-dev
|
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
|
|||||||
5
.github/workflows/deploy_collab.yml
vendored
5
.github/workflows/deploy_collab.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
uses: ./.github/actions/check_style
|
uses: ./.github/actions/check_style
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: cargo xtask clippy
|
run: ./script/clippy
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
@@ -75,6 +75,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
|
- name: Set up default .cargo/config.toml
|
||||||
|
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
|
||||||
|
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
uses: ./.github/actions/check_style
|
uses: ./.github/actions/check_style
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: cargo xtask clippy
|
run: ./script/clippy
|
||||||
tests:
|
tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Run tests
|
name: Run tests
|
||||||
|
|||||||
17
.mailmap
17
.mailmap
@@ -9,12 +9,18 @@
|
|||||||
# Keep these entries sorted alphabetically.
|
# Keep these entries sorted alphabetically.
|
||||||
# In Zed: `editor: sort lines case sensitive`
|
# In Zed: `editor: sort lines case sensitive`
|
||||||
|
|
||||||
|
Alex Viscreanu <alexviscreanu@gmail.com>
|
||||||
|
Alex Viscreanu <alexviscreanu@gmail.com> <alexandru.viscreanu@kiwi.com>
|
||||||
Antonio Scandurra <me@as-cii.com>
|
Antonio Scandurra <me@as-cii.com>
|
||||||
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||||
|
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>
|
||||||
Christian Bergschneider <christian.bergschneider@gmx.de>
|
Christian Bergschneider <christian.bergschneider@gmx.de>
|
||||||
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
||||||
Conrad Irwin <conrad@zed.dev>
|
Conrad Irwin <conrad@zed.dev>
|
||||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||||
|
Evren Sen <146845123+evrsen@users.noreply.github.com>
|
||||||
Fernando Tagawa <tagawafernando@gmail.com>
|
Fernando Tagawa <tagawafernando@gmail.com>
|
||||||
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
||||||
Greg Morenz <greg-morenz@droid.cafe>
|
Greg Morenz <greg-morenz@droid.cafe>
|
||||||
@@ -48,12 +54,23 @@ Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
|
|||||||
Nathan Sobo <nathan@zed.dev>
|
Nathan Sobo <nathan@zed.dev>
|
||||||
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
||||||
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
|
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
|
||||||
|
Peter Tripp <peter@zed.dev>
|
||||||
|
Peter Tripp <peter@zed.dev> <petertripp@gmail.com>
|
||||||
Petros Amoiridis <petros@hey.com>
|
Petros Amoiridis <petros@hey.com>
|
||||||
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
|
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
|
||||||
Piotr Osiewicz <piotr@zed.dev>
|
Piotr Osiewicz <piotr@zed.dev>
|
||||||
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
|
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
|
||||||
|
Rashid Almheiri <r.muhairi@pm.me>
|
||||||
|
Rashid Almheiri <r.muhairi@pm.me> <69181766+huwaireb@users.noreply.github.com>
|
||||||
|
Richard Feldman <oss@rtfeldman.com>
|
||||||
|
Richard Feldman <oss@rtfeldman.com> <richard@zed.dev>
|
||||||
Robert Clover <git@clo4.net>
|
Robert Clover <git@clo4.net>
|
||||||
Robert Clover <git@clo4.net> <robert@clover.gdn>
|
Robert Clover <git@clo4.net> <robert@clover.gdn>
|
||||||
|
Sergey Onufrienko <sergey@onufrienko.com>
|
||||||
Thorsten Ball <thorsten@zed.dev>
|
Thorsten Ball <thorsten@zed.dev>
|
||||||
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
|
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
|
||||||
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
|
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
|
||||||
|
Vitaly Slobodin <vitaliy.slobodin@gmail.com>
|
||||||
|
Vitaly Slobodin <vitaliy.slobodin@gmail.com> <vitaly_slobodin@fastmail.com>
|
||||||
|
WindSoilder <WindSoilder@outlook.com>
|
||||||
|
张小白 <364772080@qq.com>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": "clippy",
|
"label": "clippy",
|
||||||
"command": "cargo",
|
"command": "./script/clippy",
|
||||||
"args": ["xtask", "clippy"]
|
"args": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
914
Cargo.lock
generated
914
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
@@ -65,6 +65,7 @@ members = [
|
|||||||
"crates/open_ai",
|
"crates/open_ai",
|
||||||
"crates/outline",
|
"crates/outline",
|
||||||
"crates/outline_panel",
|
"crates/outline_panel",
|
||||||
|
"crates/paths",
|
||||||
"crates/picker",
|
"crates/picker",
|
||||||
"crates/prettier",
|
"crates/prettier",
|
||||||
"crates/project",
|
"crates/project",
|
||||||
@@ -77,6 +78,7 @@ members = [
|
|||||||
"crates/refineable/derive_refineable",
|
"crates/refineable/derive_refineable",
|
||||||
"crates/release_channel",
|
"crates/release_channel",
|
||||||
"crates/dev_server_projects",
|
"crates/dev_server_projects",
|
||||||
|
"crates/repl",
|
||||||
"crates/rich_text",
|
"crates/rich_text",
|
||||||
"crates/rope",
|
"crates/rope",
|
||||||
"crates/rpc",
|
"crates/rpc",
|
||||||
@@ -134,6 +136,7 @@ members = [
|
|||||||
"extensions/prisma",
|
"extensions/prisma",
|
||||||
"extensions/purescript",
|
"extensions/purescript",
|
||||||
"extensions/ruby",
|
"extensions/ruby",
|
||||||
|
"extensions/snippets",
|
||||||
"extensions/svelte",
|
"extensions/svelte",
|
||||||
"extensions/terraform",
|
"extensions/terraform",
|
||||||
"extensions/toml",
|
"extensions/toml",
|
||||||
@@ -214,6 +217,7 @@ ollama = { path = "crates/ollama" }
|
|||||||
open_ai = { path = "crates/open_ai" }
|
open_ai = { path = "crates/open_ai" }
|
||||||
outline = { path = "crates/outline" }
|
outline = { path = "crates/outline" }
|
||||||
outline_panel = { path = "crates/outline_panel" }
|
outline_panel = { path = "crates/outline_panel" }
|
||||||
|
paths = { path = "crates/paths" }
|
||||||
picker = { path = "crates/picker" }
|
picker = { path = "crates/picker" }
|
||||||
plugin = { path = "crates/plugin" }
|
plugin = { path = "crates/plugin" }
|
||||||
plugin_macros = { path = "crates/plugin_macros" }
|
plugin_macros = { path = "crates/plugin_macros" }
|
||||||
@@ -227,6 +231,7 @@ quick_action_bar = { path = "crates/quick_action_bar" }
|
|||||||
recent_projects = { path = "crates/recent_projects" }
|
recent_projects = { path = "crates/recent_projects" }
|
||||||
release_channel = { path = "crates/release_channel" }
|
release_channel = { path = "crates/release_channel" }
|
||||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||||
|
repl = { path = "crates/repl" }
|
||||||
rich_text = { path = "crates/rich_text" }
|
rich_text = { path = "crates/rich_text" }
|
||||||
rope = { path = "crates/rope" }
|
rope = { path = "crates/rope" }
|
||||||
rpc = { path = "crates/rpc" }
|
rpc = { path = "crates/rpc" }
|
||||||
@@ -264,19 +269,21 @@ workspace = { path = "crates/workspace" }
|
|||||||
zed = { path = "crates/zed" }
|
zed = { path = "crates/zed" }
|
||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
|
|
||||||
|
alacritty_terminal = "0.23"
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
any_vec = "0.13"
|
any_vec = "0.13"
|
||||||
ashpd = "0.8.0"
|
ashpd = "0.8.0"
|
||||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||||
|
async-dispatcher = { version = "0.1"}
|
||||||
async-fs = "1.6"
|
async-fs = "1.6"
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
async-tar = "0.4.2"
|
async-tar = "0.4.2"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||||
bitflags = "2.4.2"
|
bitflags = "2.4.2"
|
||||||
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
|
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||||
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
|
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||||
blade-util = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
|
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
||||||
cap-std = "3.0"
|
cap-std = "3.0"
|
||||||
cargo_toml = "0.20"
|
cargo_toml = "0.20"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
@@ -284,10 +291,10 @@ clap = { version = "4.4", features = ["derive"] }
|
|||||||
clickhouse = { version = "0.11.6" }
|
clickhouse = { version = "0.11.6" }
|
||||||
cocoa = "0.25"
|
cocoa = "0.25"
|
||||||
ctor = "0.2.6"
|
ctor = "0.2.6"
|
||||||
signal-hook = "0.3.17"
|
|
||||||
core-foundation = { version = "0.9.3" }
|
core-foundation = { version = "0.9.3" }
|
||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
|
dirs = "4.0"
|
||||||
emojis = "0.6.1"
|
emojis = "0.6.1"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
exec = "0.3.1"
|
exec = "0.3.1"
|
||||||
@@ -295,12 +302,13 @@ fork = "0.1.23"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-batch = "0.6.1"
|
futures-batch = "0.6.1"
|
||||||
futures-lite = "1.13"
|
futures-lite = "1.13"
|
||||||
git2 = { version = "0.18", default-features = false }
|
git2 = { version = "0.19", default-features = false }
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
html5ever = "0.27.0"
|
html5ever = "0.27.0"
|
||||||
ignore = "0.4.22"
|
ignore = "0.4.22"
|
||||||
|
image = "0.25.1"
|
||||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||||
indoc = "1"
|
indoc = "1"
|
||||||
# We explicitly disable http2 support in isahc.
|
# We explicitly disable http2 support in isahc.
|
||||||
@@ -330,6 +338,7 @@ rand = "0.8.5"
|
|||||||
refineable = { path = "./crates/refineable" }
|
refineable = { path = "./crates/refineable" }
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
repair_json = "0.1.0"
|
repair_json = "0.1.0"
|
||||||
|
runtimelib = { version="0.12", default-features = false, features = ["async-dispatcher-runtime"] }
|
||||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
@@ -345,6 +354,7 @@ serde_repr = "0.1"
|
|||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
shlex = "1.3.0"
|
shlex = "1.3.0"
|
||||||
|
signal-hook = "0.3.17"
|
||||||
similar = "1.3"
|
similar = "1.3"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
@@ -449,11 +459,12 @@ features = [
|
|||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
|
||||||
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
||||||
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }
|
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "4968e819c0d9b015437ffc694511e175801a17c7" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
debug = "limited"
|
debug = "limited"
|
||||||
|
codegen-units = 16
|
||||||
|
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
taffy = { opt-level = 3 }
|
taffy = { opt-level = 3 }
|
||||||
@@ -472,6 +483,11 @@ codegen-units = 1
|
|||||||
[profile.release.package]
|
[profile.release.package]
|
||||||
zed = { codegen-units = 16 }
|
zed = { codegen-units = 16 }
|
||||||
|
|
||||||
|
[profile.release-fast]
|
||||||
|
inherits = "release"
|
||||||
|
lto = false
|
||||||
|
codegen-units = 16
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
dbg_macro = "deny"
|
dbg_macro = "deny"
|
||||||
todo = "deny"
|
todo = "deny"
|
||||||
|
|||||||
1
assets/icons/rotate_ccw.svg
Normal file
1
assets/icons/rotate_ccw.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||||
|
After Width: | Height: | Size: 302 B |
1
assets/icons/text-cursor.svg
Normal file
1
assets/icons/text-cursor.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>
|
||||||
|
After Width: | Height: | Size: 345 B |
@@ -25,7 +25,8 @@
|
|||||||
],
|
],
|
||||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine"
|
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||||
|
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
[
|
[
|
||||||
// todo(linux): Review the editor bindings
|
|
||||||
// Standard Linux bindings
|
// Standard Linux bindings
|
||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -43,13 +42,8 @@
|
|||||||
"shift-tab": "editor::TabPrev",
|
"shift-tab": "editor::TabPrev",
|
||||||
"ctrl-k": "editor::CutToEndOfLine",
|
"ctrl-k": "editor::CutToEndOfLine",
|
||||||
"ctrl-t": "editor::Transpose",
|
"ctrl-t": "editor::Transpose",
|
||||||
// "ctrl-backspace": "editor::DeleteToBeginningOfLine",
|
|
||||||
// "ctrl-delete": "editor::DeleteToEndOfLine",
|
|
||||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
// "ctrl-w": "editor::DeleteToPreviousWordStart",
|
|
||||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||||
// "alt-h": "editor::DeleteToPreviousWordStart",
|
|
||||||
// "alt-d": "editor::DeleteToNextWordEnd",
|
|
||||||
"ctrl-x": "editor::Cut",
|
"ctrl-x": "editor::Cut",
|
||||||
"ctrl-insert": "editor::Copy",
|
"ctrl-insert": "editor::Copy",
|
||||||
"ctrl-c": "editor::Copy",
|
"ctrl-c": "editor::Copy",
|
||||||
@@ -59,25 +53,19 @@
|
|||||||
"ctrl-z": "editor::Undo",
|
"ctrl-z": "editor::Undo",
|
||||||
"ctrl-shift-z": "editor::Redo",
|
"ctrl-shift-z": "editor::Redo",
|
||||||
"up": "editor::MoveUp",
|
"up": "editor::MoveUp",
|
||||||
// "ctrl-up": "editor::MoveToStartOfParagraph", todo(linux) Should be "scroll down by 1 line"
|
"ctrl-up": "editor::LineUp",
|
||||||
|
"ctrl-down": "editor::LineDown",
|
||||||
"pageup": "editor::PageUp",
|
"pageup": "editor::PageUp",
|
||||||
// "shift-pageup": "editor::MovePageUp", todo(linux) should be 'select page up'
|
"shift-pageup": "editor::SelectPageUp",
|
||||||
"home": "editor::MoveToBeginningOfLine",
|
"home": "editor::MoveToBeginningOfLine",
|
||||||
"down": "editor::MoveDown",
|
"down": "editor::MoveDown",
|
||||||
// "ctrl-down": "editor::MoveToEndOfParagraph", todo(linux) should be "scroll up by 1 line"
|
|
||||||
"pagedown": "editor::PageDown",
|
"pagedown": "editor::PageDown",
|
||||||
// "shift-pagedown": "editor::MovePageDown", todo(linux) should be 'select page down'
|
"shift-pagedown": "editor::SelectPageDown",
|
||||||
"end": "editor::MoveToEndOfLine",
|
"end": "editor::MoveToEndOfLine",
|
||||||
"left": "editor::MoveLeft",
|
"left": "editor::MoveLeft",
|
||||||
"right": "editor::MoveRight",
|
"right": "editor::MoveRight",
|
||||||
"ctrl-left": "editor::MoveToPreviousWordStart",
|
"ctrl-left": "editor::MoveToPreviousWordStart",
|
||||||
// "alt-b": "editor::MoveToPreviousWordStart",
|
|
||||||
"ctrl-right": "editor::MoveToNextWordEnd",
|
"ctrl-right": "editor::MoveToNextWordEnd",
|
||||||
// "alt-f": "editor::MoveToNextWordEnd",
|
|
||||||
// "cmd-left": "editor::MoveToBeginningOfLine",
|
|
||||||
// "ctrl-a": "editor::MoveToBeginningOfLine",
|
|
||||||
// "cmd-right": "editor::MoveToEndOfLine",
|
|
||||||
// "ctrl-e": "editor::MoveToEndOfLine",
|
|
||||||
"ctrl-home": "editor::MoveToBeginning",
|
"ctrl-home": "editor::MoveToBeginning",
|
||||||
"ctrl-end": "editor::MoveToEnd",
|
"ctrl-end": "editor::MoveToEnd",
|
||||||
"shift-up": "editor::SelectUp",
|
"shift-up": "editor::SelectUp",
|
||||||
@@ -88,8 +76,6 @@
|
|||||||
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
||||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||||
// "ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
|
||||||
// "ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
|
||||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||||
"ctrl-shift-end": "editor::SelectToEnd",
|
"ctrl-shift-end": "editor::SelectToEnd",
|
||||||
"ctrl-a": "editor::SelectAll",
|
"ctrl-a": "editor::SelectAll",
|
||||||
@@ -166,7 +152,8 @@
|
|||||||
// "focus": false
|
// "focus": false
|
||||||
// }
|
// }
|
||||||
// ],
|
// ],
|
||||||
"ctrl->": "assistant::QuoteSelection"
|
"ctrl->": "assistant::QuoteSelection",
|
||||||
|
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -296,6 +283,13 @@
|
|||||||
"ctrl-alt-shift-x": "search::ToggleRegex"
|
"ctrl-alt-shift-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Terminal",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||||
|
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"]
|
||||||
|
}
|
||||||
|
},
|
||||||
// Bindings from VS Code
|
// Bindings from VS Code
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
|
|||||||
@@ -188,7 +188,8 @@
|
|||||||
"focus": false
|
"focus": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"cmd->": "assistant::QuoteSelection"
|
"cmd->": "assistant::QuoteSelection",
|
||||||
|
"cmd-alt-e": "editor::SelectEnclosingSymbol"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"right": "vim::Right",
|
"right": "vim::Right",
|
||||||
"space": "vim::Space",
|
"space": "vim::Space",
|
||||||
"$": "vim::EndOfLine",
|
"$": "vim::EndOfLine",
|
||||||
|
"end": "vim::EndOfLine",
|
||||||
"^": "vim::FirstNonWhitespace",
|
"^": "vim::FirstNonWhitespace",
|
||||||
"_": "vim::StartOfLineDownward",
|
"_": "vim::StartOfLineDownward",
|
||||||
"g _": "vim::EndOfLineDownward",
|
"g _": "vim::EndOfLineDownward",
|
||||||
@@ -140,7 +141,8 @@
|
|||||||
"ctrl-q": "vim::ToggleVisualBlock",
|
"ctrl-q": "vim::ToggleVisualBlock",
|
||||||
"shift-k": "editor::Hover",
|
"shift-k": "editor::Hover",
|
||||||
"shift-r": "vim::ToggleReplace",
|
"shift-r": "vim::ToggleReplace",
|
||||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
"0": "vim::StartOfLine",
|
||||||
|
"home": "vim::StartOfLine",
|
||||||
"ctrl-f": "vim::PageDown",
|
"ctrl-f": "vim::PageDown",
|
||||||
"pagedown": "vim::PageDown",
|
"pagedown": "vim::PageDown",
|
||||||
"ctrl-b": "vim::PageUp",
|
"ctrl-b": "vim::PageUp",
|
||||||
@@ -408,7 +410,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && VimCount",
|
"context": "Editor && VimCount && vim_mode != insert",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"0": ["vim::Number", 0]
|
"0": ["vim::Number", 0]
|
||||||
}
|
}
|
||||||
@@ -610,13 +612,13 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == normal && !VimWaiting",
|
"context": "Editor && vim_mode == normal && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g c c": "editor::ToggleComments"
|
"g c c": "vim::ToggleComments"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == visual",
|
"context": "Editor && vim_mode == visual",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g c": "editor::ToggleComments"
|
"g c": "vim::ToggleComments"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -634,8 +636,7 @@
|
|||||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||||
"ctrl-t": "vim::Indent",
|
"ctrl-t": "vim::Indent",
|
||||||
"ctrl-d": "vim::Outdent",
|
"ctrl-d": "vim::Outdent",
|
||||||
"ctrl-r \"": "editor::Paste",
|
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||||
"ctrl-r +": "editor::Paste"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -644,11 +645,13 @@
|
|||||||
"escape": "vim::NormalBefore",
|
"escape": "vim::NormalBefore",
|
||||||
"ctrl-c": "vim::NormalBefore",
|
"ctrl-c": "vim::NormalBefore",
|
||||||
"ctrl-[": "vim::NormalBefore",
|
"ctrl-[": "vim::NormalBefore",
|
||||||
|
"tab": "vim::Tab",
|
||||||
|
"enter": "vim::Enter",
|
||||||
"backspace": "vim::UndoReplace"
|
"backspace": "vim::UndoReplace"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && VimWaiting",
|
"context": "Editor && vim_mode != replace && VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "vim::Tab",
|
"tab": "vim::Tab",
|
||||||
"enter": "vim::Enter",
|
"enter": "vim::Enter",
|
||||||
@@ -656,6 +659,13 @@
|
|||||||
"ctrl-[": ["vim::SwitchMode", "Normal"]
|
"ctrl-[": ["vim::SwitchMode", "Normal"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_mode == insert && VimWaiting",
|
||||||
|
"bindings": {
|
||||||
|
"escape": "vim::NormalBefore",
|
||||||
|
"ctrl-[": "vim::NormalBefore"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && !in_replace",
|
"context": "BufferSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -131,7 +131,14 @@
|
|||||||
// The default number of lines to expand excerpts in the multibuffer by.
|
// The default number of lines to expand excerpts in the multibuffer by.
|
||||||
"expand_excerpt_lines": 3,
|
"expand_excerpt_lines": 3,
|
||||||
// Globs to match against file paths to determine if a file is private.
|
// Globs to match against file paths to determine if a file is private.
|
||||||
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
"private_files": [
|
||||||
|
"**/.env*",
|
||||||
|
"**/*.pem",
|
||||||
|
"**/*.key",
|
||||||
|
"**/*.cert",
|
||||||
|
"**/*.crt",
|
||||||
|
"**/secrets.yml"
|
||||||
|
],
|
||||||
// Whether to use additional LSP queries to format (and amend) the code after
|
// Whether to use additional LSP queries to format (and amend) the code after
|
||||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||||
"use_on_type_format": true,
|
"use_on_type_format": true,
|
||||||
@@ -139,15 +146,19 @@
|
|||||||
// opening parenthesis, bracket, brace, single or double quote characters.
|
// opening parenthesis, bracket, brace, single or double quote characters.
|
||||||
// For example, when you type (, Zed will add a closing ) at the correct position.
|
// For example, when you type (, Zed will add a closing ) at the correct position.
|
||||||
"use_autoclose": true,
|
"use_autoclose": true,
|
||||||
|
// Whether to automatically surround selected text when typing opening parenthesis,
|
||||||
|
// bracket, brace, single or double quote characters.
|
||||||
|
// For example, when you select text and type (, Zed will surround the text with ().
|
||||||
|
"use_auto_surround": true,
|
||||||
// Controls how the editor handles the autoclosed characters.
|
// Controls how the editor handles the autoclosed characters.
|
||||||
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
||||||
// happen only for auto-inserted characters.
|
// happen only for auto-inserted characters.
|
||||||
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
||||||
// no matter how they were inserted.
|
// no matter how they were inserted.
|
||||||
"always_treat_brackets_as_autoclosed": false,
|
"always_treat_brackets_as_autoclosed": false,
|
||||||
// Controls whether copilot provides suggestion immediately
|
// Controls whether inline completions are shown immediately (true)
|
||||||
// or waits for a `copilot::Toggle`
|
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||||
"show_copilot_suggestions": true,
|
"show_inline_completions": true,
|
||||||
// Whether to show tabs and spaces in the editor.
|
// Whether to show tabs and spaces in the editor.
|
||||||
// This setting can take three values:
|
// This setting can take three values:
|
||||||
//
|
//
|
||||||
@@ -176,7 +187,9 @@
|
|||||||
// Whether to show breadcrumbs.
|
// Whether to show breadcrumbs.
|
||||||
"breadcrumbs": true,
|
"breadcrumbs": true,
|
||||||
// Whether to show quick action buttons.
|
// Whether to show quick action buttons.
|
||||||
"quick_actions": true
|
"quick_actions": true,
|
||||||
|
// Whether to show the Selections menu in the editor toolbar
|
||||||
|
"selections_menu": true
|
||||||
},
|
},
|
||||||
// Scrollbar related settings
|
// Scrollbar related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
@@ -218,6 +231,8 @@
|
|||||||
"line_numbers": true,
|
"line_numbers": true,
|
||||||
// Whether to show code action buttons in the gutter.
|
// Whether to show code action buttons in the gutter.
|
||||||
"code_actions": true,
|
"code_actions": true,
|
||||||
|
// Whether to show runnables buttons in the gutter.
|
||||||
|
"runnables": true,
|
||||||
// Whether to show fold buttons in the gutter.
|
// Whether to show fold buttons in the gutter.
|
||||||
"folds": true
|
"folds": true
|
||||||
},
|
},
|
||||||
@@ -226,6 +241,8 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
/// The width of the indent guides in pixels, between 1 and 10.
|
/// The width of the indent guides in pixels, between 1 and 10.
|
||||||
"line_width": 1,
|
"line_width": 1,
|
||||||
|
/// The width of the active indent guide in pixels, between 1 and 10.
|
||||||
|
"active_line_width": 1,
|
||||||
/// Determines how indent guides are colored.
|
/// Determines how indent guides are colored.
|
||||||
/// This setting can take the following three values:
|
/// This setting can take the following three values:
|
||||||
///
|
///
|
||||||
@@ -240,6 +257,8 @@
|
|||||||
/// 2. "indent_aware"
|
/// 2. "indent_aware"
|
||||||
"background_coloring": "disabled"
|
"background_coloring": "disabled"
|
||||||
},
|
},
|
||||||
|
// Whether the editor will scroll beyond the last line.
|
||||||
|
"scroll_beyond_last_line": "one_page",
|
||||||
// The number of lines to keep above/below the cursor when scrolling.
|
// The number of lines to keep above/below the cursor when scrolling.
|
||||||
"vertical_scroll_margin": 3,
|
"vertical_scroll_margin": 3,
|
||||||
// Scroll sensitivity multiplier. This multiplier is applied
|
// Scroll sensitivity multiplier. This multiplier is applied
|
||||||
@@ -293,7 +312,14 @@
|
|||||||
"auto_reveal_entries": true,
|
"auto_reveal_entries": true,
|
||||||
/// Whether to fold directories automatically
|
/// Whether to fold directories automatically
|
||||||
/// when a directory has only one directory inside.
|
/// when a directory has only one directory inside.
|
||||||
"auto_fold_dirs": false
|
"auto_fold_dirs": false,
|
||||||
|
/// Scrollbar-related settings
|
||||||
|
"scrollbar": {
|
||||||
|
/// When to show the scrollbar in the project panel.
|
||||||
|
///
|
||||||
|
/// Default: always
|
||||||
|
"show": "always"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"outline_panel": {
|
"outline_panel": {
|
||||||
// Whether to show the outline panel button in the status bar
|
// Whether to show the outline panel button in the status bar
|
||||||
@@ -371,7 +397,13 @@
|
|||||||
// 3. "gpt-4-turbo-preview"
|
// 3. "gpt-4-turbo-preview"
|
||||||
// 4. "gpt-4o"
|
// 4. "gpt-4o"
|
||||||
"default_model": "gpt-4o"
|
"default_model": "gpt-4o"
|
||||||
}
|
},
|
||||||
|
// Whether to enable the /auto command in the assistant panel, which infers context.
|
||||||
|
// Enabling this also enables indexing all the files in the project to generate
|
||||||
|
// metadata used in context inference. The first time a project is indexed, indexing
|
||||||
|
// can take a long time and use a lot of system resources. Later indexing is incremental
|
||||||
|
// and much faster.
|
||||||
|
"infer_context": false
|
||||||
},
|
},
|
||||||
// Whether the screen sharing icon is shown in the os status bar.
|
// Whether the screen sharing icon is shown in the os status bar.
|
||||||
"show_call_status_icon": true,
|
"show_call_status_icon": true,
|
||||||
@@ -451,16 +483,16 @@
|
|||||||
// or falling back to formatting via language server:
|
// or falling back to formatting via language server:
|
||||||
// "formatter": "auto"
|
// "formatter": "auto"
|
||||||
"formatter": "auto",
|
"formatter": "auto",
|
||||||
// How to soft-wrap long lines of text. This setting can take
|
// How to soft-wrap long lines of text.
|
||||||
// three values:
|
// Possible values:
|
||||||
//
|
//
|
||||||
// 1. Do not soft wrap.
|
// 1. Do not soft wrap.
|
||||||
// "soft_wrap": "none",
|
// "soft_wrap": "none",
|
||||||
// 2. Prefer a single line generally, unless an overly long line is encountered.
|
// 2. Prefer a single line generally, unless an overly long line is encountered.
|
||||||
// "soft_wrap": "prefer_line",
|
// "soft_wrap": "prefer_line",
|
||||||
// 3. Soft wrap lines that overflow the editor:
|
// 3. Soft wrap lines that overflow the editor.
|
||||||
// "soft_wrap": "editor_width",
|
// "soft_wrap": "editor_width",
|
||||||
// 4. Soft wrap lines at the preferred line length
|
// 4. Soft wrap lines at the preferred line length.
|
||||||
// "soft_wrap": "preferred_line_length",
|
// "soft_wrap": "preferred_line_length",
|
||||||
"soft_wrap": "prefer_line",
|
"soft_wrap": "prefer_line",
|
||||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||||
@@ -516,9 +548,8 @@
|
|||||||
// "delay_ms": 600
|
// "delay_ms": 600
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"copilot": {
|
"inline_completions": {
|
||||||
// The set of glob patterns for which copilot should be disabled
|
// A list of globs representing files that inline completions should be disabled for.
|
||||||
// in any matching file.
|
|
||||||
"disabled_globs": [".env"]
|
"disabled_globs": [".env"]
|
||||||
},
|
},
|
||||||
// Settings specific to journaling
|
// Settings specific to journaling
|
||||||
@@ -723,7 +754,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"JavaScript": {
|
"JavaScript": {
|
||||||
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
@@ -766,7 +797,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TSX": {
|
"TSX": {
|
||||||
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
@@ -777,7 +808,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TypeScript": {
|
"TypeScript": {
|
||||||
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ use editor::Editor;
|
|||||||
use extension::ExtensionStore;
|
use extension::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
|
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
|
||||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
|
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
|
||||||
ViewContext, VisualContext as _,
|
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
|
||||||
|
VisualContext as _,
|
||||||
|
};
|
||||||
|
use language::{
|
||||||
|
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
||||||
};
|
};
|
||||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
|
||||||
use project::{LanguageServerProgress, Project};
|
use project::{LanguageServerProgress, Project};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp::Reverse, fmt::Write, sync::Arc};
|
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, ContextMenu};
|
||||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||||
|
|
||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
|
||||||
const WARNING_ICON: &str = "icons/warning.svg";
|
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ShowError { lsp_name: Arc<str>, error: String },
|
ShowError { lsp_name: Arc<str>, error: String },
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ pub struct ActivityIndicator {
|
|||||||
statuses: Vec<LspStatus>,
|
statuses: Vec<LspStatus>,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
auto_updater: Option<Model<AutoUpdater>>,
|
auto_updater: Option<Model<AutoUpdater>>,
|
||||||
|
context_menu: Option<View<ContextMenu>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LspStatus {
|
struct LspStatus {
|
||||||
@@ -35,14 +36,14 @@ struct LspStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct PendingWork<'a> {
|
struct PendingWork<'a> {
|
||||||
language_server_name: &'a str,
|
language_server_id: LanguageServerId,
|
||||||
progress_token: &'a str,
|
progress_token: &'a str,
|
||||||
progress: &'a LanguageServerProgress,
|
progress: &'a LanguageServerProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Content {
|
struct Content {
|
||||||
icon: Option<&'static str>,
|
icon: Option<gpui::AnyElement>,
|
||||||
message: String,
|
message: String,
|
||||||
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||||
}
|
}
|
||||||
@@ -78,6 +79,7 @@ impl ActivityIndicator {
|
|||||||
statuses: Default::default(),
|
statuses: Default::default(),
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
auto_updater,
|
auto_updater,
|
||||||
|
context_menu: None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,7 +153,7 @@ impl ActivityIndicator {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.language_server_statuses()
|
.language_server_statuses()
|
||||||
.rev()
|
.rev()
|
||||||
.filter_map(|status| {
|
.filter_map(|(server_id, status)| {
|
||||||
if status.pending_work.is_empty() {
|
if status.pending_work.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -159,7 +161,7 @@ impl ActivityIndicator {
|
|||||||
.pending_work
|
.pending_work
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(token, progress)| PendingWork {
|
.map(|(token, progress)| PendingWork {
|
||||||
language_server_name: status.name.as_str(),
|
language_server_id: server_id,
|
||||||
progress_token: token.as_str(),
|
progress_token: token.as_str(),
|
||||||
progress,
|
progress,
|
||||||
})
|
})
|
||||||
@@ -175,33 +177,44 @@ impl ActivityIndicator {
|
|||||||
// Show any language server has pending activity.
|
// Show any language server has pending activity.
|
||||||
let mut pending_work = self.pending_language_server_work(cx);
|
let mut pending_work = self.pending_language_server_work(cx);
|
||||||
if let Some(PendingWork {
|
if let Some(PendingWork {
|
||||||
language_server_name,
|
|
||||||
progress_token,
|
progress_token,
|
||||||
progress,
|
progress,
|
||||||
|
..
|
||||||
}) = pending_work.next()
|
}) = pending_work.next()
|
||||||
{
|
{
|
||||||
let mut message = language_server_name.to_string();
|
let mut message = progress
|
||||||
|
.title
|
||||||
message.push_str(": ");
|
.as_deref()
|
||||||
if let Some(progress_message) = progress.message.as_ref() {
|
.unwrap_or(progress_token)
|
||||||
message.push_str(progress_message);
|
.to_string();
|
||||||
} else {
|
|
||||||
message.push_str(progress_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(percentage) = progress.percentage {
|
if let Some(percentage) = progress.percentage {
|
||||||
write!(&mut message, " ({}%)", percentage).unwrap();
|
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(progress_message) = progress.message.as_ref() {
|
||||||
|
message.push_str(": ");
|
||||||
|
message.push_str(progress_message);
|
||||||
|
}
|
||||||
|
|
||||||
let additional_work_count = pending_work.count();
|
let additional_work_count = pending_work.count();
|
||||||
if additional_work_count > 0 {
|
if additional_work_count > 0 {
|
||||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Content {
|
return Content {
|
||||||
icon: None,
|
icon: Some(
|
||||||
|
Icon::new(IconName::ArrowCircle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message,
|
message,
|
||||||
on_click: None,
|
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +235,11 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
if !downloading.is_empty() {
|
if !downloading.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!("Downloading {}...", downloading.join(", "),),
|
message: format!("Downloading {}...", downloading.join(", "),),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
@@ -230,7 +247,11 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
if !checking_for_update.is_empty() {
|
if !checking_for_update.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Checking for updates to {}...",
|
"Checking for updates to {}...",
|
||||||
checking_for_update.join(", "),
|
checking_for_update.join(", "),
|
||||||
@@ -241,7 +262,11 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
if !failed.is_empty() {
|
if !failed.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(WARNING_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Failed to download {}. Click to show error.",
|
"Failed to download {}. Click to show error.",
|
||||||
failed.join(", "),
|
failed.join(", "),
|
||||||
@@ -255,7 +280,11 @@ impl ActivityIndicator {
|
|||||||
// Show any formatting failure
|
// Show any formatting failure
|
||||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(WARNING_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||||
on_click: Some(Arc::new(|_, cx| {
|
on_click: Some(Arc::new(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||||
@@ -267,17 +296,29 @@ impl ActivityIndicator {
|
|||||||
if let Some(updater) = &self.auto_updater {
|
if let Some(updater) = &self.auto_updater {
|
||||||
return match &updater.read(cx).status() {
|
return match &updater.read(cx).status() {
|
||||||
AutoUpdateStatus::Checking => Content {
|
AutoUpdateStatus::Checking => Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Checking for Zed updates…".to_string(),
|
message: "Checking for Zed updates…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Downloading => Content {
|
AutoUpdateStatus::Downloading => Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Downloading Zed update…".to_string(),
|
message: "Downloading Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Installing => Content {
|
AutoUpdateStatus::Installing => Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Installing Zed update…".to_string(),
|
message: "Installing Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
@@ -292,7 +333,11 @@ impl ActivityIndicator {
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Errored => Content {
|
AutoUpdateStatus::Errored => Content {
|
||||||
icon: Some(WARNING_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: "Auto update failed".to_string(),
|
message: "Auto update failed".to_string(),
|
||||||
on_click: Some(Arc::new(|this, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&Default::default(), cx)
|
this.dismiss_error_message(&Default::default(), cx)
|
||||||
@@ -307,7 +352,11 @@ impl ActivityIndicator {
|
|||||||
{
|
{
|
||||||
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(DOWNLOAD_ICON),
|
icon: Some(
|
||||||
|
Icon::new(IconName::Download)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
message: format!("Updating {extension_id} extension…"),
|
message: format!("Updating {extension_id} extension…"),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
@@ -316,6 +365,75 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
if self.context_menu.take().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.build_lsp_work_context_menu(cx);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
let mut has_work = false;
|
||||||
|
let this = cx.view().downgrade();
|
||||||
|
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
|
||||||
|
for work in self.pending_language_server_work(cx) {
|
||||||
|
has_work = true;
|
||||||
|
|
||||||
|
let this = this.clone();
|
||||||
|
let title = SharedString::from(
|
||||||
|
work.progress
|
||||||
|
.title
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(work.progress_token)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
if work.progress.is_cancellable {
|
||||||
|
let language_server_id = work.language_server_id;
|
||||||
|
let token = work.progress_token.to_string();
|
||||||
|
menu = menu.custom_entry(
|
||||||
|
move |_| {
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
|
.child(Label::new(title.clone()))
|
||||||
|
.child(Icon::new(IconName::XCircle))
|
||||||
|
.into_any_element()
|
||||||
|
},
|
||||||
|
move |cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.project.update(cx, |project, cx| {
|
||||||
|
project.cancel_language_server_work(
|
||||||
|
language_server_id,
|
||||||
|
Some(token.clone()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.context_menu.take();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
menu = menu.label(title.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu
|
||||||
|
});
|
||||||
|
|
||||||
|
if has_work {
|
||||||
|
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
|
||||||
|
this.context_menu.take();
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
cx.focus_view(&context_menu);
|
||||||
|
self.context_menu = Some(context_menu);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for ActivityIndicator {}
|
impl EventEmitter<Event> for ActivityIndicator {}
|
||||||
@@ -338,8 +456,17 @@ impl Render for ActivityIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
.children(content.icon.map(|icon| svg().path(icon)))
|
.gap_2()
|
||||||
|
.children(content.icon)
|
||||||
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
||||||
|
.children(self.context_menu.as_ref().map(|menu| {
|
||||||
|
deferred(
|
||||||
|
anchored()
|
||||||
|
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||||
|
.child(menu.clone()),
|
||||||
|
)
|
||||||
|
.with_priority(1)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
|
|||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||||
pub enum Model {
|
pub enum Model {
|
||||||
#[default]
|
#[default]
|
||||||
|
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3-5-sonnet-20240620")]
|
||||||
|
Claude3_5Sonnet,
|
||||||
#[serde(alias = "claude-3-opus", rename = "claude-3-opus-20240229")]
|
#[serde(alias = "claude-3-opus", rename = "claude-3-opus-20240229")]
|
||||||
Claude3Opus,
|
Claude3Opus,
|
||||||
#[serde(alias = "claude-3-sonnet", rename = "claude-3-sonnet-20240229")]
|
#[serde(alias = "claude-3-sonnet", rename = "claude-3-sonnet-20240229")]
|
||||||
@@ -22,7 +24,9 @@ pub enum Model {
|
|||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn from_id(id: &str) -> Result<Self> {
|
pub fn from_id(id: &str) -> Result<Self> {
|
||||||
if id.starts_with("claude-3-opus") {
|
if id.starts_with("claude-3-5-sonnet") {
|
||||||
|
Ok(Self::Claude3_5Sonnet)
|
||||||
|
} else if id.starts_with("claude-3-opus") {
|
||||||
Ok(Self::Claude3Opus)
|
Ok(Self::Claude3Opus)
|
||||||
} else if id.starts_with("claude-3-sonnet") {
|
} else if id.starts_with("claude-3-sonnet") {
|
||||||
Ok(Self::Claude3Sonnet)
|
Ok(Self::Claude3Sonnet)
|
||||||
@@ -35,6 +39,7 @@ impl Model {
|
|||||||
|
|
||||||
pub fn id(&self) -> &'static str {
|
pub fn id(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
|
||||||
Model::Claude3Opus => "claude-3-opus-20240229",
|
Model::Claude3Opus => "claude-3-opus-20240229",
|
||||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||||
Model::Claude3Haiku => "claude-3-opus-20240307",
|
Model::Claude3Haiku => "claude-3-opus-20240307",
|
||||||
@@ -43,6 +48,7 @@ impl Model {
|
|||||||
|
|
||||||
pub fn display_name(&self) -> &'static str {
|
pub fn display_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||||
Self::Claude3Opus => "Claude 3 Opus",
|
Self::Claude3Opus => "Claude 3 Opus",
|
||||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||||
Self::Claude3Haiku => "Claude 3 Haiku",
|
Self::Claude3Haiku => "Claude 3 Haiku",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ doctest = false
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
anthropic = { workspace = true, features = ["schemars"] }
|
anthropic = { workspace = true, features = ["schemars"] }
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
|
async-watch.workspace = true
|
||||||
cargo_toml.workspace = true
|
cargo_toml.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
@@ -39,6 +40,7 @@ ollama = { workspace = true, features = ["schemars"] }
|
|||||||
open_ai = { workspace = true, features = ["schemars"] }
|
open_ai = { workspace = true, features = ["schemars"] }
|
||||||
ordered-float.workspace = true
|
ordered-float.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
@@ -54,6 +56,7 @@ smol.workspace = true
|
|||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
|
terminal_view.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
tiktoken-rs.workspace = true
|
tiktoken-rs.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ mod search;
|
|||||||
mod slash_command;
|
mod slash_command;
|
||||||
mod streaming_diff;
|
mod streaming_diff;
|
||||||
|
|
||||||
pub use assistant_panel::AssistantPanel;
|
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
|
||||||
|
|
||||||
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
|
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
|
||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
use client::{proto, Client};
|
use client::{proto, Client};
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
pub(crate) use completion_provider::*;
|
pub(crate) use completion_provider::*;
|
||||||
pub(crate) use context_store::*;
|
pub(crate) use context_store::*;
|
||||||
|
use fs::Fs;
|
||||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||||
pub(crate) use inline_assistant::*;
|
pub(crate) use inline_assistant::*;
|
||||||
pub(crate) use model_selector::*;
|
pub(crate) use model_selector::*;
|
||||||
@@ -26,15 +26,15 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use slash_command::{
|
use slash_command::{
|
||||||
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
|
active_command, auto_command, default_command, diagnostics_command, fetch_command,
|
||||||
project_command, prompt_command, rustdoc_command, search_command, tabs_command,
|
file_command, now_command, project_command, prompt_command, rustdoc_command, search_command,
|
||||||
|
tabs_command, term_command,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
pub(crate) use streaming_diff::*;
|
pub(crate) use streaming_diff::*;
|
||||||
use util::paths::EMBEDDINGS_DIR;
|
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
assistant,
|
assistant,
|
||||||
@@ -139,7 +139,7 @@ impl LanguageModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
pub struct LanguageModelRequestMessage {
|
pub struct LanguageModelRequestMessage {
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
@@ -160,7 +160,7 @@ impl LanguageModelRequestMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Clone, Debug, Default, Serialize)]
|
||||||
pub struct LanguageModelRequest {
|
pub struct LanguageModelRequest {
|
||||||
pub model: LanguageModel,
|
pub model: LanguageModel,
|
||||||
pub messages: Vec<LanguageModelRequestMessage>,
|
pub messages: Vec<LanguageModelRequestMessage>,
|
||||||
@@ -187,7 +187,10 @@ impl LanguageModelRequest {
|
|||||||
LanguageModel::Anthropic(_) => {}
|
LanguageModel::Anthropic(_) => {}
|
||||||
LanguageModel::Ollama(_) => {}
|
LanguageModel::Ollama(_) => {}
|
||||||
LanguageModel::Cloud(model) => match model {
|
LanguageModel::Cloud(model) => match model {
|
||||||
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku => {
|
CloudModel::Claude3Opus
|
||||||
|
| CloudModel::Claude3Sonnet
|
||||||
|
| CloudModel::Claude3Haiku
|
||||||
|
| CloudModel::Claude3_5Sonnet => {
|
||||||
preprocess_anthropic_request(self);
|
preprocess_anthropic_request(self);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -262,7 +265,7 @@ impl Assistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
|
||||||
cx.set_global(Assistant::default());
|
cx.set_global(Assistant::default());
|
||||||
AssistantSettings::register(cx);
|
AssistantSettings::register(cx);
|
||||||
|
|
||||||
@@ -271,7 +274,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
async move {
|
async move {
|
||||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||||
let semantic_index = SemanticIndex::new(
|
let semantic_index = SemanticIndex::new(
|
||||||
EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
|
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||||
Arc::new(embedding_provider),
|
Arc::new(embedding_provider),
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
@@ -286,7 +289,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
assistant_slash_command::init(cx);
|
assistant_slash_command::init(cx);
|
||||||
register_slash_commands(cx);
|
register_slash_commands(cx);
|
||||||
assistant_panel::init(cx);
|
assistant_panel::init(cx);
|
||||||
inline_assistant::init(client.telemetry().clone(), cx);
|
inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
|
||||||
RustdocStore::init_global(cx);
|
RustdocStore::init_global(cx);
|
||||||
|
|
||||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
@@ -308,6 +311,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
|
|
||||||
fn register_slash_commands(cx: &mut AppContext) {
|
fn register_slash_commands(cx: &mut AppContext) {
|
||||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||||
|
slash_command_registry.register_command(auto_command::AutoCommand, true);
|
||||||
slash_command_registry.register_command(file_command::FileSlashCommand, true);
|
slash_command_registry.register_command(file_command::FileSlashCommand, true);
|
||||||
slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
|
slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
|
||||||
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
|
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
|
||||||
@@ -315,12 +319,31 @@ fn register_slash_commands(cx: &mut AppContext) {
|
|||||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
||||||
|
slash_command_registry.register_command(term_command::TermSlashCommand, true);
|
||||||
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
||||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
||||||
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
||||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn humanize_token_count(count: usize) -> String {
|
||||||
|
match count {
|
||||||
|
0..=999 => count.to_string(),
|
||||||
|
1000..=9999 => {
|
||||||
|
let thousands = count / 1000;
|
||||||
|
let hundreds = (count % 1000 + 50) / 100;
|
||||||
|
if hundreds == 0 {
|
||||||
|
format!("{}k", thousands)
|
||||||
|
} else if hundreds == 10 {
|
||||||
|
format!("{}k", thousands + 1)
|
||||||
|
} else {
|
||||||
|
format!("{}.{}k", thousands, hundreds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => format!("{}k", (count + 500) / 1000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
||||||
|
humanize_token_count,
|
||||||
prompt_library::open_prompt_library,
|
prompt_library::open_prompt_library,
|
||||||
search::*,
|
search::*,
|
||||||
slash_command::{
|
slash_command::{
|
||||||
@@ -15,32 +16,36 @@ use anyhow::{anyhow, Result};
|
|||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{BTreeSet, HashMap, HashSet};
|
use collections::{BTreeSet, HashMap, HashSet};
|
||||||
use editor::actions::ShowCompletions;
|
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
|
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
|
display_map::{
|
||||||
|
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, RenderBlock, ToDisplayPoint,
|
||||||
|
},
|
||||||
scroll::{Autoscroll, AutoscrollStrategy},
|
scroll::{Autoscroll, AutoscrollStrategy},
|
||||||
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
||||||
};
|
};
|
||||||
use editor::{display_map::FlapId, FoldPlaceholder};
|
use editor::{display_map::CreaseId, FoldPlaceholder};
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, point, rems, Action, AnyElement, AnyView, AppContext, AsyncAppContext, AsyncWindowContext,
|
div, percentage, point, rems, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
||||||
ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusableView,
|
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context as _, Empty, EventEmitter,
|
||||||
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
|
FocusHandle, FocusOutEvent, FocusableView, InteractiveElement, IntoElement, Model,
|
||||||
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UpdateGlobal, View,
|
ModelContext, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled,
|
||||||
ViewContext, VisualContext, WeakView, WindowContext,
|
Subscription, Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
|
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
|
||||||
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
|
use paths::contexts_dir;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
|
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
|
||||||
|
use rustdoc::{CrateName, RustdocStore};
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -54,10 +59,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
use telemetry_events::AssistantKind;
|
use telemetry_events::AssistantKind;
|
||||||
use ui::{
|
use ui::{
|
||||||
popover_menu, prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding,
|
prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
|
||||||
ListItem, ListItemSpacing, PopoverMenuHandle, Tab, TabBar, Tooltip,
|
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tab, TabBar, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{paths::CONTEXTS_DIR, post_inc, ResultExt, TryFutureExt};
|
use util::{post_inc, ResultExt, TryFutureExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use workspace::NewFile;
|
use workspace::NewFile;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
@@ -85,6 +90,10 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum AssistantPanelEvent {
|
||||||
|
ContextEdited,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AssistantPanel {
|
pub struct AssistantPanel {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
@@ -296,7 +305,7 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
|
||||||
self.toolbar
|
self.toolbar
|
||||||
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
|
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -356,11 +365,11 @@ impl AssistantPanel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
|
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let context_editor = assistant
|
let context_editor = assistant_panel
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.active_context_editor()
|
.active_context_editor()
|
||||||
.and_then(|editor| {
|
.and_then(|editor| {
|
||||||
@@ -387,25 +396,37 @@ impl AssistantPanel {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if assistant.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
|
if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx)) {
|
||||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
assistant.assist(
|
assistant.assist(
|
||||||
&active_editor,
|
&active_editor,
|
||||||
Some(cx.view().downgrade()),
|
Some(cx.view().downgrade()),
|
||||||
include_context,
|
include_context.then_some(&assistant_panel),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let assistant = assistant.downgrade();
|
let assistant_panel = assistant_panel.downgrade();
|
||||||
cx.spawn(|workspace, mut cx| async move {
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
assistant
|
assistant_panel
|
||||||
.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
|
.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
|
||||||
.await?;
|
.await?;
|
||||||
if assistant.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))? {
|
if assistant_panel
|
||||||
|
.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))?
|
||||||
|
{
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
let assistant_panel = if include_context {
|
||||||
|
assistant_panel.upgrade()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
assistant.assist(&active_editor, Some(workspace), include_context, cx)
|
assistant.assist(
|
||||||
|
&active_editor,
|
||||||
|
Some(workspace),
|
||||||
|
assistant_panel.as_ref(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
@@ -456,7 +477,7 @@ impl AssistantPanel {
|
|||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
});
|
});
|
||||||
self.show_saved_contexts = false;
|
self.show_saved_contexts = false;
|
||||||
|
cx.emit(AssistantPanelEvent::ContextEdited);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,6 +489,7 @@ impl AssistantPanel {
|
|||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
ContextEditorEvent::TabContentChanged => cx.notify(),
|
ContextEditorEvent::TabContentChanged => cx.notify(),
|
||||||
|
ContextEditorEvent::Edited => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +598,7 @@ impl AssistantPanel {
|
|||||||
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let assistant = cx.view().clone();
|
let assistant = cx.view().clone();
|
||||||
let zoomed = self.zoomed;
|
let zoomed = self.zoomed;
|
||||||
popover_menu("assistant-popover")
|
PopoverMenu::new("assistant-popover")
|
||||||
.trigger(IconButton::new("trigger", IconName::Menu))
|
.trigger(IconButton::new("trigger", IconName::Menu))
|
||||||
.menu(move |cx| {
|
.menu(move |cx| {
|
||||||
let assistant = assistant.clone();
|
let assistant = assistant.clone();
|
||||||
@@ -618,7 +640,7 @@ impl AssistantPanel {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
popover_menu("inject-context-menu")
|
PopoverMenu::new("inject-context-menu")
|
||||||
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
|
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
|
||||||
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
||||||
}))
|
}))
|
||||||
@@ -859,18 +881,33 @@ impl AssistantPanel {
|
|||||||
context: &Model<Context>,
|
context: &Model<Context>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<impl IntoElement> {
|
) -> Option<impl IntoElement> {
|
||||||
let remaining_tokens = context.read(cx).remaining_tokens(cx)?;
|
let model = CompletionProvider::global(cx).model();
|
||||||
let remaining_tokens_color = if remaining_tokens <= 0 {
|
let token_count = context.read(cx).token_count()?;
|
||||||
|
let max_token_count = model.max_token_count();
|
||||||
|
|
||||||
|
let remaining_tokens = max_token_count as isize - token_count as isize;
|
||||||
|
let token_count_color = if remaining_tokens <= 0 {
|
||||||
Color::Error
|
Color::Error
|
||||||
} else if remaining_tokens <= 500 {
|
} else if token_count as f32 / max_token_count as f32 >= 0.8 {
|
||||||
Color::Warning
|
Color::Warning
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
Label::new(remaining_tokens.to_string())
|
h_flex()
|
||||||
.size(LabelSize::Small)
|
.gap_0p5()
|
||||||
.color(remaining_tokens_color),
|
.child(
|
||||||
|
Label::new(humanize_token_count(token_count))
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(token_count_color),
|
||||||
|
)
|
||||||
|
.child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
|
||||||
|
.child(
|
||||||
|
Label::new(humanize_token_count(max_token_count))
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,6 +1011,7 @@ impl Panel for AssistantPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for AssistantPanel {}
|
impl EventEmitter<PanelEvent> for AssistantPanel {}
|
||||||
|
impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
|
||||||
|
|
||||||
impl FocusableView for AssistantPanel {
|
impl FocusableView for AssistantPanel {
|
||||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||||
@@ -1010,6 +1048,7 @@ pub struct Context {
|
|||||||
edit_suggestions: Vec<EditSuggestion>,
|
edit_suggestions: Vec<EditSuggestion>,
|
||||||
pending_slash_commands: Vec<PendingSlashCommand>,
|
pending_slash_commands: Vec<PendingSlashCommand>,
|
||||||
edits_since_last_slash_command_parse: language::Subscription,
|
edits_since_last_slash_command_parse: language::Subscription,
|
||||||
|
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||||
message_anchors: Vec<MessageAnchor>,
|
message_anchors: Vec<MessageAnchor>,
|
||||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
next_message_id: MessageId,
|
next_message_id: MessageId,
|
||||||
@@ -1051,6 +1090,7 @@ impl Context {
|
|||||||
next_message_id: Default::default(),
|
next_message_id: Default::default(),
|
||||||
edit_suggestions: Vec::new(),
|
edit_suggestions: Vec::new(),
|
||||||
pending_slash_commands: Vec::new(),
|
pending_slash_commands: Vec::new(),
|
||||||
|
slash_command_output_sections: Vec::new(),
|
||||||
edits_since_last_slash_command_parse,
|
edits_since_last_slash_command_parse,
|
||||||
summary: None,
|
summary: None,
|
||||||
pending_summary: Task::ready(None),
|
pending_summary: Task::ready(None),
|
||||||
@@ -1087,11 +1127,12 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
||||||
|
let buffer = self.buffer.read(cx);
|
||||||
SavedContext {
|
SavedContext {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
zed: "context".into(),
|
zed: "context".into(),
|
||||||
version: SavedContext::VERSION.into(),
|
version: SavedContext::VERSION.into(),
|
||||||
text: self.buffer.read(cx).text(),
|
text: buffer.text(),
|
||||||
message_metadata: self.messages_metadata.clone(),
|
message_metadata: self.messages_metadata.clone(),
|
||||||
messages: self
|
messages: self
|
||||||
.messages(cx)
|
.messages(cx)
|
||||||
@@ -1105,6 +1146,22 @@ impl Context {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|summary| summary.text.clone())
|
.map(|summary| summary.text.clone())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
slash_command_output_sections: self
|
||||||
|
.slash_command_output_sections
|
||||||
|
.iter()
|
||||||
|
.filter_map(|section| {
|
||||||
|
let range = section.range.to_offset(buffer);
|
||||||
|
if section.range.start.is_valid(buffer) && !range.is_empty() {
|
||||||
|
Some(SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon: section.icon,
|
||||||
|
label: section.label.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1156,6 +1213,19 @@ impl Context {
|
|||||||
next_message_id,
|
next_message_id,
|
||||||
edit_suggestions: Vec::new(),
|
edit_suggestions: Vec::new(),
|
||||||
pending_slash_commands: Vec::new(),
|
pending_slash_commands: Vec::new(),
|
||||||
|
slash_command_output_sections: saved_context
|
||||||
|
.slash_command_output_sections
|
||||||
|
.into_iter()
|
||||||
|
.map(|section| {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: buffer.anchor_after(section.range.start)
|
||||||
|
..buffer.anchor_before(section.range.end),
|
||||||
|
icon: section.icon,
|
||||||
|
label: section.label,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
edits_since_last_slash_command_parse,
|
edits_since_last_slash_command_parse,
|
||||||
summary: Some(Summary {
|
summary: Some(Summary {
|
||||||
text: saved_context.summary,
|
text: saved_context.summary,
|
||||||
@@ -1454,10 +1524,17 @@ impl Context {
|
|||||||
.map(|section| SlashCommandOutputSection {
|
.map(|section| SlashCommandOutputSection {
|
||||||
range: buffer.anchor_after(start + section.range.start)
|
range: buffer.anchor_after(start + section.range.start)
|
||||||
..buffer.anchor_before(start + section.range.end),
|
..buffer.anchor_before(start + section.range.end),
|
||||||
render_placeholder: section.render_placeholder,
|
icon: section.icon,
|
||||||
|
label: section.label,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
|
|
||||||
|
this.slash_command_output_sections
|
||||||
|
.extend(sections.iter().cloned());
|
||||||
|
this.slash_command_output_sections
|
||||||
|
.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
|
|
||||||
ContextEvent::SlashCommandFinished {
|
ContextEvent::SlashCommandFinished {
|
||||||
output_range: buffer.anchor_after(start)
|
output_range: buffer.anchor_after(start)
|
||||||
..buffer.anchor_before(new_end),
|
..buffer.anchor_before(new_end),
|
||||||
@@ -1495,11 +1572,6 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remaining_tokens(&self, cx: &AppContext) -> Option<isize> {
|
|
||||||
let model = CompletionProvider::global(cx).model();
|
|
||||||
Some(model.max_token_count() as isize - self.token_count? as isize)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
|
fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
self.count_remaining_tokens(cx);
|
self.count_remaining_tokens(cx);
|
||||||
}
|
}
|
||||||
@@ -2001,7 +2073,7 @@ impl Context {
|
|||||||
let mut discriminant = 1;
|
let mut discriminant = 1;
|
||||||
let mut new_path;
|
let mut new_path;
|
||||||
loop {
|
loop {
|
||||||
new_path = CONTEXTS_DIR.join(&format!(
|
new_path = contexts_dir().join(&format!(
|
||||||
"{} - {}.zed.json",
|
"{} - {}.zed.json",
|
||||||
summary.trim(),
|
summary.trim(),
|
||||||
discriminant
|
discriminant
|
||||||
@@ -2015,7 +2087,7 @@ impl Context {
|
|||||||
new_path
|
new_path
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.create_dir(CONTEXTS_DIR.as_ref()).await?;
|
fs.create_dir(contexts_dir().as_ref()).await?;
|
||||||
fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
|
fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
||||||
@@ -2140,6 +2212,7 @@ struct PendingCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ContextEditorEvent {
|
enum ContextEditorEvent {
|
||||||
|
Edited,
|
||||||
TabContentChanged,
|
TabContentChanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2158,7 +2231,8 @@ pub struct ContextEditor {
|
|||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
blocks: HashSet<BlockId>,
|
blocks: HashSet<BlockId>,
|
||||||
scroll_position: Option<ScrollPosition>,
|
scroll_position: Option<ScrollPosition>,
|
||||||
pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
|
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||||
|
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2209,6 +2283,7 @@ impl ContextEditor {
|
|||||||
editor.set_show_line_numbers(false, cx);
|
editor.set_show_line_numbers(false, cx);
|
||||||
editor.set_show_git_diff_gutter(false, cx);
|
editor.set_show_git_diff_gutter(false, cx);
|
||||||
editor.set_show_code_actions(false, cx);
|
editor.set_show_code_actions(false, cx);
|
||||||
|
editor.set_show_runnables(false, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_completion_provider(Box::new(completion_provider));
|
editor.set_completion_provider(Box::new(completion_provider));
|
||||||
@@ -2221,6 +2296,7 @@ impl ContextEditor {
|
|||||||
cx.subscribe(&editor, Self::handle_editor_event),
|
cx.subscribe(&editor, Self::handle_editor_event),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let sections = context.read(cx).slash_command_output_sections.clone();
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
context,
|
context,
|
||||||
editor,
|
editor,
|
||||||
@@ -2230,10 +2306,12 @@ impl ContextEditor {
|
|||||||
scroll_position: None,
|
scroll_position: None,
|
||||||
fs,
|
fs,
|
||||||
workspace: workspace.downgrade(),
|
workspace: workspace.downgrade(),
|
||||||
pending_slash_command_flaps: HashMap::default(),
|
pending_slash_command_creases: HashMap::default(),
|
||||||
|
pending_slash_command_blocks: HashMap::default(),
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
};
|
};
|
||||||
this.update_message_headers(cx);
|
this.update_message_headers(cx);
|
||||||
|
this.insert_slash_command_output_sections(sections, cx);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2491,16 +2569,27 @@ impl ContextEditor {
|
|||||||
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
|
||||||
|
let excerpt_id = *excerpt_id;
|
||||||
|
|
||||||
editor.remove_flaps(
|
editor.remove_creases(
|
||||||
removed
|
removed
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|range| self.pending_slash_command_flaps.remove(range)),
|
.filter_map(|range| self.pending_slash_command_creases.remove(range)),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let flap_ids = editor.insert_flaps(
|
editor.remove_blocks(
|
||||||
|
HashSet::from_iter(
|
||||||
|
removed.iter().filter_map(|range| {
|
||||||
|
self.pending_slash_command_blocks.remove(range)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
let crease_ids = editor.insert_creases(
|
||||||
updated.iter().map(|command| {
|
updated.iter().map(|command| {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let confirm_command = Arc::new({
|
let confirm_command = Arc::new({
|
||||||
@@ -2532,13 +2621,28 @@ impl ContextEditor {
|
|||||||
move |row, _, _, _cx: &mut WindowContext| {
|
move |row, _, _, _cx: &mut WindowContext| {
|
||||||
render_pending_slash_command_gutter_decoration(
|
render_pending_slash_command_gutter_decoration(
|
||||||
row,
|
row,
|
||||||
command.status.clone(),
|
&command.status,
|
||||||
confirm_command.clone(),
|
confirm_command.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let render_trailer =
|
let render_trailer = {
|
||||||
|_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
let command = command.clone();
|
||||||
|
move |row, _unfold, cx: &mut WindowContext| {
|
||||||
|
// TODO: In the future we should investigate how we can expose
|
||||||
|
// this as a hook on the `SlashCommand` trait so that we don't
|
||||||
|
// need to special-case it here.
|
||||||
|
if command.name == "rustdoc" {
|
||||||
|
return render_rustdoc_slash_command_trailer(
|
||||||
|
row,
|
||||||
|
command.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Empty.into_any()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let start = buffer
|
let start = buffer
|
||||||
.anchor_in_excerpt(excerpt_id, command.source_range.start)
|
.anchor_in_excerpt(excerpt_id, command.source_range.start)
|
||||||
@@ -2546,16 +2650,47 @@ impl ContextEditor {
|
|||||||
let end = buffer
|
let end = buffer
|
||||||
.anchor_in_excerpt(excerpt_id, command.source_range.end)
|
.anchor_in_excerpt(excerpt_id, command.source_range.end)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Flap::new(start..end, placeholder, render_toggle, render_trailer)
|
Crease::new(start..end, placeholder, render_toggle, render_trailer)
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.pending_slash_command_flaps.extend(
|
let block_ids = editor.insert_blocks(
|
||||||
|
updated
|
||||||
|
.iter()
|
||||||
|
.filter_map(|command| match &command.status {
|
||||||
|
PendingSlashCommandStatus::Error(error) => {
|
||||||
|
Some((command, error.clone()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|(command, error_message)| BlockProperties {
|
||||||
|
style: BlockStyle::Fixed,
|
||||||
|
position: Anchor {
|
||||||
|
buffer_id: Some(buffer_id),
|
||||||
|
excerpt_id,
|
||||||
|
text_anchor: command.source_range.start,
|
||||||
|
},
|
||||||
|
height: 1,
|
||||||
|
disposition: BlockDisposition::Below,
|
||||||
|
render: slash_command_error_block_renderer(error_message),
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.pending_slash_command_creases.extend(
|
||||||
updated
|
updated
|
||||||
.iter()
|
.iter()
|
||||||
.map(|command| command.source_range.clone())
|
.map(|command| command.source_range.clone())
|
||||||
.zip(flap_ids),
|
.zip(crease_ids),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.pending_slash_command_blocks.extend(
|
||||||
|
updated
|
||||||
|
.iter()
|
||||||
|
.map(|command| command.source_range.clone())
|
||||||
|
.zip(block_ids),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2598,7 +2733,7 @@ impl ContextEditor {
|
|||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||||
let mut buffer_rows_to_fold = BTreeSet::new();
|
let mut buffer_rows_to_fold = BTreeSet::new();
|
||||||
let mut flaps = Vec::new();
|
let mut creases = Vec::new();
|
||||||
for section in sections {
|
for section in sections {
|
||||||
let start = buffer
|
let start = buffer
|
||||||
.anchor_in_excerpt(excerpt_id, section.range.start)
|
.anchor_in_excerpt(excerpt_id, section.range.start)
|
||||||
@@ -2608,26 +2743,32 @@ impl ContextEditor {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||||
buffer_rows_to_fold.insert(buffer_row);
|
buffer_rows_to_fold.insert(buffer_row);
|
||||||
flaps.push(Flap::new(
|
creases.push(Crease::new(
|
||||||
start..end,
|
start..end,
|
||||||
FoldPlaceholder {
|
FoldPlaceholder {
|
||||||
render: Arc::new({
|
render: Arc::new({
|
||||||
let editor = cx.view().downgrade();
|
let editor = cx.view().downgrade();
|
||||||
let render_placeholder = section.render_placeholder.clone();
|
let icon = section.icon;
|
||||||
move |fold_id, fold_range, cx| {
|
let label = section.label.clone();
|
||||||
|
move |fold_id, fold_range, _cx| {
|
||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
let unfold = Arc::new(move |cx: &mut WindowContext| {
|
ButtonLike::new(fold_id)
|
||||||
editor
|
.style(ButtonStyle::Filled)
|
||||||
.update(cx, |editor, cx| {
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
let buffer_start = fold_range
|
.child(Icon::new(icon))
|
||||||
.start
|
.child(Label::new(label.clone()).single_line())
|
||||||
.to_point(&editor.buffer().read(cx).read(cx));
|
.on_click(move |_, cx| {
|
||||||
let buffer_row = MultiBufferRow(buffer_start.row);
|
editor
|
||||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
.update(cx, |editor, cx| {
|
||||||
})
|
let buffer_start = fold_range
|
||||||
.ok();
|
.start
|
||||||
});
|
.to_point(&editor.buffer().read(cx).read(cx));
|
||||||
render_placeholder(fold_id.into(), unfold, cx)
|
let buffer_row = MultiBufferRow(buffer_start.row);
|
||||||
|
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
constrain_width: false,
|
constrain_width: false,
|
||||||
@@ -2638,7 +2779,7 @@ impl ContextEditor {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.insert_flaps(flaps, cx);
|
editor.insert_creases(creases, cx);
|
||||||
|
|
||||||
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
||||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
@@ -2664,6 +2805,7 @@ impl ContextEditor {
|
|||||||
EditorEvent::SelectionsChanged { .. } => {
|
EditorEvent::SelectionsChanged { .. } => {
|
||||||
self.scroll_position = self.cursor_scroll_position(cx);
|
self.scroll_position = self.cursor_scroll_position(cx);
|
||||||
}
|
}
|
||||||
|
EditorEvent::BufferEdited => cx.emit(ContextEditorEvent::Edited),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3140,7 +3282,7 @@ fn render_slash_command_output_toggle(
|
|||||||
|
|
||||||
fn render_pending_slash_command_gutter_decoration(
|
fn render_pending_slash_command_gutter_decoration(
|
||||||
row: MultiBufferRow,
|
row: MultiBufferRow,
|
||||||
status: PendingSlashCommandStatus,
|
status: &PendingSlashCommandStatus,
|
||||||
confirm_command: Arc<dyn Fn(&mut WindowContext)>,
|
confirm_command: Arc<dyn Fn(&mut WindowContext)>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let mut icon = IconButton::new(
|
let mut icon = IconButton::new(
|
||||||
@@ -3158,16 +3300,43 @@ fn render_pending_slash_command_gutter_decoration(
|
|||||||
PendingSlashCommandStatus::Running { .. } => {
|
PendingSlashCommandStatus::Running { .. } => {
|
||||||
icon = icon.selected(true);
|
icon = icon.selected(true);
|
||||||
}
|
}
|
||||||
PendingSlashCommandStatus::Error(error) => {
|
PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
|
||||||
icon = icon
|
|
||||||
.icon_color(Color::Error)
|
|
||||||
.tooltip(move |cx| Tooltip::text(format!("error: {error}"), cx));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
icon.into_any_element()
|
icon.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_rustdoc_slash_command_trailer(
|
||||||
|
row: MultiBufferRow,
|
||||||
|
command: PendingSlashCommand,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> AnyElement {
|
||||||
|
let rustdoc_store = RustdocStore::global(cx);
|
||||||
|
|
||||||
|
let Some((crate_name, _)) = command
|
||||||
|
.argument
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|arg| arg.split_once(':'))
|
||||||
|
else {
|
||||||
|
return Empty.into_any();
|
||||||
|
};
|
||||||
|
|
||||||
|
let crate_name = CrateName::from(crate_name);
|
||||||
|
if !rustdoc_store.is_indexing(&crate_name) {
|
||||||
|
return Empty.into_any();
|
||||||
|
}
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(("crates-being-indexed", row.0))
|
||||||
|
.child(Icon::new(IconName::ArrowCircle).with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(4)).repeat(),
|
||||||
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
))
|
||||||
|
.tooltip(move |cx| Tooltip::text(format!("Indexing {crate_name}…"), cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
fn make_lsp_adapter_delegate(
|
fn make_lsp_adapter_delegate(
|
||||||
project: &Model<Project>,
|
project: &Model<Project>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
@@ -3182,6 +3351,19 @@ fn make_lsp_adapter_delegate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn slash_command_error_block_renderer(message: String) -> RenderBlock {
|
||||||
|
Box::new(move |_| {
|
||||||
|
div()
|
||||||
|
.pl_6()
|
||||||
|
.child(
|
||||||
|
Label::new(format!("error: {}", message))
|
||||||
|
.single_line()
|
||||||
|
.color(Color::Error),
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub enum CloudModel {
|
|||||||
Gpt4Turbo,
|
Gpt4Turbo,
|
||||||
#[default]
|
#[default]
|
||||||
Gpt4Omni,
|
Gpt4Omni,
|
||||||
|
Claude3_5Sonnet,
|
||||||
Claude3Opus,
|
Claude3Opus,
|
||||||
Claude3Sonnet,
|
Claude3Sonnet,
|
||||||
Claude3Haiku,
|
Claude3Haiku,
|
||||||
@@ -105,6 +106,7 @@ impl CloudModel {
|
|||||||
Self::Gpt4 => "gpt-4",
|
Self::Gpt4 => "gpt-4",
|
||||||
Self::Gpt4Turbo => "gpt-4-turbo-preview",
|
Self::Gpt4Turbo => "gpt-4-turbo-preview",
|
||||||
Self::Gpt4Omni => "gpt-4o",
|
Self::Gpt4Omni => "gpt-4o",
|
||||||
|
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
|
||||||
Self::Claude3Opus => "claude-3-opus",
|
Self::Claude3Opus => "claude-3-opus",
|
||||||
Self::Claude3Sonnet => "claude-3-sonnet",
|
Self::Claude3Sonnet => "claude-3-sonnet",
|
||||||
Self::Claude3Haiku => "claude-3-haiku",
|
Self::Claude3Haiku => "claude-3-haiku",
|
||||||
@@ -118,6 +120,7 @@ impl CloudModel {
|
|||||||
Self::Gpt4 => "GPT 4",
|
Self::Gpt4 => "GPT 4",
|
||||||
Self::Gpt4Turbo => "GPT 4 Turbo",
|
Self::Gpt4Turbo => "GPT 4 Turbo",
|
||||||
Self::Gpt4Omni => "GPT 4 Omni",
|
Self::Gpt4Omni => "GPT 4 Omni",
|
||||||
|
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||||
Self::Claude3Opus => "Claude 3 Opus",
|
Self::Claude3Opus => "Claude 3 Opus",
|
||||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||||
Self::Claude3Haiku => "Claude 3 Haiku",
|
Self::Claude3Haiku => "Claude 3 Haiku",
|
||||||
@@ -130,7 +133,10 @@ impl CloudModel {
|
|||||||
Self::Gpt3Point5Turbo => 2048,
|
Self::Gpt3Point5Turbo => 2048,
|
||||||
Self::Gpt4 => 4096,
|
Self::Gpt4 => 4096,
|
||||||
Self::Gpt4Turbo | Self::Gpt4Omni => 128000,
|
Self::Gpt4Turbo | Self::Gpt4Omni => 128000,
|
||||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 200000,
|
Self::Claude3_5Sonnet
|
||||||
|
| Self::Claude3Opus
|
||||||
|
| Self::Claude3Sonnet
|
||||||
|
| Self::Claude3Haiku => 200000,
|
||||||
Self::Custom(_) => 4096, // TODO: Make this configurable
|
Self::Custom(_) => 4096, // TODO: Make this configurable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,6 +225,7 @@ pub struct AssistantSettings {
|
|||||||
pub default_width: Pixels,
|
pub default_width: Pixels,
|
||||||
pub default_height: Pixels,
|
pub default_height: Pixels,
|
||||||
pub provider: AssistantProvider,
|
pub provider: AssistantProvider,
|
||||||
|
pub infer_context: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assistant panel settings
|
/// Assistant panel settings
|
||||||
@@ -276,6 +283,7 @@ impl AssistantSettingsContent {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
infer_context: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,6 +383,7 @@ impl Default for VersionedAssistantSettingsContent {
|
|||||||
default_width: None,
|
default_width: None,
|
||||||
default_height: None,
|
default_height: None,
|
||||||
provider: None,
|
provider: None,
|
||||||
|
infer_context: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,6 +415,14 @@ pub struct AssistantSettingsContentV1 {
|
|||||||
/// This can either be the internal `zed.dev` service or an external `openai` service,
|
/// This can either be the internal `zed.dev` service or an external `openai` service,
|
||||||
/// each with their respective default models and configurations.
|
/// each with their respective default models and configurations.
|
||||||
provider: Option<AssistantProviderContent>,
|
provider: Option<AssistantProviderContent>,
|
||||||
|
/// When using the assistant panel, enable the /auto command to automatically
|
||||||
|
/// infer context. Enabling this will enable background indexing of the project,
|
||||||
|
/// to generate the metadata /auto needs to infer context. The first time a project
|
||||||
|
/// is indexed, the indexing process can take a long time and use a lot of system
|
||||||
|
/// resources. After the first time, later indexing is incremental and much faster.
|
||||||
|
///
|
||||||
|
/// Default: false
|
||||||
|
infer_context: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
@@ -460,6 +477,7 @@ impl Settings for AssistantSettings {
|
|||||||
&mut settings.default_height,
|
&mut settings.default_height,
|
||||||
value.default_height.map(Into::into),
|
value.default_height.map(Into::into),
|
||||||
);
|
);
|
||||||
|
merge(&mut settings.infer_context, value.infer_context);
|
||||||
if let Some(provider) = value.provider.clone() {
|
if let Some(provider) = value.provider.clone() {
|
||||||
match (&mut settings.provider, provider) {
|
match (&mut settings.provider, provider) {
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ impl CloudCompletionProvider {
|
|||||||
count_open_ai_tokens(request, cx.background_executor())
|
count_open_ai_tokens(request, cx.background_executor())
|
||||||
}
|
}
|
||||||
LanguageModel::Cloud(
|
LanguageModel::Cloud(
|
||||||
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku,
|
CloudModel::Claude3_5Sonnet
|
||||||
|
| CloudModel::Claude3Opus
|
||||||
|
| CloudModel::Claude3Sonnet
|
||||||
|
| CloudModel::Claude3Haiku,
|
||||||
) => {
|
) => {
|
||||||
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
|
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
|
||||||
count_open_ai_tokens(request, cx.background_executor())
|
count_open_ai_tokens(request, cx.background_executor())
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ pub fn count_open_ai_tokens(
|
|||||||
|
|
||||||
match request.model {
|
match request.model {
|
||||||
LanguageModel::Anthropic(_)
|
LanguageModel::Anthropic(_)
|
||||||
|
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3Opus)
|
| LanguageModel::Cloud(CloudModel::Claude3Opus)
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
|
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
|
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{AppContext, Model, ModelContext, Task};
|
use gpui::{AppContext, Model, ModelContext, Task};
|
||||||
|
use paths::contexts_dir;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use ui::Context;
|
use ui::Context;
|
||||||
use util::{paths::CONTEXTS_DIR, ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct SavedMessage {
|
pub struct SavedMessage {
|
||||||
@@ -26,10 +28,22 @@ pub struct SavedContext {
|
|||||||
pub messages: Vec<SavedMessage>,
|
pub messages: Vec<SavedMessage>,
|
||||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
|
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SavedContext {
|
impl SavedContext {
|
||||||
pub const VERSION: &'static str = "0.2.0";
|
pub const VERSION: &'static str = "0.3.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SavedContextV0_2_0 {
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub zed: String,
|
||||||
|
pub version: String,
|
||||||
|
pub text: String,
|
||||||
|
pub messages: Vec<SavedMessage>,
|
||||||
|
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
|
pub summary: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -62,7 +76,7 @@ impl ContextStore {
|
|||||||
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||||
let (mut events, _) = fs.watch(&CONTEXTS_DIR, CONTEXT_WATCH_DURATION).await;
|
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||||
|
|
||||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
|
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
|
||||||
contexts_metadata: Vec::new(),
|
contexts_metadata: Vec::new(),
|
||||||
@@ -99,6 +113,20 @@ impl ContextStore {
|
|||||||
SavedContext::VERSION => {
|
SavedContext::VERSION => {
|
||||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||||
}
|
}
|
||||||
|
"0.2.0" => {
|
||||||
|
let saved_context =
|
||||||
|
serde_json::from_value::<SavedContextV0_2_0>(saved_context_json)?;
|
||||||
|
Ok(SavedContext {
|
||||||
|
id: saved_context.id,
|
||||||
|
zed: saved_context.zed,
|
||||||
|
version: saved_context.version,
|
||||||
|
text: saved_context.text,
|
||||||
|
messages: saved_context.messages,
|
||||||
|
message_metadata: saved_context.message_metadata,
|
||||||
|
summary: saved_context.summary,
|
||||||
|
slash_command_output_sections: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
"0.1.0" => {
|
"0.1.0" => {
|
||||||
let saved_context =
|
let saved_context =
|
||||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||||
@@ -110,6 +138,7 @@ impl ContextStore {
|
|||||||
messages: saved_context.messages,
|
messages: saved_context.messages,
|
||||||
message_metadata: saved_context.message_metadata,
|
message_metadata: saved_context.message_metadata,
|
||||||
summary: saved_context.summary,
|
summary: saved_context.summary,
|
||||||
|
slash_command_output_sections: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
||||||
@@ -152,9 +181,9 @@ impl ContextStore {
|
|||||||
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
fs.create_dir(&CONTEXTS_DIR).await?;
|
fs.create_dir(contexts_dir()).await?;
|
||||||
|
|
||||||
let mut paths = fs.read_dir(&CONTEXTS_DIR).await?;
|
let mut paths = fs.read_dir(contexts_dir()).await?;
|
||||||
let mut contexts = Vec::<SavedContextMetadata>::new();
|
let mut contexts = Vec::<SavedContextMetadata>::new();
|
||||||
while let Some(path) = paths.next().await {
|
while let Some(path) = paths.next().await {
|
||||||
let path = path?;
|
let path = path?;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use crate::{assistant_settings::AssistantSettings, CompletionProvider, ToggleModelSelector};
|
use crate::{assistant_settings::AssistantSettings, CompletionProvider, ToggleModelSelector};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, PopoverMenuHandle, Tooltip};
|
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ModelSelector {
|
pub struct ModelSelector {
|
||||||
@@ -19,7 +19,7 @@ impl ModelSelector {
|
|||||||
|
|
||||||
impl RenderOnce for ModelSelector {
|
impl RenderOnce for ModelSelector {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
popover_menu("model-switcher")
|
PopoverMenu::new("model-switcher")
|
||||||
.with_handle(self.handle)
|
.with_handle(self.handle)
|
||||||
.menu(move |cx| {
|
.menu(move |cx| {
|
||||||
ContextMenu::build(cx, |mut menu, cx| {
|
ContextMenu::build(cx, |mut menu, cx| {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ use ui::{
|
|||||||
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
||||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||||
};
|
};
|
||||||
use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ actions!(
|
|||||||
/// Init starts loading the PromptStore in the background and assigns
|
/// Init starts loading the PromptStore in the background and assigns
|
||||||
/// a shared future to a global.
|
/// a shared future to a global.
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
let db_path = PROMPTS_DIR.join("prompts-library-db.0.mdb");
|
let db_path = paths::prompts_dir().join("prompts-library-db.0.mdb");
|
||||||
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
|
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
|
||||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -569,7 +569,7 @@ impl PromptLibrary {
|
|||||||
let provider = CompletionProvider::global(cx);
|
let provider = CompletionProvider::global(cx);
|
||||||
if provider.is_authenticated() {
|
if provider.is_authenticated() {
|
||||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
assistant.assist(&prompt_editor, None, false, cx)
|
assistant.assist(&prompt_editor, None, None, cx)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
for window in cx.windows() {
|
for window in cx.windows() {
|
||||||
@@ -832,13 +832,8 @@ impl PromptLibrary {
|
|||||||
|
|
||||||
impl Render for PromptLibrary {
|
impl Render for PromptLibrary {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let (ui_font, ui_font_size) = {
|
let ui_font = theme::setup_ui_font(cx);
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
|
||||||
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
|
||||||
};
|
|
||||||
|
|
||||||
let theme = cx.theme().clone();
|
let theme = cx.theme().clone();
|
||||||
cx.set_rem_size(ui_font_size);
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("prompt-manager")
|
.id("prompt-manager")
|
||||||
|
|||||||
@@ -33,35 +33,32 @@ pub fn generate_content_prompt(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include file content.
|
writeln!(
|
||||||
for chunk in buffer.text_for_range(0..range.start) {
|
prompt,
|
||||||
prompt.push_str(chunk);
|
"The user has the following file open in the editor:"
|
||||||
}
|
)?;
|
||||||
|
|
||||||
if range.is_empty() {
|
if range.is_empty() {
|
||||||
prompt.push_str("<|START|>");
|
write!(prompt, "```")?;
|
||||||
} else {
|
if let Some(language_name) = language_name {
|
||||||
prompt.push_str("<|START|");
|
write!(prompt, "{language_name}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for chunk in buffer.text_for_range(range.clone()) {
|
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
|
||||||
prompt.push_str(chunk);
|
prompt.push_str(chunk);
|
||||||
}
|
}
|
||||||
|
prompt.push_str("<|CURSOR|>");
|
||||||
|
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
|
||||||
|
prompt.push_str(chunk);
|
||||||
|
}
|
||||||
|
if !prompt.ends_with('\n') {
|
||||||
|
prompt.push('\n');
|
||||||
|
}
|
||||||
|
writeln!(prompt, "```")?;
|
||||||
|
prompt.push('\n');
|
||||||
|
|
||||||
if !range.is_empty() {
|
|
||||||
prompt.push_str("|END|>");
|
|
||||||
}
|
|
||||||
|
|
||||||
for chunk in buffer.text_for_range(range.end..buffer.len()) {
|
|
||||||
prompt.push_str(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt.push('\n');
|
|
||||||
|
|
||||||
if range.is_empty() {
|
|
||||||
writeln!(
|
writeln!(
|
||||||
prompt,
|
prompt,
|
||||||
"Assume the cursor is located where the `<|START|>` span is."
|
"Assume the cursor is located where the `<|CURSOR|>` span is."
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
writeln!(
|
writeln!(
|
||||||
@@ -75,11 +72,42 @@ pub fn generate_content_prompt(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
writeln!(prompt, "Modify the user's selected {content_type} based upon the users prompt: '{user_prompt}'").unwrap();
|
write!(prompt, "```")?;
|
||||||
writeln!(prompt, "You must reply with only the adjusted {content_type} (within the '<|START|' and '|END|>' spans) not the entire file.").unwrap();
|
for chunk in buffer.as_rope().chunks() {
|
||||||
|
prompt.push_str(chunk);
|
||||||
|
}
|
||||||
|
if !prompt.ends_with('\n') {
|
||||||
|
prompt.push('\n');
|
||||||
|
}
|
||||||
|
writeln!(prompt, "```")?;
|
||||||
|
prompt.push('\n');
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
prompt,
|
prompt,
|
||||||
"Double check that you only return code and not the '<|START|' and '|END|'> spans"
|
"In particular, the following piece of text is selected:"
|
||||||
|
)?;
|
||||||
|
write!(prompt, "```")?;
|
||||||
|
if let Some(language_name) = language_name {
|
||||||
|
write!(prompt, "{language_name}")?;
|
||||||
|
}
|
||||||
|
prompt.push('\n');
|
||||||
|
for chunk in buffer.text_for_range(range.clone()) {
|
||||||
|
prompt.push_str(chunk);
|
||||||
|
}
|
||||||
|
if !prompt.ends_with('\n') {
|
||||||
|
prompt.push('\n');
|
||||||
|
}
|
||||||
|
writeln!(prompt, "```")?;
|
||||||
|
prompt.push('\n');
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"You must reply with only the adjusted {content_type}, not the entire file."
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use anyhow::Result;
|
|||||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
||||||
use editor::{CompletionProvider, Editor};
|
use editor::{CompletionProvider, Editor};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
|
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
|
||||||
use language::{Anchor, Buffer, CodeLabel, Documentation, LanguageServerId, ToPoint};
|
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -14,9 +14,11 @@ use std::{
|
|||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use ui::ActiveTheme;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub mod active_command;
|
pub mod active_command;
|
||||||
|
pub mod auto_command;
|
||||||
pub mod default_command;
|
pub mod default_command;
|
||||||
pub mod diagnostics_command;
|
pub mod diagnostics_command;
|
||||||
pub mod fetch_command;
|
pub mod fetch_command;
|
||||||
@@ -27,6 +29,7 @@ pub mod prompt_command;
|
|||||||
pub mod rustdoc_command;
|
pub mod rustdoc_command;
|
||||||
pub mod search_command;
|
pub mod search_command;
|
||||||
pub mod tabs_command;
|
pub mod tabs_command;
|
||||||
|
pub mod term_command;
|
||||||
|
|
||||||
pub(crate) struct SlashCommandCompletionProvider {
|
pub(crate) struct SlashCommandCompletionProvider {
|
||||||
commands: Arc<SlashCommandRegistry>,
|
commands: Arc<SlashCommandRegistry>,
|
||||||
@@ -347,3 +350,19 @@ impl SlashCommandLine {
|
|||||||
call
|
call
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_label_for_command(
|
||||||
|
command_name: &str,
|
||||||
|
arguments: &[&str],
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> CodeLabel {
|
||||||
|
let mut label = CodeLabel::default();
|
||||||
|
label.push_str(command_name, None);
|
||||||
|
label.push_str(" ", None);
|
||||||
|
label.push_str(
|
||||||
|
&arguments.join(" "),
|
||||||
|
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
||||||
|
);
|
||||||
|
label.filter_range = 0..command_name.len();
|
||||||
|
label
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
use super::{
|
||||||
|
diagnostics_command::write_single_file_diagnostics,
|
||||||
|
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||||
|
SlashCommand, SlashCommandOutput,
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::sync::atomic::AtomicBool;
|
||||||
use ui::{IntoElement, WindowContext};
|
use std::sync::Arc;
|
||||||
|
use ui::WindowContext;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ActiveSlashCommand;
|
pub(crate) struct ActiveSlashCommand;
|
||||||
@@ -24,9 +28,9 @@ impl SlashCommand for ActiveSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
@@ -57,46 +61,38 @@ impl SlashCommand for ActiveSlashCommand {
|
|||||||
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
let path = snapshot.resolve_file_path(cx, true);
|
let path = snapshot.resolve_file_path(cx, true);
|
||||||
let text = cx.background_executor().spawn({
|
let task = cx.background_executor().spawn({
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
async move {
|
async move {
|
||||||
let path = path
|
let mut output = String::new();
|
||||||
.as_ref()
|
output.push_str(&codeblock_fence_for_path(path.as_deref(), None));
|
||||||
.map(|path| path.to_string_lossy())
|
|
||||||
.unwrap_or_else(|| Cow::Borrowed("untitled"));
|
|
||||||
|
|
||||||
let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
|
|
||||||
output.push_str("```");
|
|
||||||
output.push_str(&path);
|
|
||||||
output.push('\n');
|
|
||||||
for chunk in snapshot.as_rope().chunks() {
|
for chunk in snapshot.as_rope().chunks() {
|
||||||
output.push_str(chunk);
|
output.push_str(chunk);
|
||||||
}
|
}
|
||||||
if !output.ends_with('\n') {
|
if !output.ends_with('\n') {
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
output.push_str("```");
|
output.push_str("```\n");
|
||||||
output
|
let has_diagnostics =
|
||||||
|
write_single_file_diagnostics(&mut output, path.as_deref(), &snapshot);
|
||||||
|
if output.ends_with('\n') {
|
||||||
|
output.pop();
|
||||||
|
}
|
||||||
|
(output, has_diagnostics)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let text = text.await;
|
let (text, has_diagnostics) = task.await;
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![build_entry_output_section(
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
path.as_deref(),
|
||||||
FilePlaceholder {
|
false,
|
||||||
id,
|
None,
|
||||||
path: path.clone(),
|
)],
|
||||||
line_range: None,
|
run_commands_in_text: has_diagnostics,
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
|
||||||
run_commands_in_text: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
242
crates/assistant/src/slash_command/auto_command.rs
Normal file
242
crates/assistant/src/slash_command/auto_command.rs
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
use super::create_label_for_command;
|
||||||
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
|
use crate::{CompletionProvider, LanguageModelRequest, LanguageModelRequestMessage, Role};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
|
||||||
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
|
use ui::WindowContext;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
pub(crate) struct AutoCommand;
|
||||||
|
|
||||||
|
impl SlashCommand for AutoCommand {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"auto".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"Automatically infer what context to add, based on your prompt".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu_text(&self) -> String {
|
||||||
|
"Automatically Infer Context".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
|
create_label_for_command("auto", &["--prompt"], cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_argument(
|
||||||
|
self: Arc<Self>,
|
||||||
|
_query: String,
|
||||||
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
|
_cx: &mut AppContext,
|
||||||
|
) -> Task<Result<Vec<String>>> {
|
||||||
|
// There's no autocomplete for a prompt, since it's arbitrary text.
|
||||||
|
Task::ready(Ok(Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_argument(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self: Arc<Self>,
|
||||||
|
argument: Option<&str>,
|
||||||
|
_workspace: WeakView<Workspace>,
|
||||||
|
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Task<Result<SlashCommandOutput>> {
|
||||||
|
let Some(argument) = argument else {
|
||||||
|
return Task::ready(Err(anyhow!("missing prompt")));
|
||||||
|
};
|
||||||
|
|
||||||
|
// to_string() is needed so it can live long enough to be used in cx.spawn
|
||||||
|
let original_prompt = argument.to_string();
|
||||||
|
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
|
||||||
|
let summaries: Vec<FileSummary> = serde_json::from_str(SUMMARY).unwrap_or_else(|_| {
|
||||||
|
// Since we generate the JSON ourselves, this parsing should never fail. If it does, that's a bug.
|
||||||
|
log::error!("JSON parsing of project file summaries failed");
|
||||||
|
|
||||||
|
// Handle this gracefully by not including any summaries. Assistant results
|
||||||
|
// will be worse than if we actually had summaries, but we won't block the user.
|
||||||
|
Vec::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
commands_for_summaries(&summaries, &original_prompt, &cx).await
|
||||||
|
});
|
||||||
|
|
||||||
|
// As a convenience, append /auto's argument to the end of the prompt
|
||||||
|
// so you don't have to write it again.
|
||||||
|
let original_prompt = argument.to_string();
|
||||||
|
|
||||||
|
cx.background_executor().spawn(async move {
|
||||||
|
let commands = task.await?;
|
||||||
|
let mut prompt = String::new();
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Translating this response into slash-commands: {:?}",
|
||||||
|
commands
|
||||||
|
);
|
||||||
|
|
||||||
|
for command in commands {
|
||||||
|
prompt.push('/');
|
||||||
|
prompt.push_str(&command.name);
|
||||||
|
prompt.push(' ');
|
||||||
|
prompt.push_str(&command.arg);
|
||||||
|
prompt.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.push('\n');
|
||||||
|
prompt.push_str(&original_prompt);
|
||||||
|
|
||||||
|
Ok(SlashCommandOutput {
|
||||||
|
text: prompt,
|
||||||
|
sections: Vec::new(),
|
||||||
|
run_commands_in_text: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = include_str!("prompt_before_summary.txt");
|
||||||
|
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = include_str!("prompt_after_summary.txt");
|
||||||
|
const SUMMARY: &str = include_str!("/Users/rtfeldman/code/summarize-dir/combined_summaries.json");
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct FileSummary {
|
||||||
|
filename: String,
|
||||||
|
summary: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summaries_prompt(summaries: &[FileSummary], original_prompt: &str) -> String {
|
||||||
|
let json_summaries = serde_json::to_string(summaries).unwrap();
|
||||||
|
|
||||||
|
format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{json_summaries}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{original_prompt}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The slash commands that the model is told about, and which we look for in the inference response.
|
||||||
|
const SUPPORTED_SLASH_COMMANDS: &[&str] = &["search", "file"];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct CommandToRun {
|
||||||
|
name: String,
|
||||||
|
arg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given the pre-indexed file summaries for this project, as well as the original prompt
|
||||||
|
/// string passed to `/auto`, get a list of slash commands to run, along with their arguments.
|
||||||
|
///
|
||||||
|
/// The prompt's output does not include the slashes (to reduce the chance that it makes a mistake),
|
||||||
|
/// so taking one of these returned Strings and turning it into a real slash-command-with-argument
|
||||||
|
/// involves prepending a slash to it.
|
||||||
|
///
|
||||||
|
/// This function will validate that each of the returned lines begins with one of SUPPORTED_SLASH_COMMANDS.
|
||||||
|
/// Any other lines it encounters will be discarded, with a warning logged.
|
||||||
|
async fn commands_for_summaries(
|
||||||
|
summaries: &[FileSummary],
|
||||||
|
original_prompt: &str,
|
||||||
|
cx: &AsyncAppContext,
|
||||||
|
) -> Result<Vec<CommandToRun>> {
|
||||||
|
if summaries.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let model = cx.update(|cx| CompletionProvider::global(cx).model())?;
|
||||||
|
let max_token_count = model.max_token_count();
|
||||||
|
|
||||||
|
// Rather than recursing (which would require this async function use a pinned box),
|
||||||
|
// we use an explicit stack of arguments and answers for when we need to "recurse."
|
||||||
|
let mut stack = vec![(summaries, String::new())];
|
||||||
|
let mut final_response = Vec::new();
|
||||||
|
|
||||||
|
while let Some((current_summaries, mut accumulated_response)) = stack.pop() {
|
||||||
|
// The split can result in one slice being empty and the other having one element.
|
||||||
|
// Whenever that happens, skip the empty one.
|
||||||
|
if current_summaries.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Inferring prompt context using {} file summaries",
|
||||||
|
current_summaries.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = LanguageModelRequest {
|
||||||
|
model: model.clone(),
|
||||||
|
messages: vec![LanguageModelRequestMessage {
|
||||||
|
role: Role::User,
|
||||||
|
content: summaries_prompt(¤t_summaries, original_prompt),
|
||||||
|
}],
|
||||||
|
stop: Vec::new(),
|
||||||
|
temperature: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let token_count = cx
|
||||||
|
.update(|cx| CompletionProvider::global(cx).count_tokens(request.clone(), cx))?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if token_count < max_token_count {
|
||||||
|
let mut response_chunks = cx
|
||||||
|
.update(|cx| CompletionProvider::global(cx).complete(request))?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
while let Some(chunk) = response_chunks.next().await {
|
||||||
|
accumulated_response.push_str(&chunk?);
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in accumulated_response.split('\n') {
|
||||||
|
if let Some(first_space) = line.find(' ') {
|
||||||
|
let command = &line[..first_space].trim();
|
||||||
|
let arg = &line[first_space..].trim();
|
||||||
|
|
||||||
|
// Don't return empty or duplicate or duplicate commands
|
||||||
|
if !command.is_empty()
|
||||||
|
&& !final_response
|
||||||
|
.iter()
|
||||||
|
.any(|cmd: &CommandToRun| cmd.name == *command && cmd.arg == *arg)
|
||||||
|
{
|
||||||
|
if SUPPORTED_SLASH_COMMANDS
|
||||||
|
.iter()
|
||||||
|
.any(|supported| command == supported)
|
||||||
|
{
|
||||||
|
final_response.push(CommandToRun {
|
||||||
|
name: command.to_string(),
|
||||||
|
arg: arg.to_string(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Context inference returned an unrecognized slash-commend line: {:?}",
|
||||||
|
line
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !line.trim().is_empty() {
|
||||||
|
// All slash-commands currently supported in context inference need a space for the argument.
|
||||||
|
log::warn!(
|
||||||
|
"Context inference returned a non-blank line that contained no spaces (meaning no argument for the slash-command): {:?}",
|
||||||
|
line
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if current_summaries.len() == 1 {
|
||||||
|
log::warn!("Inferring context for a single file's summary failed because the prompt's token length exceeded the model's token limit.");
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"Context inference using file summaries resulted in a prompt containing {token_count} tokens, which exceeded the model's max of {max_token_count}. Retrying as two separate prompts, each including half the number of summaries.",
|
||||||
|
);
|
||||||
|
let (left, right) = current_summaries.split_at(current_summaries.len() / 2);
|
||||||
|
stack.push((right, accumulated_response.clone()));
|
||||||
|
stack.push((left, accumulated_response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the commands by name (reversed just so that /search appears before /file)
|
||||||
|
final_response.sort_by(|cmd1, cmd2| cmd1.name.cmp(&cmd2.name).reverse());
|
||||||
|
|
||||||
|
Ok(final_response)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{prompt_command::PromptPlaceholder, SlashCommand, SlashCommandOutput};
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
use crate::prompt_library::PromptStore;
|
use crate::prompt_library::PromptStore;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
@@ -31,7 +31,7 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancellation_flag: Arc<AtomicBool>,
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -53,7 +53,7 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
let prompts = store.default_prompt_metadata();
|
let prompts = store.default_prompt_metadata();
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
writeln!(text, "Default Prompt:").unwrap();
|
text.push('\n');
|
||||||
for prompt in prompts {
|
for prompt in prompts {
|
||||||
if let Some(title) = prompt.title {
|
if let Some(title) = prompt.title {
|
||||||
writeln!(text, "/prompt {}", title).unwrap();
|
writeln!(text, "/prompt {}", title).unwrap();
|
||||||
@@ -61,17 +61,15 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
}
|
}
|
||||||
text.pop();
|
text.pop();
|
||||||
|
|
||||||
|
if text.is_empty() {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::Library,
|
||||||
PromptPlaceholder {
|
label: "Default".into(),
|
||||||
title: "Default".into(),
|
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
text,
|
text,
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use fuzzy::{PathMatch, StringMatchCandidate};
|
use fuzzy::{PathMatch, StringMatchCandidate};
|
||||||
use gpui::{svg, AppContext, Model, RenderOnce, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
||||||
OffsetRangeExt, ToOffset,
|
OffsetRangeExt, ToOffset,
|
||||||
@@ -10,11 +10,12 @@ use language::{
|
|||||||
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
|
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use util::paths::PathMatcher;
|
use util::paths::PathMatcher;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
@@ -57,7 +58,7 @@ impl DiagnosticsCommand {
|
|||||||
include_ignored: worktree
|
include_ignored: worktree
|
||||||
.root_entry()
|
.root_entry()
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
.map_or(false, |entry| entry.is_ignored),
|
||||||
include_root_name: false,
|
include_root_name: true,
|
||||||
candidates: project::Candidates::Entries,
|
candidates: project::Candidates::Entries,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -85,6 +86,10 @@ impl SlashCommand for DiagnosticsCommand {
|
|||||||
"diagnostics".into()
|
"diagnostics".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn label(&self, cx: &AppContext) -> language::CodeLabel {
|
||||||
|
create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
"Insert diagnostics".into()
|
"Insert diagnostics".into()
|
||||||
}
|
}
|
||||||
@@ -98,7 +103,7 @@ impl SlashCommand for DiagnosticsCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -157,21 +162,55 @@ impl SlashCommand for DiagnosticsCommand {
|
|||||||
|
|
||||||
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
||||||
cx.spawn(move |_| async move {
|
cx.spawn(move |_| async move {
|
||||||
let (text, sections) = task.await?;
|
let Some((text, sections)) = task.await? else {
|
||||||
|
return Ok(SlashCommandOutput::default());
|
||||||
|
};
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: sections
|
sections: sections
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: match placeholder_type {
|
||||||
DiagnosticsPlaceholder {
|
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
|
||||||
id,
|
PlaceholderType::File(_) => IconName::File,
|
||||||
unfold,
|
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => {
|
||||||
placeholder_type: placeholder_type.clone(),
|
IconName::XCircle
|
||||||
}
|
}
|
||||||
.into_any_element()
|
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
|
||||||
}),
|
IconName::ExclamationTriangle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: match placeholder_type {
|
||||||
|
PlaceholderType::Root(summary, source) => {
|
||||||
|
let mut label = String::new();
|
||||||
|
label.push_str("Diagnostics");
|
||||||
|
if let Some(source) = source {
|
||||||
|
write!(label, " ({})", source).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if summary.error_count > 0 || summary.warning_count > 0 {
|
||||||
|
label.push(':');
|
||||||
|
|
||||||
|
if summary.error_count > 0 {
|
||||||
|
write!(label, " {} errors", summary.error_count).unwrap();
|
||||||
|
if summary.warning_count > 0 {
|
||||||
|
label.push_str(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if summary.warning_count > 0 {
|
||||||
|
write!(label, " {} warnings", summary.warning_count)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label.into()
|
||||||
|
}
|
||||||
|
PlaceholderType::File(file_path) => file_path.into(),
|
||||||
|
PlaceholderType::Diagnostic(_, message) => message.into(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
@@ -189,7 +228,7 @@ struct Options {
|
|||||||
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
pub fn parse(arguments_line: Option<&str>) -> Self {
|
fn parse(arguments_line: Option<&str>) -> Self {
|
||||||
arguments_line
|
arguments_line
|
||||||
.map(|arguments_line| {
|
.map(|arguments_line| {
|
||||||
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
|
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
|
||||||
@@ -199,7 +238,7 @@ impl Options {
|
|||||||
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
||||||
include_warnings = true;
|
include_warnings = true;
|
||||||
} else {
|
} else {
|
||||||
path_matcher = PathMatcher::new(arg).log_err();
|
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
@@ -222,25 +261,59 @@ fn collect_diagnostics(
|
|||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
options: Options,
|
options: Options,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
|
) -> Task<Result<Option<(String, Vec<(Range<usize>, PlaceholderType)>)>>> {
|
||||||
let header = if let Some(path_matcher) = &options.path_matcher {
|
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||||
format!("diagnostics: {}", path_matcher.source())
|
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||||
|
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||||
} else {
|
} else {
|
||||||
"diagnostics".to_string()
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let glob_is_exact_file_match = if let Some(path) = options
|
||||||
|
.path_matcher
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|pm| pm.sources().first())
|
||||||
|
{
|
||||||
|
PathBuf::try_from(path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|path| {
|
||||||
|
project.read(cx).worktrees().find_map(|worktree| {
|
||||||
|
let worktree = worktree.read(cx);
|
||||||
|
let worktree_root_path = Path::new(worktree.root_name());
|
||||||
|
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
||||||
|
worktree.absolutize(&relative_path).ok()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let project_handle = project.downgrade();
|
let project_handle = project.downgrade();
|
||||||
let diagnostic_summaries: Vec<_> = project.read(cx).diagnostic_summaries(false, cx).collect();
|
let diagnostic_summaries: Vec<_> = project
|
||||||
|
.read(cx)
|
||||||
|
.diagnostic_summaries(false, cx)
|
||||||
|
.flat_map(|(path, _, summary)| {
|
||||||
|
let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?;
|
||||||
|
let mut path_buf = PathBuf::from(worktree.read(cx).root_name());
|
||||||
|
path_buf.push(&path.path);
|
||||||
|
Some((path, path_buf, summary))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
writeln!(text, "{}", &header).unwrap();
|
if let Some(error_source) = error_source.as_ref() {
|
||||||
|
writeln!(text, "diagnostics: {}", error_source).unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(text, "diagnostics").unwrap();
|
||||||
|
}
|
||||||
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
|
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
|
||||||
|
|
||||||
let mut project_summary = DiagnosticSummary::default();
|
let mut project_summary = DiagnosticSummary::default();
|
||||||
for (project_path, _, summary) in diagnostic_summaries {
|
for (project_path, path, summary) in diagnostic_summaries {
|
||||||
if let Some(path_matcher) = &options.path_matcher {
|
if let Some(path_matcher) = &options.path_matcher {
|
||||||
if !path_matcher.is_match(&project_path.path) {
|
if !path_matcher.is_match(&path) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,8 +326,10 @@ fn collect_diagnostics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let last_end = text.len();
|
let last_end = text.len();
|
||||||
let file_path = project_path.path.to_string_lossy().to_string();
|
let file_path = path.to_string_lossy().to_string();
|
||||||
writeln!(&mut text, "{file_path}").unwrap();
|
if !glob_is_exact_file_match {
|
||||||
|
writeln!(&mut text, "{file_path}").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(buffer) = project_handle
|
if let Some(buffer) = project_handle
|
||||||
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))?
|
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||||
@@ -269,20 +344,52 @@ fn collect_diagnostics(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sections.push((
|
if !glob_is_exact_file_match {
|
||||||
last_end..text.len().saturating_sub(1),
|
sections.push((
|
||||||
PlaceholderType::File(file_path),
|
last_end..text.len().saturating_sub(1),
|
||||||
))
|
PlaceholderType::File(file_path),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No diagnostics found
|
||||||
|
if sections.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
sections.push((
|
sections.push((
|
||||||
0..text.len(),
|
0..text.len(),
|
||||||
PlaceholderType::Root(project_summary, header),
|
PlaceholderType::Root(project_summary, error_source),
|
||||||
));
|
));
|
||||||
|
Ok(Some((text, sections)))
|
||||||
Ok((text, sections))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn buffer_has_error_diagnostics(snapshot: &BufferSnapshot) -> bool {
|
||||||
|
for (_, group) in snapshot.diagnostic_groups(None) {
|
||||||
|
let entry = &group.entries[group.primary_ix];
|
||||||
|
if entry.diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_single_file_diagnostics(
|
||||||
|
output: &mut String,
|
||||||
|
path: Option<&Path>,
|
||||||
|
snapshot: &BufferSnapshot,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(path) = path {
|
||||||
|
if buffer_has_error_diagnostics(&snapshot) {
|
||||||
|
output.push_str("/diagnostics ");
|
||||||
|
output.push_str(&path.to_string_lossy());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_buffer_diagnostics(
|
fn collect_buffer_diagnostics(
|
||||||
text: &mut String,
|
text: &mut String,
|
||||||
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
||||||
@@ -362,12 +469,12 @@ fn collect_diagnostic(
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum PlaceholderType {
|
pub enum PlaceholderType {
|
||||||
Root(DiagnosticSummary, String),
|
Root(DiagnosticSummary, Option<String>),
|
||||||
File(String),
|
File(String),
|
||||||
Diagnostic(DiagnosticType, String),
|
Diagnostic(DiagnosticType, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, IntoElement)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum DiagnosticType {
|
pub enum DiagnosticType {
|
||||||
Warning,
|
Warning,
|
||||||
Error,
|
Error,
|
||||||
@@ -381,64 +488,3 @@ impl DiagnosticType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct DiagnosticsPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub placeholder_type: PlaceholderType,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for DiagnosticsPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
let (icon, content) = match self.placeholder_type {
|
|
||||||
PlaceholderType::Root(summary, title) => (
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.gap_0p5()
|
|
||||||
.when(summary.error_count > 0, |this| {
|
|
||||||
this.child(DiagnosticType::Error)
|
|
||||||
.child(Label::new(summary.error_count.to_string()))
|
|
||||||
})
|
|
||||||
.when(summary.warning_count > 0, |this| {
|
|
||||||
this.child(DiagnosticType::Warning)
|
|
||||||
.child(Label::new(summary.warning_count.to_string()))
|
|
||||||
})
|
|
||||||
.into_any_element(),
|
|
||||||
Label::new(title),
|
|
||||||
),
|
|
||||||
PlaceholderType::File(file) => (
|
|
||||||
Icon::new(IconName::File).into_any_element(),
|
|
||||||
Label::new(file),
|
|
||||||
),
|
|
||||||
PlaceholderType::Diagnostic(diagnostic_type, message) => (
|
|
||||||
diagnostic_type.into_any_element(),
|
|
||||||
Label::new(message).single_line(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(icon)
|
|
||||||
.child(content)
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for DiagnosticType {
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
svg()
|
|
||||||
.size(cx.text_style().font_size)
|
|
||||||
.flex_none()
|
|
||||||
.map(|icon| match self {
|
|
||||||
DiagnosticType::Error => icon
|
|
||||||
.path(IconName::XCircle.path())
|
|
||||||
.text_color(Color::Error.color(cx)),
|
|
||||||
DiagnosticType::Warning => icon
|
|
||||||
.path(IconName::ExclamationTriangle.path())
|
|
||||||
.text_color(Color::Warning.color(cx)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use gpui::{AppContext, Task, WeakView};
|
|||||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||||
@@ -62,6 +62,7 @@ impl FetchSlashCommand {
|
|||||||
match content_type {
|
match content_type {
|
||||||
ContentType::Html => {
|
ContentType::Html => {
|
||||||
let mut handlers: Vec<TagHandler> = vec![
|
let mut handlers: Vec<TagHandler> = vec![
|
||||||
|
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
||||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
||||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
||||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
Rc::new(RefCell::new(markdown::ListHandler)),
|
||||||
@@ -113,7 +114,7 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -152,37 +153,11 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::AtSign,
|
||||||
FetchPlaceholder {
|
label: format!("fetch {}", url).into(),
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
url: url.clone(),
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct FetchPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
pub url: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for FetchPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::AtSign))
|
|
||||||
.child(Label::new(format!("fetch {url}", url = self.url)))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{LineEnding, LspAdapterDelegate};
|
use language::{BufferSnapshot, LineEnding, LspAdapterDelegate};
|
||||||
use project::PathMatchCandidateSet;
|
use project::{PathMatchCandidateSet, Project};
|
||||||
use std::{
|
use std::{
|
||||||
|
fmt::Write,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
|
use util::{paths::PathMatcher, ResultExt};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct FileSlashCommand;
|
pub(crate) struct FileSlashCommand;
|
||||||
@@ -58,7 +60,7 @@ impl FileSlashCommand {
|
|||||||
.root_entry()
|
.root_entry()
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
.map_or(false, |entry| entry.is_ignored),
|
||||||
include_root_name: true,
|
include_root_name: true,
|
||||||
candidates: project::Candidates::Files,
|
candidates: project::Candidates::Entries,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -98,7 +100,7 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -139,88 +141,225 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
return Task::ready(Err(anyhow!("missing path")));
|
return Task::ready(Err(anyhow!("missing path")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = PathBuf::from(argument);
|
let task = collect_files(workspace.read(cx).project().clone(), argument, cx);
|
||||||
let abs_path = workspace
|
|
||||||
.read(cx)
|
|
||||||
.visible_worktrees(cx)
|
|
||||||
.find_map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let worktree_root_path = Path::new(worktree.root_name());
|
|
||||||
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
|
||||||
worktree.absolutize(&relative_path).ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
let Some(abs_path) = abs_path else {
|
|
||||||
return Task::ready(Err(anyhow!("missing path")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let fs = workspace.read(cx).app_state().fs.clone();
|
|
||||||
let argument = argument.to_string();
|
|
||||||
let text = cx.background_executor().spawn(async move {
|
|
||||||
let mut content = fs.load(&abs_path).await?;
|
|
||||||
LineEnding::normalize(&mut content);
|
|
||||||
let mut output = String::with_capacity(argument.len() + content.len() + 9);
|
|
||||||
output.push_str("```");
|
|
||||||
output.push_str(&argument);
|
|
||||||
output.push('\n');
|
|
||||||
output.push_str(&content);
|
|
||||||
if !output.ends_with('\n') {
|
|
||||||
output.push('\n');
|
|
||||||
}
|
|
||||||
output.push_str("```");
|
|
||||||
anyhow::Ok(output)
|
|
||||||
});
|
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let text = text.await?;
|
let (text, ranges) = task.await?;
|
||||||
let range = 0..text.len();
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: ranges
|
||||||
range,
|
.into_iter()
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
.map(|(range, path, entry_type)| {
|
||||||
FilePlaceholder {
|
build_entry_output_section(
|
||||||
path: Some(path.clone()),
|
range,
|
||||||
line_range: None,
|
Some(&path),
|
||||||
id,
|
entry_type == EntryType::Directory,
|
||||||
unfold,
|
None,
|
||||||
}
|
)
|
||||||
.into_any_element()
|
})
|
||||||
}),
|
.collect(),
|
||||||
}],
|
run_commands_in_text: true,
|
||||||
run_commands_in_text: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct FilePlaceholder {
|
enum EntryType {
|
||||||
pub path: Option<PathBuf>,
|
File,
|
||||||
pub line_range: Option<Range<u32>>,
|
Directory,
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for FilePlaceholder {
|
fn collect_files(
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
project: Model<Project>,
|
||||||
let unfold = self.unfold;
|
glob_input: &str,
|
||||||
let title = if let Some(path) = self.path.as_ref() {
|
cx: &mut AppContext,
|
||||||
SharedString::from(path.to_string_lossy().to_string())
|
) -> Task<Result<(String, Vec<(Range<usize>, PathBuf, EntryType)>)>> {
|
||||||
} else {
|
let Ok(matcher) = PathMatcher::new(&[glob_input.to_owned()]) else {
|
||||||
SharedString::from("untitled")
|
return Task::ready(Err(anyhow!("invalid path")));
|
||||||
};
|
};
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
let project_handle = project.downgrade();
|
||||||
.style(ButtonStyle::Filled)
|
let snapshots = project
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
.read(cx)
|
||||||
.child(Icon::new(IconName::File))
|
.worktrees()
|
||||||
.child(Label::new(title))
|
.map(|worktree| worktree.read(cx).snapshot())
|
||||||
.when_some(self.line_range, |button, line_range| {
|
.collect::<Vec<_>>();
|
||||||
button.child(Label::new(":")).child(Label::new(format!(
|
cx.spawn(|mut cx| async move {
|
||||||
"{}-{}",
|
let mut text = String::new();
|
||||||
line_range.start, line_range.end
|
let mut ranges = Vec::new();
|
||||||
)))
|
for snapshot in snapshots {
|
||||||
})
|
let worktree_id = snapshot.id();
|
||||||
.on_click(move |_, cx| unfold(cx))
|
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
||||||
|
let mut folded_directory_names_stack = Vec::new();
|
||||||
|
let mut is_top_level_directory = true;
|
||||||
|
for entry in snapshot.entries(false, 0) {
|
||||||
|
let mut path_including_worktree_name = PathBuf::new();
|
||||||
|
path_including_worktree_name.push(snapshot.root_name());
|
||||||
|
path_including_worktree_name.push(&entry.path);
|
||||||
|
if !matcher.is_match(&path_including_worktree_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((dir, _, _)) = directory_stack.last() {
|
||||||
|
if entry.path.starts_with(dir) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let (_, entry_name, start) = directory_stack.pop().unwrap();
|
||||||
|
ranges.push((
|
||||||
|
start..text.len().saturating_sub(1),
|
||||||
|
PathBuf::from(entry_name),
|
||||||
|
EntryType::Directory,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let filename = entry
|
||||||
|
.path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if entry.is_dir() {
|
||||||
|
// Auto-fold directories that contain no files
|
||||||
|
let mut child_entries = snapshot.child_entries(&entry.path);
|
||||||
|
if let Some(child) = child_entries.next() {
|
||||||
|
if child_entries.next().is_none() && child.kind.is_dir() {
|
||||||
|
if is_top_level_directory {
|
||||||
|
is_top_level_directory = false;
|
||||||
|
folded_directory_names_stack.push(
|
||||||
|
path_including_worktree_name.to_string_lossy().to_string(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
folded_directory_names_stack.push(filename.to_string());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Skip empty directories
|
||||||
|
folded_directory_names_stack.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
||||||
|
let entry_start = text.len();
|
||||||
|
if prefix_paths.is_empty() {
|
||||||
|
if is_top_level_directory {
|
||||||
|
text.push_str(&path_including_worktree_name.to_string_lossy());
|
||||||
|
is_top_level_directory = false;
|
||||||
|
} else {
|
||||||
|
text.push_str(&filename);
|
||||||
|
}
|
||||||
|
directory_stack.push((entry.path.clone(), filename, entry_start));
|
||||||
|
} else {
|
||||||
|
let entry_name = format!("{}/{}", prefix_paths, &filename);
|
||||||
|
text.push_str(&entry_name);
|
||||||
|
directory_stack.push((entry.path.clone(), entry_name, entry_start));
|
||||||
|
}
|
||||||
|
text.push('\n');
|
||||||
|
} else if entry.is_file() {
|
||||||
|
let Some(open_buffer_task) = project_handle
|
||||||
|
.update(&mut cx, |project, cx| {
|
||||||
|
project.open_buffer((worktree_id, &entry.path), cx)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(buffer) = open_buffer_task.await.log_err() {
|
||||||
|
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
|
||||||
|
let prev_len = text.len();
|
||||||
|
collect_file_content(&mut text, &snapshot, filename.clone());
|
||||||
|
text.push('\n');
|
||||||
|
if !write_single_file_diagnostics(
|
||||||
|
&mut text,
|
||||||
|
Some(&path_including_worktree_name),
|
||||||
|
&snapshot,
|
||||||
|
) {
|
||||||
|
text.pop();
|
||||||
|
}
|
||||||
|
ranges.push((
|
||||||
|
prev_len..text.len(),
|
||||||
|
PathBuf::from(filename),
|
||||||
|
EntryType::File,
|
||||||
|
));
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((dir, _, start)) = directory_stack.pop() {
|
||||||
|
let mut root_path = PathBuf::new();
|
||||||
|
root_path.push(snapshot.root_name());
|
||||||
|
root_path.push(&dir);
|
||||||
|
ranges.push((start..text.len(), root_path, EntryType::Directory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((text, ranges))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
|
||||||
|
let mut content = snapshot.text();
|
||||||
|
LineEnding::normalize(&mut content);
|
||||||
|
buffer.reserve(filename.len() + content.len() + 9);
|
||||||
|
buffer.push_str(&codeblock_fence_for_path(
|
||||||
|
Some(&PathBuf::from(filename)),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
buffer.push_str(&content);
|
||||||
|
if !buffer.ends_with('\n') {
|
||||||
|
buffer.push('\n');
|
||||||
|
}
|
||||||
|
buffer.push_str("```");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
|
||||||
|
let mut text = String::new();
|
||||||
|
write!(text, "```").unwrap();
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
||||||
|
write!(text, "{} ", extension).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(text, "{}", path.display()).unwrap();
|
||||||
|
} else {
|
||||||
|
write!(text, "untitled").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(row_range) = row_range {
|
||||||
|
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
text.push('\n');
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_entry_output_section(
|
||||||
|
range: Range<usize>,
|
||||||
|
path: Option<&Path>,
|
||||||
|
is_directory: bool,
|
||||||
|
line_range: Option<Range<u32>>,
|
||||||
|
) -> SlashCommandOutputSection<usize> {
|
||||||
|
let mut label = if let Some(path) = path {
|
||||||
|
path.to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
"untitled".to_string()
|
||||||
|
};
|
||||||
|
if let Some(line_range) = line_range {
|
||||||
|
write!(label, ":{}-{}", line_range.start, line_range.end).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon = if is_directory {
|
||||||
|
IconName::Folder
|
||||||
|
} else {
|
||||||
|
IconName::File
|
||||||
|
};
|
||||||
|
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon,
|
||||||
|
label: label.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl SlashCommand for NowSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -53,9 +53,8 @@ impl SlashCommand for NowSlashCommand {
|
|||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::CountdownTimer,
|
||||||
NowPlaceholder { id, unfold, now }.into_any_element()
|
label: now.to_rfc3339().into(),
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ProjectSlashCommand;
|
pub(crate) struct ProjectSlashCommand;
|
||||||
@@ -102,7 +102,7 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -138,15 +138,8 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::FileTree,
|
||||||
ButtonLike::new(id)
|
label: "Project".into(),
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::FileTree))
|
|
||||||
.child(Label::new("Project"))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
|
|||||||
24
crates/assistant/src/slash_command/prompt_after_summary.txt
Normal file
24
crates/assistant/src/slash_command/prompt_after_summary.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Actions have a cost, so only include actions that you think
|
||||||
|
will be helpful to you in doing a great job answering the
|
||||||
|
prompt in the future.
|
||||||
|
|
||||||
|
You must respond ONLY with a list of actions you would like to
|
||||||
|
perform. Each action should be on its own line, and followed by a space and then its parameter.
|
||||||
|
|
||||||
|
Actions can be performed more than once with different parameters.
|
||||||
|
Here is an example valid response:
|
||||||
|
|
||||||
|
```
|
||||||
|
file path/to/my/file.txt
|
||||||
|
file path/to/another/file.txt
|
||||||
|
search something to search for
|
||||||
|
search something else to search for
|
||||||
|
```
|
||||||
|
|
||||||
|
Once again, do not forget: you must respond ONLY in the format of
|
||||||
|
one action per line, and the action name should be followed by
|
||||||
|
its parameter. Your response must not include anything other
|
||||||
|
than a list of actions, with one action per line, in this format.
|
||||||
|
It is extremely important that you do not deviate from this format even slightly!
|
||||||
|
|
||||||
|
This is the end of my instructions for how to respond. The rest is the prompt:
|
||||||
31
crates/assistant/src/slash_command/prompt_before_summary.txt
Normal file
31
crates/assistant/src/slash_command/prompt_before_summary.txt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
I'm going to give you a prompt. I don't want you to respond
|
||||||
|
to the prompt itself. I want you to figure out which of the following
|
||||||
|
actions on my project, if any, would help you answer the prompt.
|
||||||
|
|
||||||
|
Here are the actions:
|
||||||
|
|
||||||
|
## file
|
||||||
|
|
||||||
|
This action's parameter is a file path to one of the files
|
||||||
|
in the project. If you ask for this action, I will tell you
|
||||||
|
the full contents of the file, so you can learn all the
|
||||||
|
details of the file.
|
||||||
|
|
||||||
|
## search
|
||||||
|
|
||||||
|
This action's parameter is a string to do a semantic search for
|
||||||
|
across the files in the project. (You will have a JSON summary
|
||||||
|
of all the files in the project.) It will tell you which files this string
|
||||||
|
(or similar strings; it is a semantic search) appear in,
|
||||||
|
as well as some context of the lines surrounding each result.
|
||||||
|
It's very important that you only use this action when you think
|
||||||
|
that searching across the specific files in this project for the query
|
||||||
|
in question will be useful. For example, don't use this command to search
|
||||||
|
for queries you might put into a general Web search engine, because those
|
||||||
|
will be too general to give useful results in this project-specific search.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
That was the end of the list of actions.
|
||||||
|
|
||||||
|
Here is a JSON summary of each of the files in my project:
|
||||||
@@ -5,7 +5,7 @@ use assistant_slash_command::SlashCommandOutputSection;
|
|||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct PromptSlashCommand;
|
pub(crate) struct PromptSlashCommand;
|
||||||
@@ -28,7 +28,7 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
query: String,
|
query: String,
|
||||||
_cancellation_flag: Arc<AtomicBool>,
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -69,42 +69,20 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let prompt = prompt.await?;
|
let mut prompt = prompt.await?;
|
||||||
|
if prompt.is_empty() {
|
||||||
|
prompt.push('\n');
|
||||||
|
}
|
||||||
let range = 0..prompt.len();
|
let range = 0..prompt.len();
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text: prompt,
|
text: prompt,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::Library,
|
||||||
PromptPlaceholder {
|
label: title,
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
title: title.clone(),
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct PromptPlaceholder {
|
|
||||||
pub title: SharedString,
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for PromptPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::Library))
|
|
||||||
.child(Label::new(self.title))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,20 +10,11 @@ use gpui::{AppContext, Model, Task, WeakView};
|
|||||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use rustdoc::{convert_rustdoc_to_markdown, RustdocStore};
|
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
|
||||||
use rustdoc::{CrateName, LocalProvider};
|
use ui::prelude::*;
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum RustdocSource {
|
|
||||||
/// The docs were sourced from local `cargo doc` output.
|
|
||||||
Local,
|
|
||||||
/// The docs were sourced from `docs.rs`.
|
|
||||||
DocsDotRs,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct RustdocSlashCommand;
|
pub(crate) struct RustdocSlashCommand;
|
||||||
|
|
||||||
impl RustdocSlashCommand {
|
impl RustdocSlashCommand {
|
||||||
@@ -116,7 +107,7 @@ impl SlashCommand for RustdocSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
query: String,
|
query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -189,11 +180,18 @@ impl SlashCommand for RustdocSlashCommand {
|
|||||||
let item_path = item_path.clone();
|
let item_path = item_path.clone();
|
||||||
async move {
|
async move {
|
||||||
let item_docs = rustdoc_store
|
let item_docs = rustdoc_store
|
||||||
.load(crate_name.clone(), Some(item_path.join("::")))
|
.load(
|
||||||
|
crate_name.clone(),
|
||||||
|
if item_path.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(item_path.join("::"))
|
||||||
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Ok(item_docs) = item_docs {
|
if let Ok(item_docs) = item_docs {
|
||||||
anyhow::Ok((RustdocSource::Local, item_docs.docs().to_owned()))
|
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
|
||||||
} else {
|
} else {
|
||||||
Self::build_message(
|
Self::build_message(
|
||||||
fs,
|
fs,
|
||||||
@@ -215,56 +213,26 @@ impl SlashCommand for RustdocSlashCommand {
|
|||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let (source, text) = text.await?;
|
let (source, text) = text.await?;
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
|
let crate_path = module_path
|
||||||
|
.map(|module_path| format!("{}::{}", crate_name, module_path))
|
||||||
|
.unwrap_or_else(|| crate_name.to_string());
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::FileRust,
|
||||||
RustdocPlaceholder {
|
label: format!(
|
||||||
id,
|
"rustdoc ({source}): {crate_path}",
|
||||||
unfold,
|
source = match source {
|
||||||
source,
|
RustdocSource::Index => "index",
|
||||||
crate_name: crate_name.clone(),
|
RustdocSource::Local => "local",
|
||||||
module_path: module_path.clone(),
|
RustdocSource::DocsDotRs => "docs.rs",
|
||||||
}
|
}
|
||||||
.into_any_element()
|
)
|
||||||
}),
|
.into(),
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct RustdocPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
pub source: RustdocSource,
|
|
||||||
pub crate_name: CrateName,
|
|
||||||
pub module_path: Option<SharedString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for RustdocPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
|
|
||||||
let crate_path = self
|
|
||||||
.module_path
|
|
||||||
.map(|module_path| format!("{crate_name}::{module_path}", crate_name = self.crate_name))
|
|
||||||
.unwrap_or(self.crate_name.to_string());
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::FileRust))
|
|
||||||
.child(Label::new(format!(
|
|
||||||
"rustdoc ({source}): {crate_path}",
|
|
||||||
source = match self.source {
|
|
||||||
RustdocSource::Local => "local",
|
|
||||||
RustdocSource::DocsDotRs => "docs.rs",
|
|
||||||
}
|
|
||||||
)))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
use super::{
|
||||||
|
create_label_for_command,
|
||||||
|
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||||
|
SlashCommand, SlashCommandOutput,
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::{CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
|
||||||
use semantic_index::SemanticIndex;
|
use semantic_index::SemanticIndex;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex, Icon, IconName};
|
use ui::{prelude::*, IconName};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -21,14 +25,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn label(&self, cx: &AppContext) -> CodeLabel {
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
let mut label = CodeLabel::default();
|
create_label_for_command("search", &["--n"], cx)
|
||||||
label.push_str("search ", None);
|
|
||||||
label.push_str(
|
|
||||||
"--n",
|
|
||||||
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
|
||||||
);
|
|
||||||
label.filter_range = 0.."search".len();
|
|
||||||
label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
@@ -44,7 +41,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -125,9 +122,8 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
let range_start = result.range.start.min(file_content.len());
|
let range_start = result.range.start.min(file_content.len());
|
||||||
let range_end = result.range.end.min(file_content.len());
|
let range_end = result.range.end.min(file_content.len());
|
||||||
|
|
||||||
let start_line =
|
let start_row = file_content[0..range_start].matches('\n').count() as u32;
|
||||||
file_content[0..range_start].matches('\n').count() as u32 + 1;
|
let end_row = file_content[0..range_end].matches('\n').count() as u32;
|
||||||
let end_line = file_content[0..range_end].matches('\n').count() as u32 + 1;
|
|
||||||
let start_line_byte_offset = file_content[0..range_start]
|
let start_line_byte_offset = file_content[0..range_start]
|
||||||
.rfind('\n')
|
.rfind('\n')
|
||||||
.map(|pos| pos + 1)
|
.map(|pos| pos + 1)
|
||||||
@@ -138,47 +134,30 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
.unwrap_or_else(|| file_content.len());
|
.unwrap_or_else(|| file_content.len());
|
||||||
|
|
||||||
let section_start_ix = text.len();
|
let section_start_ix = text.len();
|
||||||
writeln!(
|
text.push_str(&codeblock_fence_for_path(
|
||||||
text,
|
Some(&result.path),
|
||||||
"```{}:{}-{}",
|
Some(start_row..end_row),
|
||||||
result.path.display(),
|
));
|
||||||
start_line,
|
|
||||||
end_line,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let mut excerpt =
|
let mut excerpt =
|
||||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
||||||
LineEnding::normalize(&mut excerpt);
|
LineEnding::normalize(&mut excerpt);
|
||||||
text.push_str(&excerpt);
|
text.push_str(&excerpt);
|
||||||
writeln!(text, "\n```\n").unwrap();
|
writeln!(text, "\n```\n").unwrap();
|
||||||
let section_end_ix = text.len() - 1;
|
let section_end_ix = text.len() - 1;
|
||||||
|
sections.push(build_entry_output_section(
|
||||||
sections.push(SlashCommandOutputSection {
|
section_start_ix..section_end_ix,
|
||||||
range: section_start_ix..section_end_ix,
|
Some(&full_path),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
false,
|
||||||
FilePlaceholder {
|
Some(start_row + 1..end_row + 1),
|
||||||
id,
|
));
|
||||||
path: Some(full_path.clone()),
|
|
||||||
line_range: Some(start_line..end_line),
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = SharedString::from(query);
|
let query = SharedString::from(query);
|
||||||
sections.push(SlashCommandOutputSection {
|
sections.push(SlashCommandOutputSection {
|
||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::MagnifyingGlass,
|
||||||
ButtonLike::new(id)
|
label: query,
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::MagnifyingGlass))
|
|
||||||
.child(Label::new(query.clone()))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
SlashCommandOutput {
|
SlashCommandOutput {
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
use super::{
|
||||||
|
diagnostics_command::write_single_file_diagnostics,
|
||||||
|
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||||
|
SlashCommand, SlashCommandOutput,
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Entity, Task, WeakView};
|
use gpui::{AppContext, Entity, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::{fmt::Write, path::Path, sync::Arc};
|
use std::{fmt::Write, sync::Arc};
|
||||||
use ui::{IntoElement, WindowContext};
|
use ui::WindowContext;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct TabsSlashCommand;
|
pub(crate) struct TabsSlashCommand;
|
||||||
@@ -29,7 +32,7 @@ impl SlashCommand for TabsSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -75,44 +78,37 @@ impl SlashCommand for TabsSlashCommand {
|
|||||||
|
|
||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
|
let mut has_diagnostics = false;
|
||||||
for (full_path, buffer, _) in open_buffers {
|
for (full_path, buffer, _) in open_buffers {
|
||||||
let section_start_ix = text.len();
|
let section_start_ix = text.len();
|
||||||
writeln!(
|
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
|
||||||
text,
|
|
||||||
"```{}\n",
|
|
||||||
full_path
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or(Path::new("untitled"))
|
|
||||||
.display()
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
for chunk in buffer.as_rope().chunks() {
|
for chunk in buffer.as_rope().chunks() {
|
||||||
text.push_str(chunk);
|
text.push_str(chunk);
|
||||||
}
|
}
|
||||||
if !text.ends_with('\n') {
|
if !text.ends_with('\n') {
|
||||||
text.push('\n');
|
text.push('\n');
|
||||||
}
|
}
|
||||||
writeln!(text, "```\n").unwrap();
|
writeln!(text, "```").unwrap();
|
||||||
let section_end_ix = text.len() - 1;
|
if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
|
||||||
|
has_diagnostics = true;
|
||||||
|
}
|
||||||
|
if !text.ends_with('\n') {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
sections.push(SlashCommandOutputSection {
|
let section_end_ix = text.len() - 1;
|
||||||
range: section_start_ix..section_end_ix,
|
sections.push(build_entry_output_section(
|
||||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
section_start_ix..section_end_ix,
|
||||||
FilePlaceholder {
|
full_path.as_deref(),
|
||||||
id,
|
false,
|
||||||
path: full_path.clone(),
|
None,
|
||||||
line_range: None,
|
));
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections,
|
sections,
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: has_diagnostics,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Err(error) => Task::ready(Err(error)),
|
Err(error) => Task::ready(Err(error)),
|
||||||
|
|||||||
105
crates/assistant/src/slash_command/term_command.rs
Normal file
105
crates/assistant/src/slash_command/term_command.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
|
use gpui::{AppContext, Task, WeakView};
|
||||||
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
|
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use super::create_label_for_command;
|
||||||
|
|
||||||
|
pub(crate) struct TermSlashCommand;
|
||||||
|
|
||||||
|
const LINE_COUNT_ARG: &str = "--line-count";
|
||||||
|
|
||||||
|
impl SlashCommand for TermSlashCommand {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"term".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
|
create_label_for_command("term", &[LINE_COUNT_ARG], cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"insert terminal output".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu_text(&self) -> String {
|
||||||
|
"Insert terminal output".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_argument(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_argument(
|
||||||
|
self: Arc<Self>,
|
||||||
|
_query: String,
|
||||||
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
|
_cx: &mut AppContext,
|
||||||
|
) -> Task<Result<Vec<String>>> {
|
||||||
|
Task::ready(Ok(vec![LINE_COUNT_ARG.to_string()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self: Arc<Self>,
|
||||||
|
argument: Option<&str>,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
|
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Task<Result<SlashCommandOutput>> {
|
||||||
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||||
|
};
|
||||||
|
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
|
||||||
|
};
|
||||||
|
let Some(active_terminal) = terminal_panel
|
||||||
|
.read(cx)
|
||||||
|
.pane()
|
||||||
|
.read(cx)
|
||||||
|
.active_item()
|
||||||
|
.and_then(|t| t.downcast::<TerminalView>())
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("no active terminal")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_count = argument.and_then(|a| parse_argument(a)).unwrap_or(20);
|
||||||
|
|
||||||
|
let lines = active_terminal
|
||||||
|
.read(cx)
|
||||||
|
.model()
|
||||||
|
.read(cx)
|
||||||
|
.last_n_non_empty_lines(line_count);
|
||||||
|
|
||||||
|
let mut text = String::new();
|
||||||
|
text.push_str("Terminal output:\n");
|
||||||
|
text.push_str(&lines.join("\n"));
|
||||||
|
let range = 0..text.len();
|
||||||
|
|
||||||
|
Task::ready(Ok(SlashCommandOutput {
|
||||||
|
text,
|
||||||
|
sections: vec![SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon: IconName::Terminal,
|
||||||
|
label: "Terminal".into(),
|
||||||
|
}],
|
||||||
|
run_commands_in_text: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_argument(argument: &str) -> Option<usize> {
|
||||||
|
let mut args = argument.split(' ');
|
||||||
|
if args.next() == Some(LINE_COUNT_ARG) {
|
||||||
|
if let Some(line_count) = args.next().and_then(|s| s.parse::<usize>().ok()) {
|
||||||
|
return Some(line_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
@@ -18,4 +18,5 @@ derive_more.workspace = true
|
|||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
mod slash_command_registry;
|
mod slash_command_registry;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext};
|
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
pub use slash_command_registry::*;
|
pub use slash_command_registry::*;
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use workspace::Workspace;
|
use workspace::{ui::IconName, Workspace};
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
SlashCommandRegistry::default_global(cx);
|
SlashCommandRegistry::default_global(cx);
|
||||||
@@ -22,7 +23,7 @@ pub trait SlashCommand: 'static + Send + Sync {
|
|||||||
fn description(&self) -> String;
|
fn description(&self) -> String;
|
||||||
fn menu_text(&self) -> String;
|
fn menu_text(&self) -> String;
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
&self,
|
self: Arc<Self>,
|
||||||
query: String,
|
query: String,
|
||||||
cancel: Arc<AtomicBool>,
|
cancel: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -49,14 +50,16 @@ pub type RenderFoldPlaceholder = Arc<
|
|||||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct SlashCommandOutput {
|
pub struct SlashCommandOutput {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
||||||
pub run_commands_in_text: bool,
|
pub run_commands_in_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct SlashCommandOutputSection<T> {
|
pub struct SlashCommandOutputSection<T> {
|
||||||
pub range: Range<T>,
|
pub range: Range<T>,
|
||||||
pub render_placeholder: RenderFoldPlaceholder,
|
pub icon: IconName,
|
||||||
|
pub label: SharedString,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,8 +141,13 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
|||||||
let auto_updater = cx.new_model(|cx| {
|
let auto_updater = cx.new_model(|cx| {
|
||||||
let updater = AutoUpdater::new(version, http_client);
|
let updater = AutoUpdater::new(version, http_client);
|
||||||
|
|
||||||
|
let poll_for_updates = ReleaseChannel::try_global(cx)
|
||||||
|
.map(|channel| channel.poll_for_updates())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
if option_env!("ZED_UPDATE_EXPLANATION").is_none()
|
if option_env!("ZED_UPDATE_EXPLANATION").is_none()
|
||||||
&& env::var("ZED_UPDATE_EXPLANATION").is_err()
|
&& env::var("ZED_UPDATE_EXPLANATION").is_err()
|
||||||
|
&& poll_for_updates
|
||||||
{
|
{
|
||||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||||
.0
|
.0
|
||||||
@@ -186,6 +191,13 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ReleaseChannel::try_global(cx)
|
||||||
|
.map(|channel| channel.poll_for_updates())
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(updater) = AutoUpdater::get(cx) {
|
if let Some(updater) = AutoUpdater::get(cx) {
|
||||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -86,10 +86,16 @@ impl Render for Breadcrumbs {
|
|||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.on_click(move |_, cx| {
|
.on_click(move |_, cx| {
|
||||||
if let Some(editor) = editor.upgrade() {
|
if let Some(editor) = editor.upgrade() {
|
||||||
outline::toggle(editor, &outline::Toggle, cx)
|
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
|
.tooltip(|cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Show symbol outline",
|
||||||
|
&editor::actions::ToggleOutline,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
None => element
|
None => element
|
||||||
// Match the height of the `ButtonLike` in the other arm.
|
// Match the height of the `ButtonLike` in the other arm.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ path = "src/call.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
no-webrtc = ["live_kit_client/no-webrtc"]
|
||||||
test-support = [
|
test-support = [
|
||||||
"client/test-support",
|
"client/test-support",
|
||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ impl ActiveCall {
|
|||||||
async fn handle_incoming_call(
|
async fn handle_incoming_call(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
@@ -142,7 +141,6 @@ impl ActiveCall {
|
|||||||
async fn handle_call_canceled(
|
async fn handle_call_canceled(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::CallCanceled>,
|
envelope: TypedEnvelope<proto::CallCanceled>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
|
|||||||
@@ -697,7 +697,6 @@ impl Room {
|
|||||||
async fn handle_room_updated(
|
async fn handle_room_updated(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::RoomUpdated>,
|
envelope: TypedEnvelope<proto::RoomUpdated>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let room = envelope
|
let room = envelope
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ impl ChannelBuffer {
|
|||||||
async fn handle_update_channel_buffer(
|
async fn handle_update_channel_buffer(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ops = update_channel_buffer
|
let ops = update_channel_buffer
|
||||||
@@ -160,7 +159,6 @@ impl ChannelBuffer {
|
|||||||
async fn handle_update_channel_buffer_collaborators(
|
async fn handle_update_channel_buffer_collaborators(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
|||||||
@@ -528,7 +528,6 @@ impl ChannelChat {
|
|||||||
async fn handle_message_sent(
|
async fn handle_message_sent(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::ChannelMessageSent>,
|
message: TypedEnvelope<proto::ChannelMessageSent>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
@@ -553,7 +552,6 @@ impl ChannelChat {
|
|||||||
async fn handle_message_removed(
|
async fn handle_message_removed(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@@ -565,7 +563,6 @@ impl ChannelChat {
|
|||||||
async fn handle_message_updated(
|
async fn handle_message_updated(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::ChannelMessageUpdate>,
|
message: TypedEnvelope<proto::ChannelMessageUpdate>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
|
|||||||
@@ -888,7 +888,6 @@ impl ChannelStore {
|
|||||||
async fn handle_update_channels(
|
async fn handle_update_channels(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateChannels>,
|
message: TypedEnvelope<proto::UpdateChannels>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
@@ -902,7 +901,6 @@ impl ChannelStore {
|
|||||||
async fn handle_update_user_channels(
|
async fn handle_update_user_channels(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateUserChannels>,
|
message: TypedEnvelope<proto::UpdateUserChannels>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ anyhow.workspace = true
|
|||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
ipc-channel = "0.18"
|
ipc-channel = "0.18"
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
||||||
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs, io,
|
env, fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
|
sync::Arc,
|
||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
};
|
};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathLikeWithPosition;
|
||||||
@@ -54,7 +56,7 @@ struct Args {
|
|||||||
fn parse_path_with_position(
|
fn parse_path_with_position(
|
||||||
argument_str: &str,
|
argument_str: &str,
|
||||||
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
|
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
|
||||||
PathLikeWithPosition::parse_str(argument_str, |path_str| {
|
PathLikeWithPosition::parse_str(argument_str, |_, path_str| {
|
||||||
Ok(Path::new(path_str).to_path_buf())
|
Ok(Path::new(path_str).to_path_buf())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -123,26 +125,34 @@ fn main() -> Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
|
let exit_status = Arc::new(Mutex::new(None));
|
||||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
|
||||||
let (tx, rx) = (handshake.requests, handshake.responses);
|
|
||||||
tx.send(CliRequest::Open {
|
|
||||||
paths,
|
|
||||||
wait: args.wait,
|
|
||||||
open_new_workspace,
|
|
||||||
dev_server_token: args.dev_server_token,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
while let Ok(response) = rx.recv() {
|
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
||||||
match response {
|
let exit_status = exit_status.clone();
|
||||||
CliResponse::Ping => {}
|
move || {
|
||||||
CliResponse::Stdout { message } => println!("{message}"),
|
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||||
CliResponse::Stderr { message } => eprintln!("{message}"),
|
let (tx, rx) = (handshake.requests, handshake.responses);
|
||||||
CliResponse::Exit { status } => std::process::exit(status),
|
tx.send(CliRequest::Open {
|
||||||
|
paths,
|
||||||
|
wait: args.wait,
|
||||||
|
open_new_workspace,
|
||||||
|
dev_server_token: args.dev_server_token,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
while let Ok(response) = rx.recv() {
|
||||||
|
match response {
|
||||||
|
CliResponse::Ping => {}
|
||||||
|
CliResponse::Stdout { message } => println!("{message}"),
|
||||||
|
CliResponse::Stderr { message } => eprintln!("{message}"),
|
||||||
|
CliResponse::Exit { status } => {
|
||||||
|
exit_status.lock().replace(status);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if args.foreground {
|
if args.foreground {
|
||||||
@@ -152,6 +162,9 @@ fn main() -> Result<()> {
|
|||||||
sender.join().unwrap()?;
|
sender.join().unwrap()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(exit_status) = exit_status.lock().take() {
|
||||||
|
std::process::exit(exit_status);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +185,6 @@ mod linux {
|
|||||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||||
use fork::Fork;
|
use fork::Fork;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use util::paths;
|
|
||||||
|
|
||||||
use crate::{Detect, InstalledApp};
|
use crate::{Detect, InstalledApp};
|
||||||
|
|
||||||
@@ -221,7 +233,7 @@ mod linux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||||
let sock_path = paths::SUPPORT_DIR.join(format!("zed-{}.sock", *RELEASE_CHANNEL));
|
let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL));
|
||||||
let sock = UnixDatagram::unbound()?;
|
let sock = UnixDatagram::unbound()?;
|
||||||
if sock.connect(&sock_path).is_err() {
|
if sock.connect(&sock_path).is_err() {
|
||||||
self.boot_background(ipc_url)?;
|
self.boot_background(ipc_url)?;
|
||||||
|
|||||||
@@ -13,20 +13,12 @@ path = "src/client.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["static-libraries"]
|
|
||||||
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
|
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||||
static-libraries = ["async-native-tls", "isahc"]
|
|
||||||
dynamic-libraries = []
|
|
||||||
|
|
||||||
# Revert the changes
|
|
||||||
# Reintroduce them, but with a feature flag.
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-recursion = "0.3"
|
async-recursion = "0.3"
|
||||||
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
|
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
|
||||||
async-native-tls = { version = "0.5.0", features = ["vendored"], optional = true}
|
|
||||||
isahc = { workspace = true, features = ["static-curl"], optional = true}
|
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
@@ -38,6 +30,7 @@ http.workspace = true
|
|||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
@@ -58,6 +51,7 @@ time.workspace = true
|
|||||||
tiny_http = "0.8"
|
tiny_http = "0.8"
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
worktree.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clock = { workspace = true, features = ["test-support"] }
|
clock = { workspace = true, features = ["test-support"] }
|
||||||
@@ -73,3 +67,5 @@ windows.workspace = true
|
|||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
cocoa.workspace = true
|
cocoa.workspace = true
|
||||||
|
isahc = { workspace = true, features = ["static-curl"] }
|
||||||
|
async-native-tls = { version = "0.5.0", features = ["vendored"] }
|
||||||
|
|||||||
@@ -509,7 +509,7 @@ impl Client {
|
|||||||
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
|
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
|
||||||
if use_zed_development_auth {
|
if use_zed_development_auth {
|
||||||
Arc::new(DevelopmentCredentialsProvider {
|
Arc::new(DevelopmentCredentialsProvider {
|
||||||
path: util::paths::CONFIG_DIR.join("development_auth"),
|
path: paths::config_dir().join("development_auth"),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Arc::new(KeychainCredentialsProvider)
|
Arc::new(KeychainCredentialsProvider)
|
||||||
@@ -689,6 +689,22 @@ impl Client {
|
|||||||
entity: WeakModel<E>,
|
entity: WeakModel<E>,
|
||||||
handler: H,
|
handler: H,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
|
where
|
||||||
|
M: EnvelopedMessage,
|
||||||
|
E: 'static,
|
||||||
|
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
||||||
|
F: 'static + Future<Output = Result<()>>,
|
||||||
|
{
|
||||||
|
self.add_message_handler_impl(entity, move |model, message, _, cx| {
|
||||||
|
handler(model, message, cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_message_handler_impl<M, E, H, F>(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
entity: WeakModel<E>,
|
||||||
|
handler: H,
|
||||||
|
) -> Subscription
|
||||||
where
|
where
|
||||||
M: EnvelopedMessage,
|
M: EnvelopedMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
@@ -737,19 +753,11 @@ impl Client {
|
|||||||
where
|
where
|
||||||
M: RequestMessage,
|
M: RequestMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
H: 'static
|
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
||||||
+ Sync
|
|
||||||
+ Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
|
|
||||||
+ Send
|
|
||||||
+ Sync,
|
|
||||||
F: 'static + Future<Output = Result<M::Response>>,
|
F: 'static + Future<Output = Result<M::Response>>,
|
||||||
{
|
{
|
||||||
self.add_message_handler(model, move |handle, envelope, this, cx| {
|
self.add_message_handler_impl(model, move |handle, envelope, this, cx| {
|
||||||
Self::respond_to_request(
|
Self::respond_to_request(envelope.receipt(), handler(handle, envelope, cx), this)
|
||||||
envelope.receipt(),
|
|
||||||
handler(handle, envelope, this.clone(), cx),
|
|
||||||
this,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,11 +765,11 @@ impl Client {
|
|||||||
where
|
where
|
||||||
M: EntityMessage,
|
M: EntityMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
H: 'static + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
||||||
F: 'static + Future<Output = Result<()>>,
|
F: 'static + Future<Output = Result<()>>,
|
||||||
{
|
{
|
||||||
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
|
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, _, cx| {
|
||||||
handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
|
handler(subscriber.downcast::<E>().unwrap(), message, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,13 +816,13 @@ impl Client {
|
|||||||
where
|
where
|
||||||
M: EntityMessage + RequestMessage,
|
M: EntityMessage + RequestMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
H: 'static + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
||||||
F: 'static + Future<Output = Result<M::Response>>,
|
F: 'static + Future<Output = Result<M::Response>>,
|
||||||
{
|
{
|
||||||
self.add_model_message_handler(move |entity, envelope, client, cx| {
|
self.add_entity_message_handler::<M, E, _, _>(move |entity, envelope, client, cx| {
|
||||||
Self::respond_to_request::<M, _>(
|
Self::respond_to_request::<M, _>(
|
||||||
envelope.receipt(),
|
envelope.receipt(),
|
||||||
handler(entity, envelope, client.clone(), cx),
|
handler(entity.downcast::<E>().unwrap(), envelope, cx),
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -1912,7 +1920,7 @@ mod tests {
|
|||||||
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
|
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
|
||||||
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
||||||
client.add_model_message_handler(
|
client.add_model_message_handler(
|
||||||
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
|
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, mut cx| {
|
||||||
match model.update(&mut cx, |model, _| model.id).unwrap() {
|
match model.update(&mut cx, |model, _| model.id).unwrap() {
|
||||||
1 => done_tx1.try_send(()).unwrap(),
|
1 => done_tx1.try_send(()).unwrap(),
|
||||||
2 => done_tx2.try_send(()).unwrap(),
|
2 => done_tx2.try_send(()).unwrap(),
|
||||||
@@ -1974,7 +1982,7 @@ mod tests {
|
|||||||
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
||||||
let subscription1 = client.add_message_handler(
|
let subscription1 = client.add_message_handler(
|
||||||
model.downgrade(),
|
model.downgrade(),
|
||||||
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
|
move |_, _: TypedEnvelope<proto::Ping>, _| {
|
||||||
done_tx1.try_send(()).unwrap();
|
done_tx1.try_send(()).unwrap();
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
},
|
},
|
||||||
@@ -1982,7 +1990,7 @@ mod tests {
|
|||||||
drop(subscription1);
|
drop(subscription1);
|
||||||
let _subscription2 = client.add_message_handler(
|
let _subscription2 = client.add_message_handler(
|
||||||
model.downgrade(),
|
model.downgrade(),
|
||||||
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
|
move |_, _: TypedEnvelope<proto::Ping>, _| {
|
||||||
done_tx2.try_send(()).unwrap();
|
done_tx2.try_send(()).unwrap();
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
},
|
},
|
||||||
@@ -2008,7 +2016,7 @@ mod tests {
|
|||||||
let (done_tx, mut done_rx) = smol::channel::unbounded();
|
let (done_tx, mut done_rx) = smol::channel::unbounded();
|
||||||
let subscription = client.add_message_handler(
|
let subscription = client.add_message_handler(
|
||||||
model.clone().downgrade(),
|
model.clone().downgrade(),
|
||||||
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
|
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, mut cx| {
|
||||||
model
|
model
|
||||||
.update(&mut cx, |model, _| model.subscription.take())
|
.update(&mut cx, |model, _| model.subscription.take())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod event_coalescer;
|
|||||||
use crate::{ChannelId, TelemetrySettings};
|
use crate::{ChannelId, TelemetrySettings};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clock::SystemClock;
|
use clock::SystemClock;
|
||||||
|
use collections::{HashMap, HashSet};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||||
use http::{self, HttpClient, HttpClientWithUrl, Method};
|
use http::{self, HttpClient, HttpClientWithUrl, Method};
|
||||||
@@ -23,6 +24,7 @@ use tempfile::NamedTempFile;
|
|||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||||
|
|
||||||
use self::event_coalescer::EventCoalescer;
|
use self::event_coalescer::EventCoalescer;
|
||||||
|
|
||||||
@@ -47,12 +49,31 @@ struct TelemetryState {
|
|||||||
first_event_date_time: Option<DateTime<Utc>>,
|
first_event_date_time: Option<DateTime<Utc>>,
|
||||||
event_coalescer: EventCoalescer,
|
event_coalescer: EventCoalescer,
|
||||||
max_queue_size: usize,
|
max_queue_size: usize,
|
||||||
|
worktree_id_map: WorktreeIdMap,
|
||||||
|
|
||||||
os_name: String,
|
os_name: String,
|
||||||
app_version: String,
|
app_version: String,
|
||||||
os_version: Option<String>,
|
os_version: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WorktreeIdMap(HashMap<String, ProjectCache>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProjectCache {
|
||||||
|
name: String,
|
||||||
|
worktree_ids_reported: HashSet<WorktreeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectCache {
|
||||||
|
fn new(name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
worktree_ids_reported: HashSet::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
const MAX_QUEUE_LEN: usize = 5;
|
const MAX_QUEUE_LEN: usize = 5;
|
||||||
|
|
||||||
@@ -180,6 +201,16 @@ impl Telemetry {
|
|||||||
first_event_date_time: None,
|
first_event_date_time: None,
|
||||||
event_coalescer: EventCoalescer::new(clock.clone()),
|
event_coalescer: EventCoalescer::new(clock.clone()),
|
||||||
max_queue_size: MAX_QUEUE_LEN,
|
max_queue_size: MAX_QUEUE_LEN,
|
||||||
|
worktree_id_map: WorktreeIdMap(HashMap::from_iter([
|
||||||
|
(
|
||||||
|
"yarn.lock".to_string(),
|
||||||
|
ProjectCache::new("yarn".to_string()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"package.json".to_string(),
|
||||||
|
ProjectCache::new("node".to_string()),
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
|
||||||
os_version: None,
|
os_version: None,
|
||||||
os_name: os_name(),
|
os_name: os_name(),
|
||||||
@@ -192,7 +223,7 @@ impl Telemetry {
|
|||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(tempfile) =
|
if let Some(tempfile) =
|
||||||
NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err()
|
NamedTempFile::new_in(paths::config_dir().as_path()).log_err()
|
||||||
{
|
{
|
||||||
state.lock().log_file = Some(tempfile);
|
state.lock().log_file = Some(tempfile);
|
||||||
}
|
}
|
||||||
@@ -450,6 +481,52 @@ impl Telemetry {
|
|||||||
self.report_event(event)
|
self.report_event(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_discovered_project_events(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
worktree_id: WorktreeId,
|
||||||
|
updated_entries_set: &UpdatedEntriesSet,
|
||||||
|
) {
|
||||||
|
let project_names: Vec<String> = {
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
state
|
||||||
|
.worktree_id_map
|
||||||
|
.0
|
||||||
|
.iter_mut()
|
||||||
|
.filter_map(|(project_file_name, project_type_telemetry)| {
|
||||||
|
if project_type_telemetry
|
||||||
|
.worktree_ids_reported
|
||||||
|
.contains(&worktree_id)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let project_file_found = updated_entries_set.iter().any(|(path, _, _)| {
|
||||||
|
path.as_ref()
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.map(|name_str| name_str == project_file_name)
|
||||||
|
.unwrap_or(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
if !project_file_found {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
project_type_telemetry
|
||||||
|
.worktree_ids_reported
|
||||||
|
.insert(worktree_id);
|
||||||
|
|
||||||
|
Some(project_type_telemetry.name.clone())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Done on purpose to avoid calling `self.state.lock()` multiple times
|
||||||
|
for project_name in project_names {
|
||||||
|
self.report_app_event(format!("open {} project", project_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn report_event(self: &Arc<Self>, event: Event) {
|
fn report_event(self: &Arc<Self>, event: Event) {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
|
||||||
@@ -513,10 +590,6 @@ impl Telemetry {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ZED_CLIENT_CHECKSUM_SEED.is_none() {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
self.executor
|
self.executor
|
||||||
.spawn(
|
.spawn(
|
||||||
@@ -552,9 +625,7 @@ impl Telemetry {
|
|||||||
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(checksum) = calculate_json_checksum(&json_bytes) else {
|
let checksum = calculate_json_checksum(&json_bytes).unwrap_or("".to_string());
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = http::Request::builder()
|
let request = http::Request::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ impl UserStore {
|
|||||||
async fn handle_update_invite_info(
|
async fn handle_update_invite_info(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@@ -258,7 +257,6 @@ impl UserStore {
|
|||||||
async fn handle_show_contacts(
|
async fn handle_show_contacts(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
_: TypedEnvelope<proto::ShowContacts>,
|
_: TypedEnvelope<proto::ShowContacts>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
||||||
@@ -272,7 +270,6 @@ impl UserStore {
|
|||||||
async fn handle_update_contacts(
|
async fn handle_update_contacts(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateContacts>,
|
message: TypedEnvelope<proto::UpdateContacts>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: anthropic
|
name: anthropic
|
||||||
key: api_key
|
key: api_key
|
||||||
|
- name: GOOGLE_AI_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: google-ai
|
||||||
|
key: api_key
|
||||||
- name: BLOB_STORE_ACCESS_KEY
|
- name: BLOB_STORE_ACCESS_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
@@ -308,13 +308,12 @@ pub async fn post_panic(
|
|||||||
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
||||||
let panic = report.panic;
|
let panic = report.panic;
|
||||||
|
|
||||||
// better OS reporting for linux (because linux is hard):
|
if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
|
||||||
// - Remove os_version/app_version/os_name from the gpui platform trait
|
return Err(Error::Http(
|
||||||
// - Move platform processing data into client/telemetry
|
StatusCode::BAD_REQUEST,
|
||||||
// - Duplicate some small code in macOS platform for a version check
|
"invalid os version".into(),
|
||||||
// - Add GPUI API for reporting the selected platform integration
|
))?;
|
||||||
// - macos-blade, macos-metal, linux-X11, linux-headless
|
}
|
||||||
// if cfg(macos( { "Macos" } else { "Linux-{cx.compositor_name()"} ))
|
|
||||||
|
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
service = "client",
|
service = "client",
|
||||||
@@ -402,12 +401,7 @@ pub async fn post_events(
|
|||||||
))?;
|
))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
if checksum != expected {
|
let checksum_matched = checksum == expected;
|
||||||
return Err(Error::Http(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
"invalid checksum".into(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let request_body: telemetry_events::EventRequestBody =
|
let request_body: telemetry_events::EventRequestBody =
|
||||||
serde_json::from_slice(&body).map_err(|err| {
|
serde_json::from_slice(&body).map_err(|err| {
|
||||||
@@ -432,6 +426,7 @@ pub async fn post_events(
|
|||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
country_code.clone(),
|
country_code.clone(),
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
// Needed for clients sending old copilot_event types
|
// Needed for clients sending old copilot_event types
|
||||||
Event::Copilot(_) => {}
|
Event::Copilot(_) => {}
|
||||||
@@ -444,6 +439,7 @@ pub async fn post_events(
|
|||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
country_code.clone(),
|
country_code.clone(),
|
||||||
|
checksum_matched,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
||||||
@@ -451,6 +447,7 @@ pub async fn post_events(
|
|||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::Assistant(event) => {
|
Event::Assistant(event) => {
|
||||||
to_upload
|
to_upload
|
||||||
@@ -460,6 +457,7 @@ pub async fn post_events(
|
|||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
||||||
@@ -467,36 +465,42 @@ pub async fn post_events(
|
|||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
)),
|
)),
|
||||||
Event::Extension(event) => {
|
Event::Extension(event) => {
|
||||||
let metadata = app
|
let metadata = app
|
||||||
@@ -511,6 +515,7 @@ pub async fn post_events(
|
|||||||
&request_body,
|
&request_body,
|
||||||
metadata,
|
metadata,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
|
checksum_matched,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,29 +663,30 @@ where
|
|||||||
|
|
||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct EditorEventRow {
|
pub struct EditorEventRow {
|
||||||
pub installation_id: String,
|
installation_id: String,
|
||||||
pub operation: String,
|
operation: String,
|
||||||
pub app_version: String,
|
app_version: String,
|
||||||
pub file_extension: String,
|
file_extension: String,
|
||||||
pub os_name: String,
|
os_name: String,
|
||||||
pub os_version: String,
|
os_version: String,
|
||||||
pub release_channel: String,
|
release_channel: String,
|
||||||
pub signed_in: bool,
|
signed_in: bool,
|
||||||
pub vim_mode: bool,
|
vim_mode: bool,
|
||||||
#[serde(serialize_with = "serialize_country_code")]
|
#[serde(serialize_with = "serialize_country_code")]
|
||||||
pub country_code: String,
|
country_code: String,
|
||||||
pub region_code: String,
|
region_code: String,
|
||||||
pub city: String,
|
city: String,
|
||||||
pub time: i64,
|
time: i64,
|
||||||
pub copilot_enabled: bool,
|
copilot_enabled: bool,
|
||||||
pub copilot_enabled_for_language: bool,
|
copilot_enabled_for_language: bool,
|
||||||
pub historical_event: bool,
|
historical_event: bool,
|
||||||
pub architecture: String,
|
architecture: String,
|
||||||
pub is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
pub session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
pub major: Option<i32>,
|
major: Option<i32>,
|
||||||
pub minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
pub patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorEventRow {
|
impl EditorEventRow {
|
||||||
@@ -690,6 +696,7 @@ impl EditorEventRow {
|
|||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
country_code: Option<String>,
|
country_code: Option<String>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -700,6 +707,7 @@ impl EditorEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -743,6 +751,7 @@ pub struct InlineCompletionEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlineCompletionEventRow {
|
impl InlineCompletionEventRow {
|
||||||
@@ -752,6 +761,7 @@ impl InlineCompletionEventRow {
|
|||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
country_code: Option<String>,
|
country_code: Option<String>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -762,6 +772,7 @@ impl InlineCompletionEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -790,6 +801,7 @@ pub struct CallEventRow {
|
|||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
|
checksum_matched: bool,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: String,
|
installation_id: String,
|
||||||
@@ -809,6 +821,7 @@ impl CallEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -819,6 +832,7 @@ impl CallEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -840,6 +854,7 @@ pub struct AssistantEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -864,6 +879,7 @@ impl AssistantEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -874,6 +890,7 @@ impl AssistantEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -908,6 +925,7 @@ pub struct CpuEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CpuEventRow {
|
impl CpuEventRow {
|
||||||
@@ -916,6 +934,7 @@ impl CpuEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -926,6 +945,7 @@ impl CpuEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -946,6 +966,7 @@ pub struct MemoryEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -967,6 +988,7 @@ impl MemoryEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -977,6 +999,7 @@ impl MemoryEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -997,6 +1020,7 @@ pub struct AppEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -1017,6 +1041,7 @@ impl AppEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1027,6 +1052,7 @@ impl AppEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -1046,6 +1072,7 @@ pub struct SettingEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -1066,6 +1093,7 @@ impl SettingEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1075,6 +1103,7 @@ impl SettingEventRow {
|
|||||||
app_version: body.app_version.clone(),
|
app_version: body.app_version.clone(),
|
||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
|
checksum_matched,
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
@@ -1096,6 +1125,7 @@ pub struct ExtensionEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -1121,6 +1151,7 @@ impl ExtensionEventRow {
|
|||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
extension_metadata: Option<ExtensionMetadata>,
|
extension_metadata: Option<ExtensionMetadata>,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1131,6 +1162,7 @@ impl ExtensionEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -1162,6 +1194,7 @@ pub struct EditEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -1186,6 +1219,7 @@ impl EditEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1199,6 +1233,7 @@ impl EditEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -1220,6 +1255,7 @@ pub struct ActionEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
checksum_matched: bool,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
@@ -1242,6 +1278,7 @@ impl ActionEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
checksum_matched: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1252,6 +1289,7 @@ impl ActionEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
|
checksum_matched,
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
|||||||
@@ -379,6 +379,7 @@ fn metadata_from_extension_and_version(
|
|||||||
|
|
||||||
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
|
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
|
||||||
chrono::DateTime::from_naive_utc_and_offset(
|
chrono::DateTime::from_naive_utc_and_offset(
|
||||||
|
#[allow(deprecated)]
|
||||||
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
|
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
|
||||||
Utc,
|
Utc,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2583,14 +2583,13 @@ async fn rejoin_dev_server_projects(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
notify_rejoined_projects(&mut rejoined_projects, &session)?;
|
|
||||||
|
|
||||||
response.send(proto::RejoinRemoteProjectsResponse {
|
response.send(proto::RejoinRemoteProjectsResponse {
|
||||||
rejoined_projects: rejoined_projects
|
rejoined_projects: rejoined_projects
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|project| project.to_proto())
|
.map(|project| project.to_proto())
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
})?;
|
||||||
|
notify_rejoined_projects(&mut rejoined_projects, &session)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reconnect_dev_server(
|
async fn reconnect_dev_server(
|
||||||
@@ -4503,6 +4502,7 @@ async fn complete_with_google_ai(
|
|||||||
session.http_client.clone(),
|
session.http_client.clone(),
|
||||||
google_ai::API_URL,
|
google_ai::API_URL,
|
||||||
api_key.as_ref(),
|
api_key.as_ref(),
|
||||||
|
&request.model.clone(),
|
||||||
crate::ai::language_model_request_to_google_ai(request)?,
|
crate::ai::language_model_request_to_google_ai(request)?,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ impl ConnectionPool {
|
|||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.connections.clear();
|
self.connections.clear();
|
||||||
self.connected_users.clear();
|
self.connected_users.clear();
|
||||||
|
self.connected_dev_servers.clear();
|
||||||
self.channels.clear();
|
self.channels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -504,6 +504,29 @@ async fn test_dev_server_reconnect(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_dev_server_restart(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
|
||||||
|
let (server, client1) = TestServer::start1(cx1).await;
|
||||||
|
|
||||||
|
let (_dev_server, remote_workspace) =
|
||||||
|
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
|
||||||
|
let cx = VisualTestContext::from_window(remote_workspace.into(), cx1).as_mut();
|
||||||
|
|
||||||
|
server.reset().await;
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
cx.simulate_keystrokes("cmd-p 1 enter");
|
||||||
|
remote_workspace
|
||||||
|
.update(cx, |ws, cx| {
|
||||||
|
ws.active_item_as::<Editor>(cx)
|
||||||
|
.unwrap()
|
||||||
|
.update(cx, |ed, cx| {
|
||||||
|
assert_eq!(ed.text(cx).to_string(), "remote\nremote\nremote");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_create_dev_server_project_path_validation(
|
async fn test_create_dev_server_project_path_validation(
|
||||||
cx1: &mut gpui::TestAppContext,
|
cx1: &mut gpui::TestAppContext,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use language::{
|
|||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use project::{
|
use project::{
|
||||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||||
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
|
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||||
};
|
};
|
||||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||||
use rpc::RECEIVE_TIMEOUT;
|
use rpc::RECEIVE_TIMEOUT;
|
||||||
@@ -344,7 +344,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
|||||||
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
@@ -461,7 +461,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
|||||||
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
@@ -585,7 +585,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
||||||
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
||||||
@@ -607,7 +607,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
||||||
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
||||||
@@ -619,7 +619,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
changes: Some(
|
changes: Some(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
vec![lsp::TextEdit::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(
|
lsp::Range::new(
|
||||||
lsp::Position::new(1, 22),
|
lsp::Position::new(1, 22),
|
||||||
@@ -629,7 +629,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||||
vec![lsp::TextEdit::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(
|
lsp::Range::new(
|
||||||
lsp::Position::new(0, 0),
|
lsp::Position::new(0, 0),
|
||||||
@@ -689,7 +689,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
changes: Some(
|
changes: Some(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
vec![lsp::TextEdit::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(
|
lsp::Range::new(
|
||||||
lsp::Position::new(1, 22),
|
lsp::Position::new(1, 22),
|
||||||
@@ -699,7 +699,7 @@ async fn test_collaborating_with_code_actions(
|
|||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||||
vec![lsp::TextEdit::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(
|
lsp::Range::new(
|
||||||
lsp::Position::new(0, 0),
|
lsp::Position::new(0, 0),
|
||||||
@@ -897,14 +897,14 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||||||
changes: Some(
|
changes: Some(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
lsp::Uri::from_file_path("/dir/one.rs").unwrap().into(),
|
lsp::Url::from_file_path("/dir/one.rs").unwrap(),
|
||||||
vec![lsp::TextEdit::new(
|
vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
"THREE".to_string(),
|
"THREE".to_string(),
|
||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
lsp::Uri::from_file_path("/dir/two.rs").unwrap().into(),
|
lsp::Url::from_file_path("/dir/two.rs").unwrap(),
|
||||||
vec![
|
vec![
|
||||||
lsp::TextEdit::new(
|
lsp::TextEdit::new(
|
||||||
lsp::Range::new(
|
lsp::Range::new(
|
||||||
@@ -1006,6 +1006,8 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.start_progress("the-token").await;
|
fake_language_server.start_progress("the-token").await;
|
||||||
|
|
||||||
|
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
||||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||||
@@ -1015,11 +1017,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
assert_eq!(status.pending_work.len(), 1);
|
assert_eq!(status.pending_work.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1036,10 +1037,11 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
|
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
||||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||||
@@ -1049,11 +1051,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
|
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
assert_eq!(status.pending_work.len(), 1);
|
assert_eq!(status.pending_work.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1063,7 +1064,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
});
|
});
|
||||||
|
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
assert_eq!(status.pending_work.len(), 1);
|
assert_eq!(status.pending_work.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1203,7 +1204,7 @@ async fn test_share_project(
|
|||||||
buffer_a.read_with(cx_a, |buffer, _| {
|
buffer_a.read_with(cx_a, |buffer, _| {
|
||||||
buffer
|
buffer
|
||||||
.snapshot()
|
.snapshot()
|
||||||
.remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
|
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
|
||||||
.count()
|
.count()
|
||||||
== 1
|
== 1
|
||||||
});
|
});
|
||||||
@@ -1244,7 +1245,7 @@ async fn test_share_project(
|
|||||||
buffer_a.read_with(cx_a, |buffer, _| {
|
buffer_a.read_with(cx_a, |buffer, _| {
|
||||||
buffer
|
buffer
|
||||||
.snapshot()
|
.snapshot()
|
||||||
.remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
|
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
|
||||||
.count()
|
.count()
|
||||||
== 0
|
== 0
|
||||||
});
|
});
|
||||||
@@ -1313,7 +1314,7 @@ async fn test_on_input_format_from_host_to_guest(
|
|||||||
|params, _| async move {
|
|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
@@ -1441,7 +1442,7 @@ async fn test_on_input_format_from_guest_to_host(
|
|||||||
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
@@ -1610,7 +1611,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||||||
async move {
|
async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
||||||
Ok(Some(vec![lsp::InlayHint {
|
Ok(Some(vec![lsp::InlayHint {
|
||||||
@@ -1873,7 +1874,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
|||||||
async move {
|
async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
|
let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
|
||||||
let character = if other_hints { 0 } else { 2 };
|
let character = if other_hints { 0 } else { 2 };
|
||||||
|
|||||||
@@ -3897,7 +3897,7 @@ async fn test_collaborating_with_diagnostics(
|
|||||||
.await;
|
.await;
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
diagnostics: vec![lsp::Diagnostic {
|
diagnostics: vec![lsp::Diagnostic {
|
||||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||||
@@ -3917,7 +3917,7 @@ async fn test_collaborating_with_diagnostics(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
diagnostics: vec![lsp::Diagnostic {
|
diagnostics: vec![lsp::Diagnostic {
|
||||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||||
@@ -3991,7 +3991,7 @@ async fn test_collaborating_with_diagnostics(
|
|||||||
// Simulate a language server reporting more errors for a file.
|
// Simulate a language server reporting more errors for a file.
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
diagnostics: vec![
|
diagnostics: vec![
|
||||||
lsp::Diagnostic {
|
lsp::Diagnostic {
|
||||||
@@ -4085,7 +4085,7 @@ async fn test_collaborating_with_diagnostics(
|
|||||||
// Simulate a language server reporting no errors for a file.
|
// Simulate a language server reporting no errors for a file.
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
diagnostics: vec![],
|
diagnostics: vec![],
|
||||||
},
|
},
|
||||||
@@ -4189,9 +4189,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
|||||||
for file_name in file_names {
|
for file_name in file_names {
|
||||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Uri::from_file_path(Path::new("/test").join(file_name))
|
uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
version: None,
|
version: None,
|
||||||
diagnostics: vec![lsp::Diagnostic {
|
diagnostics: vec![lsp::Diagnostic {
|
||||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||||
@@ -4609,7 +4607,7 @@ async fn test_definition(
|
|||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Location::new(
|
lsp::Location::new(
|
||||||
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
|
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
),
|
),
|
||||||
)))
|
)))
|
||||||
@@ -4638,7 +4636,7 @@ async fn test_definition(
|
|||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Location::new(
|
lsp::Location::new(
|
||||||
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
|
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
|
||||||
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
||||||
),
|
),
|
||||||
)))
|
)))
|
||||||
@@ -4674,7 +4672,7 @@ async fn test_definition(
|
|||||||
);
|
);
|
||||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Location::new(
|
lsp::Location::new(
|
||||||
lsp::Uri::from_file_path("/root/dir-2/c.rs").unwrap().into(),
|
lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
|
||||||
lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
|
lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
|
||||||
),
|
),
|
||||||
)))
|
)))
|
||||||
@@ -4774,7 +4772,7 @@ async fn test_references(
|
|||||||
// User is informed that a request is pending.
|
// User is informed that a request is pending.
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().cloned().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
status.pending_work.values().next().unwrap().message,
|
status.pending_work.values().next().unwrap().message,
|
||||||
@@ -4786,21 +4784,15 @@ async fn test_references(
|
|||||||
lsp_response_tx
|
lsp_response_tx
|
||||||
.unbounded_send(Ok(Some(vec![
|
.unbounded_send(Ok(Some(vec![
|
||||||
lsp::Location {
|
lsp::Location {
|
||||||
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
|
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
|
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
|
||||||
},
|
},
|
||||||
lsp::Location {
|
lsp::Location {
|
||||||
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
|
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
|
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
|
||||||
},
|
},
|
||||||
lsp::Location {
|
lsp::Location {
|
||||||
uri: lsp::Uri::from_file_path("/root/dir-2/three.rs")
|
uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
|
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
|
||||||
},
|
},
|
||||||
])))
|
])))
|
||||||
@@ -4810,7 +4802,7 @@ async fn test_references(
|
|||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, cx| {
|
project_b.read_with(cx_b, |project, cx| {
|
||||||
// User is informed that a request is no longer pending.
|
// User is informed that a request is no longer pending.
|
||||||
let status = project.language_server_statuses().next().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert!(status.pending_work.is_empty());
|
assert!(status.pending_work.is_empty());
|
||||||
|
|
||||||
assert_eq!(references.len(), 3);
|
assert_eq!(references.len(), 3);
|
||||||
@@ -4838,7 +4830,7 @@ async fn test_references(
|
|||||||
// User is informed that a request is pending.
|
// User is informed that a request is pending.
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().cloned().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
status.pending_work.values().next().unwrap().message,
|
status.pending_work.values().next().unwrap().message,
|
||||||
@@ -4855,7 +4847,7 @@ async fn test_references(
|
|||||||
// User is informed that the request is no longer pending.
|
// User is informed that the request is no longer pending.
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap();
|
let status = project.language_server_statuses().next().unwrap().1;
|
||||||
assert!(status.pending_work.is_empty());
|
assert!(status.pending_work.is_empty());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4912,7 +4904,15 @@ async fn test_project_search(
|
|||||||
let mut results = HashMap::default();
|
let mut results = HashMap::default();
|
||||||
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
||||||
project.search(
|
project.search(
|
||||||
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
|
SearchQuery::text(
|
||||||
|
"world",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -5300,9 +5300,7 @@ async fn test_project_symbols(
|
|||||||
lsp::SymbolInformation {
|
lsp::SymbolInformation {
|
||||||
name: "TWO".into(),
|
name: "TWO".into(),
|
||||||
location: lsp::Location {
|
location: lsp::Location {
|
||||||
uri: lsp::Uri::from_file_path("/code/crate-2/two.rs")
|
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
},
|
},
|
||||||
kind: lsp::SymbolKind::CONSTANT,
|
kind: lsp::SymbolKind::CONSTANT,
|
||||||
@@ -5392,7 +5390,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||||
lsp::Location::new(
|
lsp::Location::new(
|
||||||
lsp::Uri::from_file_path("/root/b.rs").unwrap().into(),
|
lsp::Url::from_file_path("/root/b.rs").unwrap(),
|
||||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||||
),
|
),
|
||||||
)))
|
)))
|
||||||
|
|||||||
@@ -875,8 +875,15 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
|
|
||||||
let mut search = project.update(cx, |project, cx| {
|
let mut search = project.update(cx, |project, cx| {
|
||||||
project.search(
|
project.search(
|
||||||
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
|
SearchQuery::text(
|
||||||
.unwrap(),
|
query,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1101,7 +1108,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
files
|
files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|file| lsp::Location {
|
.map(|file| lsp::Location {
|
||||||
uri: lsp::Uri::from_file_path(file).unwrap().into(),
|
uri: lsp::Url::from_file_path(file).unwrap(),
|
||||||
range: Default::default(),
|
range: Default::default(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|||||||
@@ -35,10 +35,12 @@ call.workspace = true
|
|||||||
channel.workspace = true
|
channel.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
command_palette.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
emojis.workspace = true
|
emojis.workspace = true
|
||||||
extensions_ui.workspace = true
|
extensions_ui.workspace = true
|
||||||
|
feedback.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use settings::Settings;
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{
|
use ui::{
|
||||||
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu,
|
||||||
TabBar, Tooltip,
|
TabBar, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
@@ -679,7 +679,7 @@ impl ChatPanel {
|
|||||||
cx,
|
cx,
|
||||||
div()
|
div()
|
||||||
.child(
|
.child(
|
||||||
popover_menu(("menu", message_id))
|
PopoverMenu::new(("menu", message_id))
|
||||||
.trigger(IconButton::new(
|
.trigger(IconButton::new(
|
||||||
("trigger", message_id),
|
("trigger", message_id),
|
||||||
IconName::Ellipsis,
|
IconName::Ellipsis,
|
||||||
|
|||||||
@@ -25,8 +25,15 @@ use crate::panel_settings::MessageEditorSettings;
|
|||||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MENTIONS_SEARCH: SearchQuery =
|
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
|
||||||
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
|
"@[-_\\w]+",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ use gpui::{
|
|||||||
use project::{Project, RepositoryEntry};
|
use project::{Project, RepositoryEntry};
|
||||||
use recent_projects::RecentProjects;
|
use recent_projects::RecentProjects;
|
||||||
use rpc::proto::{self, DevServerStatus};
|
use rpc::proto::{self, DevServerStatus};
|
||||||
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::ActiveTheme;
|
use theme::{ActiveTheme, ThemeSettings};
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
|
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
|
||||||
ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, TintColor, TitleBar, Tooltip,
|
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
||||||
@@ -73,6 +74,7 @@ impl Render for CollabTitlebarItem {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.children(self.render_application_menu(cx))
|
||||||
.children(self.render_project_host(cx))
|
.children(self.render_project_host(cx))
|
||||||
.child(self.render_project_name(cx))
|
.child(self.render_project_name(cx))
|
||||||
.children(self.render_project_branch(cx))
|
.children(self.render_project_branch(cx))
|
||||||
@@ -386,8 +388,173 @@ impl CollabTitlebarItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve if you are in a room -> render_project_owner
|
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||||
// render_project_owner -> resolve if you are in a room -> Option<foo>
|
cfg!(not(target_os = "macos")).then(|| {
|
||||||
|
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||||
|
let font = cx.text_style().font();
|
||||||
|
let font_id = cx.text_system().resolve_font(&font);
|
||||||
|
let width = cx
|
||||||
|
.text_system()
|
||||||
|
.typographic_bounds(font_id, ui_font_size, 'm')
|
||||||
|
.unwrap()
|
||||||
|
.size
|
||||||
|
.width
|
||||||
|
* 3.0;
|
||||||
|
|
||||||
|
PopoverMenu::new("application-menu")
|
||||||
|
.menu(move |cx| {
|
||||||
|
let width = width;
|
||||||
|
ContextMenu::build(cx, move |menu, _cx| {
|
||||||
|
let width = width;
|
||||||
|
menu.header("Workspace")
|
||||||
|
.action("Open Command Palette", Box::new(command_palette::Toggle))
|
||||||
|
.custom_row(move |cx| {
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.justify_between()
|
||||||
|
.cursor(gpui::CursorStyle::Arrow)
|
||||||
|
.child(Label::new("Buffer Font Size"))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.child(div().w(px(16.0)))
|
||||||
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"reset-buffer-zoom",
|
||||||
|
IconName::RotateCcw,
|
||||||
|
)
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(
|
||||||
|
zed_actions::ResetBufferFontSize,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("--buffer-zoom", IconName::Dash)
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(
|
||||||
|
zed_actions::DecreaseBufferFontSize,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(width)
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.justify_around()
|
||||||
|
.child(Label::new(
|
||||||
|
theme::get_buffer_font_size(cx).to_string(),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("+-buffer-zoom", IconName::Plus)
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(
|
||||||
|
zed_actions::IncreaseBufferFontSize,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
.custom_row(move |cx| {
|
||||||
|
div()
|
||||||
|
.w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.justify_between()
|
||||||
|
.cursor(gpui::CursorStyle::Arrow)
|
||||||
|
.child(Label::new("UI Font Size"))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.child(
|
||||||
|
IconButton::new(
|
||||||
|
"reset-ui-zoom",
|
||||||
|
IconName::RotateCcw,
|
||||||
|
)
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(
|
||||||
|
zed_actions::ResetUiFontSize,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("--ui-zoom", IconName::Dash)
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(
|
||||||
|
zed_actions::DecreaseUiFontSize,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(width)
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.justify_around()
|
||||||
|
.child(Label::new(
|
||||||
|
theme::get_ui_font_size(cx).to_string(),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("+-ui-zoom", IconName::Plus)
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(Box::new(
|
||||||
|
zed_actions::IncreaseUiFontSize,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
.header("Project")
|
||||||
|
.action(
|
||||||
|
"Add Folder to Project...",
|
||||||
|
Box::new(workspace::AddFolderToProject),
|
||||||
|
)
|
||||||
|
.action("Open a new Project...", Box::new(workspace::Open))
|
||||||
|
.action(
|
||||||
|
"Open Recent Projects...",
|
||||||
|
Box::new(recent_projects::OpenRecent {
|
||||||
|
create_new_window: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.header("Help")
|
||||||
|
.action("About Zed", Box::new(zed_actions::About))
|
||||||
|
.action("Welcome", Box::new(workspace::Welcome))
|
||||||
|
.link(
|
||||||
|
"Documentation",
|
||||||
|
Box::new(zed_actions::OpenBrowser {
|
||||||
|
url: "https://zed.dev/docs".into(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.action("Give Feedback", Box::new(feedback::GiveFeedback))
|
||||||
|
.action("Check for Updates", Box::new(auto_update::Check))
|
||||||
|
.action("View Telemetry", Box::new(zed_actions::OpenTelemetryLog))
|
||||||
|
.action(
|
||||||
|
"View Dependency Licenses",
|
||||||
|
Box::new(zed_actions::OpenLicenses),
|
||||||
|
)
|
||||||
|
.separator()
|
||||||
|
.action("Quit", Box::new(zed_actions::Quit))
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.trigger(
|
||||||
|
IconButton::new("application-menu", ui::IconName::Menu)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.tooltip(|cx| Tooltip::text("Open Application Menu", cx))
|
||||||
|
.icon_size(IconSize::Small),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||||
if let Some(dev_server) =
|
if let Some(dev_server) =
|
||||||
@@ -739,12 +906,13 @@ impl CollabTitlebarItem {
|
|||||||
|
|
||||||
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||||
if let Some(user) = self.user_store.read(cx).current_user() {
|
if let Some(user) = self.user_store.read(cx).current_user() {
|
||||||
popover_menu("user-menu")
|
PopoverMenu::new("user-menu")
|
||||||
.menu(|cx| {
|
.menu(|cx| {
|
||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
|
||||||
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
||||||
|
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
||||||
.separator()
|
.separator()
|
||||||
.action("Sign Out", client::SignOut.boxed_clone())
|
.action("Sign Out", client::SignOut.boxed_clone())
|
||||||
})
|
})
|
||||||
@@ -767,12 +935,13 @@ impl CollabTitlebarItem {
|
|||||||
)
|
)
|
||||||
.anchor(gpui::AnchorCorner::TopRight)
|
.anchor(gpui::AnchorCorner::TopRight)
|
||||||
} else {
|
} else {
|
||||||
popover_menu("user-menu")
|
PopoverMenu::new("user-menu")
|
||||||
.menu(|cx| {
|
.menu(|cx| {
|
||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
|
||||||
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
||||||
|
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -124,5 +124,6 @@ fn notification_window_options(
|
|||||||
display_id: Some(screen.id()),
|
display_id: Some(screen.id()),
|
||||||
window_background: WindowBackgroundAppearance::default(),
|
window_background: WindowBackgroundAppearance::default(),
|
||||||
app_id: Some(app_id.to_owned()),
|
app_id: Some(app_id.to_owned()),
|
||||||
|
window_min_size: Size::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ use crate::notifications::collab_notification::CollabNotification;
|
|||||||
use call::{ActiveCall, IncomingCall};
|
use call::{ActiveCall, IncomingCall};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{prelude::*, AppContext, WindowHandle};
|
use gpui::{prelude::*, AppContext, WindowHandle};
|
||||||
use settings::Settings;
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::{prelude::*, Button, Label};
|
use ui::{prelude::*, Button, Label};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
@@ -113,13 +112,7 @@ impl IncomingCallNotification {
|
|||||||
|
|
||||||
impl Render for IncomingCallNotification {
|
impl Render for IncomingCallNotification {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
// TODO: Is there a better place for us to initialize the font?
|
let ui_font = theme::setup_ui_font(cx);
|
||||||
let (ui_font, ui_font_size) = {
|
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
|
||||||
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.set_rem_size(ui_font_size);
|
|
||||||
|
|
||||||
div().size_full().font(ui_font).child(
|
div().size_full().font(ui_font).child(
|
||||||
CollabNotification::new(
|
CollabNotification::new(
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ use call::{room, ActiveCall};
|
|||||||
use client::User;
|
use client::User;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, Size};
|
use gpui::{AppContext, Size};
|
||||||
use settings::Settings;
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::{prelude::*, Button, Label};
|
use ui::{prelude::*, Button, Label};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
@@ -124,13 +123,7 @@ impl ProjectSharedNotification {
|
|||||||
|
|
||||||
impl Render for ProjectSharedNotification {
|
impl Render for ProjectSharedNotification {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
// TODO: Is there a better place for us to initialize the font?
|
let ui_font = theme::setup_ui_font(cx);
|
||||||
let (ui_font, ui_font_size) = {
|
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
|
||||||
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.set_rem_size(ui_font_size);
|
|
||||||
|
|
||||||
div().size_full().font(ui_font).child(
|
div().size_full().font(ui_font).child(
|
||||||
CollabNotification::new(
|
CollabNotification::new(
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ lsp.workspace = true
|
|||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
node_runtime.workspace = true
|
node_runtime.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::{fs::remove_matching, maybe, paths, ResultExt};
|
use util::{fs::remove_matching, maybe, ResultExt};
|
||||||
|
|
||||||
pub use copilot_completion_provider::CopilotCompletionProvider;
|
pub use copilot_completion_provider::CopilotCompletionProvider;
|
||||||
pub use sign_in::CopilotCodeVerification;
|
pub use sign_in::CopilotCodeVerification;
|
||||||
@@ -188,7 +188,7 @@ impl Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RegisteredBuffer {
|
struct RegisteredBuffer {
|
||||||
uri: lsp::RawUri,
|
uri: lsp::Url,
|
||||||
language_id: String,
|
language_id: String,
|
||||||
snapshot: BufferSnapshot,
|
snapshot: BufferSnapshot,
|
||||||
snapshot_version: i32,
|
snapshot_version: i32,
|
||||||
@@ -644,7 +644,7 @@ impl Copilot {
|
|||||||
registered_buffers
|
registered_buffers
|
||||||
.entry(buffer.entity_id())
|
.entry(buffer.entity_id())
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
let uri = uri_for_buffer(buffer, cx);
|
let uri: lsp::Url = uri_for_buffer(buffer, cx);
|
||||||
let language_id = id_for_language(buffer.read(cx).language());
|
let language_id = id_for_language(buffer.read(cx).language());
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
server
|
server
|
||||||
@@ -959,16 +959,16 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
|
|||||||
.unwrap_or_else(|| "plaintext".to_string())
|
.unwrap_or_else(|| "plaintext".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::RawUri {
|
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
|
||||||
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||||
lsp::Uri::from_file_path(file.abs_path(cx)).unwrap().into()
|
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||||
} else {
|
} else {
|
||||||
format!("buffer://{}", buffer.entity_id()).parse().unwrap()
|
format!("buffer://{}", buffer.entity_id()).parse().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_copilot_dir() {
|
async fn clear_copilot_dir() {
|
||||||
remove_matching(&paths::COPILOT_DIR, |_| true).await
|
remove_matching(paths::copilot_dir(), |_| true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||||
@@ -979,7 +979,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
let release =
|
let release =
|
||||||
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
|
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
|
||||||
|
|
||||||
let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.tag_name));
|
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
|
||||||
|
|
||||||
fs::create_dir_all(version_dir).await?;
|
fs::create_dir_all(version_dir).await?;
|
||||||
let server_path = version_dir.join(SERVER_PATH);
|
let server_path = version_dir.join(SERVER_PATH);
|
||||||
@@ -1003,7 +1003,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
let archive = Archive::new(decompressed_bytes);
|
let archive = Archive::new(decompressed_bytes);
|
||||||
archive.unpack(dist_dir).await?;
|
archive.unpack(dist_dir).await?;
|
||||||
|
|
||||||
remove_matching(&paths::COPILOT_DIR, |entry| entry != version_dir).await;
|
remove_matching(paths::copilot_dir(), |entry| entry != version_dir).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(server_path)
|
Ok(server_path)
|
||||||
@@ -1016,7 +1016,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
// Fetch a cached binary, if it exists
|
// Fetch a cached binary, if it exists
|
||||||
maybe!(async {
|
maybe!(async {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
|
let mut entries = fs::read_dir(paths::copilot_dir()).await?;
|
||||||
while let Some(entry) = entries.next().await {
|
while let Some(entry) = entries.next().await {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
if entry.file_type().await?.is_dir() {
|
if entry.file_type().await?.is_dir() {
|
||||||
@@ -1042,8 +1042,6 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
|
||||||
@@ -1052,8 +1050,9 @@ mod tests {
|
|||||||
let (copilot, mut lsp) = Copilot::fake(cx);
|
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||||
|
|
||||||
let buffer_1 = cx.new_model(|cx| Buffer::local("Hello", cx));
|
let buffer_1 = cx.new_model(|cx| Buffer::local("Hello", cx));
|
||||||
let buffer_1_uri =
|
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||||
lsp::RawUri::from_str(&format!("buffer://{}", buffer_1.entity_id().as_u64())).unwrap();
|
.parse()
|
||||||
|
.unwrap();
|
||||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
@@ -1069,8 +1068,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
|
let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
|
||||||
let buffer_2_uri =
|
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||||
lsp::RawUri::from_str(&format!("buffer://{}", buffer_2.entity_id().as_u64())).unwrap();
|
.parse()
|
||||||
|
.unwrap();
|
||||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
@@ -1119,9 +1119,7 @@ mod tests {
|
|||||||
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let buffer_1_uri: lsp::RawUri = lsp::Uri::from_file_path("/root/child/buffer-1")
|
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
|
||||||
.unwrap()
|
|
||||||
.into();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
.await,
|
.await,
|
||||||
|
|||||||
@@ -1121,10 +1121,7 @@ mod tests {
|
|||||||
cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
|
cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
|
||||||
let completions = completions.clone();
|
let completions = completions.clone();
|
||||||
async move {
|
async move {
|
||||||
assert_eq!(
|
assert_eq!(params.text_document_position.text_document.uri, url.clone());
|
||||||
params.text_document_position.text_document.uri,
|
|
||||||
url.clone().into()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
complete_from_position
|
complete_from_position
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub struct GetCompletionsDocument {
|
|||||||
pub tab_size: u32,
|
pub tab_size: u32,
|
||||||
pub indent_size: u32,
|
pub indent_size: u32,
|
||||||
pub insert_spaces: bool,
|
pub insert_spaces: bool,
|
||||||
pub uri: lsp::RawUri,
|
pub uri: lsp::Url,
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
pub position: lsp::Position,
|
pub position: lsp::Position,
|
||||||
pub version: usize,
|
pub version: usize,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ gpui.workspace = true
|
|||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
sqlez.workspace = true
|
sqlez.workspace = true
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ use anyhow::Context;
|
|||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
pub use indoc::indoc;
|
pub use indoc::indoc;
|
||||||
pub use lazy_static;
|
pub use lazy_static;
|
||||||
|
pub use paths::database_dir;
|
||||||
pub use smol;
|
pub use smol;
|
||||||
pub use sqlez;
|
pub use sqlez;
|
||||||
pub use sqlez_macros;
|
pub use sqlez_macros;
|
||||||
pub use util::paths::DB_DIR;
|
|
||||||
|
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
pub use release_channel::RELEASE_CHANNEL;
|
pub use release_channel::RELEASE_CHANNEL;
|
||||||
@@ -145,7 +145,7 @@ macro_rules! define_connection {
|
|||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
$crate::lazy_static::lazy_static! {
|
$crate::lazy_static::lazy_static! {
|
||||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
|
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
||||||
@@ -176,7 +176,7 @@ macro_rules! define_connection {
|
|||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
$crate::lazy_static::lazy_static! {
|
$crate::lazy_static::lazy_static! {
|
||||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
|
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ impl Store {
|
|||||||
async fn handle_dev_server_projects_update(
|
async fn handle_dev_server_projects_update(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::DevServerProjectsUpdate>,
|
envelope: TypedEnvelope<proto::DevServerProjectsUpdate>,
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ impl ProjectDiagnosticsEditor {
|
|||||||
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
||||||
cx.emit(EditorEvent::TitleChanged);
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
|
|
||||||
if this.editor.read(cx).is_focused(cx) || this.focus_handle.is_focused(cx) {
|
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
|
||||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
||||||
} else {
|
} else {
|
||||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
||||||
@@ -150,7 +150,7 @@ impl ProjectDiagnosticsEditor {
|
|||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
|
cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
|
||||||
.detach();
|
.detach();
|
||||||
cx.on_focus_out(&focus_handle, |this, cx| this.focus_out(cx))
|
cx.on_focus_out(&focus_handle, |this, _event, cx| this.focus_out(cx))
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let excerpts = cx.new_model(|cx| {
|
let excerpts = cx.new_model(|cx| {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
percentage, rems, Animation, AnimationExt, EventEmitter, IntoElement, ParentElement, Render,
|
rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
|
||||||
Styled, Subscription, Transformation, View, ViewContext, WeakView,
|
ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use language::Diagnostic;
|
use language::Diagnostic;
|
||||||
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
||||||
@@ -61,42 +59,7 @@ impl Render for DiagnosticIndicator {
|
|||||||
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let has_in_progress_checks = self
|
let status = if let Some(diagnostic) = &self.current_diagnostic {
|
||||||
.workspace
|
|
||||||
.upgrade()
|
|
||||||
.and_then(|workspace| {
|
|
||||||
workspace
|
|
||||||
.read(cx)
|
|
||||||
.project()
|
|
||||||
.read(cx)
|
|
||||||
.language_servers_running_disk_based_diagnostics()
|
|
||||||
.next()
|
|
||||||
})
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
let status = if has_in_progress_checks {
|
|
||||||
Some(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ArrowCircle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
|
||||||
|icon, delta| {
|
|
||||||
icon.transform(Transformation::rotate(percentage(delta)))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new("Checking…")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
} else if let Some(diagnostic) = &self.current_diagnostic {
|
|
||||||
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
||||||
Some(
|
Some(
|
||||||
Button::new("diagnostic_message", message)
|
Button::new("diagnostic_message", message)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//! This module contains all actions supported by [`Editor`].
|
//! This module contains all actions supported by [`Editor`].
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use gpui::action_as;
|
||||||
use util::serde::default_true;
|
use util::serde::default_true;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
@@ -169,6 +170,7 @@ gpui::actions!(
|
|||||||
AddSelectionBelow,
|
AddSelectionBelow,
|
||||||
Backspace,
|
Backspace,
|
||||||
Cancel,
|
Cancel,
|
||||||
|
CancelLanguageServerWork,
|
||||||
ConfirmRename,
|
ConfirmRename,
|
||||||
ContextMenuFirst,
|
ContextMenuFirst,
|
||||||
ContextMenuLast,
|
ContextMenuLast,
|
||||||
@@ -266,6 +268,7 @@ gpui::actions!(
|
|||||||
SelectAllMatches,
|
SelectAllMatches,
|
||||||
SelectDown,
|
SelectDown,
|
||||||
SelectLargerSyntaxNode,
|
SelectLargerSyntaxNode,
|
||||||
|
SelectEnclosingSymbol,
|
||||||
SelectLeft,
|
SelectLeft,
|
||||||
SelectLine,
|
SelectLine,
|
||||||
SelectRight,
|
SelectRight,
|
||||||
@@ -279,6 +282,8 @@ gpui::actions!(
|
|||||||
SelectToPreviousWordStart,
|
SelectToPreviousWordStart,
|
||||||
SelectToStartOfParagraph,
|
SelectToStartOfParagraph,
|
||||||
SelectUp,
|
SelectUp,
|
||||||
|
SelectPageDown,
|
||||||
|
SelectPageUp,
|
||||||
ShowCharacterPalette,
|
ShowCharacterPalette,
|
||||||
ShowInlineCompletion,
|
ShowInlineCompletion,
|
||||||
ShuffleLines,
|
ShuffleLines,
|
||||||
@@ -289,6 +294,7 @@ gpui::actions!(
|
|||||||
TabPrev,
|
TabPrev,
|
||||||
ToggleGitBlame,
|
ToggleGitBlame,
|
||||||
ToggleGitBlameInline,
|
ToggleGitBlameInline,
|
||||||
|
ToggleSelectionMenu,
|
||||||
ToggleHunkDiff,
|
ToggleHunkDiff,
|
||||||
ToggleInlayHints,
|
ToggleInlayHints,
|
||||||
ToggleLineNumbers,
|
ToggleLineNumbers,
|
||||||
@@ -303,3 +309,7 @@ gpui::actions!(
|
|||||||
UniqueLinesCaseSensitive,
|
UniqueLinesCaseSensitive,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
action_as!(outline, ToggleOutline as Toggle);
|
||||||
|
|
||||||
|
action_as!(go_to_line, ToggleGoToLine as Toggle);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
//! [EditorElement]: crate::element::EditorElement
|
//! [EditorElement]: crate::element::EditorElement
|
||||||
|
|
||||||
mod block_map;
|
mod block_map;
|
||||||
mod flap_map;
|
mod crease_map;
|
||||||
mod fold_map;
|
mod fold_map;
|
||||||
mod inlay_map;
|
mod inlay_map;
|
||||||
mod tab_map;
|
mod tab_map;
|
||||||
@@ -33,7 +33,7 @@ pub use block_map::{
|
|||||||
};
|
};
|
||||||
use block_map::{BlockRow, BlockSnapshot};
|
use block_map::{BlockRow, BlockSnapshot};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
pub use flap_map::*;
|
pub use crease_map::*;
|
||||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||||
use fold_map::{FoldMap, FoldSnapshot};
|
use fold_map::{FoldMap, FoldSnapshot};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -106,7 +106,7 @@ pub struct DisplayMap {
|
|||||||
/// Regions of inlays that should be highlighted.
|
/// Regions of inlays that should be highlighted.
|
||||||
inlay_highlights: InlayHighlights,
|
inlay_highlights: InlayHighlights,
|
||||||
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
|
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
|
||||||
flap_map: FlapMap,
|
crease_map: CreaseMap,
|
||||||
fold_placeholder: FoldPlaceholder,
|
fold_placeholder: FoldPlaceholder,
|
||||||
pub clip_at_line_ends: bool,
|
pub clip_at_line_ends: bool,
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ impl DisplayMap {
|
|||||||
excerpt_header_height,
|
excerpt_header_height,
|
||||||
excerpt_footer_height,
|
excerpt_footer_height,
|
||||||
);
|
);
|
||||||
let flap_map = FlapMap::default();
|
let crease_map = CreaseMap::default();
|
||||||
|
|
||||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ impl DisplayMap {
|
|||||||
tab_map,
|
tab_map,
|
||||||
wrap_map,
|
wrap_map,
|
||||||
block_map,
|
block_map,
|
||||||
flap_map,
|
crease_map,
|
||||||
fold_placeholder,
|
fold_placeholder,
|
||||||
text_highlights: Default::default(),
|
text_highlights: Default::default(),
|
||||||
inlay_highlights: Default::default(),
|
inlay_highlights: Default::default(),
|
||||||
@@ -169,7 +169,7 @@ impl DisplayMap {
|
|||||||
let (wrap_snapshot, edits) = self
|
let (wrap_snapshot, edits) = self
|
||||||
.wrap_map
|
.wrap_map
|
||||||
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
|
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
|
||||||
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
|
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
|
||||||
|
|
||||||
DisplaySnapshot {
|
DisplaySnapshot {
|
||||||
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
|
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
|
||||||
@@ -178,7 +178,7 @@ impl DisplayMap {
|
|||||||
tab_snapshot,
|
tab_snapshot,
|
||||||
wrap_snapshot,
|
wrap_snapshot,
|
||||||
block_snapshot,
|
block_snapshot,
|
||||||
flap_snapshot: self.flap_map.snapshot(),
|
crease_snapshot: self.crease_map.snapshot(),
|
||||||
text_highlights: self.text_highlights.clone(),
|
text_highlights: self.text_highlights.clone(),
|
||||||
inlay_highlights: self.inlay_highlights.clone(),
|
inlay_highlights: self.inlay_highlights.clone(),
|
||||||
clip_at_line_ends: self.clip_at_line_ends,
|
clip_at_line_ends: self.clip_at_line_ends,
|
||||||
@@ -247,22 +247,22 @@ impl DisplayMap {
|
|||||||
self.block_map.read(snapshot, edits);
|
self.block_map.read(snapshot, edits);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_flaps(
|
pub fn insert_creases(
|
||||||
&mut self,
|
&mut self,
|
||||||
flaps: impl IntoIterator<Item = Flap>,
|
creases: impl IntoIterator<Item = Crease>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Vec<FlapId> {
|
) -> Vec<CreaseId> {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
self.flap_map.insert(flaps, &snapshot)
|
self.crease_map.insert(creases, &snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_flaps(
|
pub fn remove_creases(
|
||||||
&mut self,
|
&mut self,
|
||||||
flap_ids: impl IntoIterator<Item = FlapId>,
|
crease_ids: impl IntoIterator<Item = CreaseId>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
self.flap_map.remove(flap_ids, &snapshot)
|
self.crease_map.remove(crease_ids, &snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_blocks(
|
pub fn insert_blocks(
|
||||||
@@ -348,6 +348,25 @@ impl DisplayMap {
|
|||||||
block_map.remove(ids);
|
block_map.remove(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn row_for_block(
|
||||||
|
&mut self,
|
||||||
|
block_id: BlockId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<DisplayRow> {
|
||||||
|
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);
|
||||||
|
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||||
|
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||||
|
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||||
|
let (snapshot, edits) = self
|
||||||
|
.wrap_map
|
||||||
|
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||||
|
let block_map = self.block_map.read(snapshot, edits);
|
||||||
|
let block_row = block_map.row_for_block(block_id)?;
|
||||||
|
Some(DisplayRow(block_row.0))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn highlight_text(
|
pub fn highlight_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
@@ -472,7 +491,7 @@ pub struct HighlightedChunk<'a> {
|
|||||||
pub struct DisplaySnapshot {
|
pub struct DisplaySnapshot {
|
||||||
pub buffer_snapshot: MultiBufferSnapshot,
|
pub buffer_snapshot: MultiBufferSnapshot,
|
||||||
pub fold_snapshot: FoldSnapshot,
|
pub fold_snapshot: FoldSnapshot,
|
||||||
pub flap_snapshot: FlapSnapshot,
|
pub crease_snapshot: CreaseSnapshot,
|
||||||
inlay_snapshot: InlaySnapshot,
|
inlay_snapshot: InlaySnapshot,
|
||||||
tab_snapshot: TabSnapshot,
|
tab_snapshot: TabSnapshot,
|
||||||
wrap_snapshot: WrapSnapshot,
|
wrap_snapshot: WrapSnapshot,
|
||||||
@@ -955,13 +974,13 @@ impl DisplaySnapshot {
|
|||||||
buffer_row: MultiBufferRow,
|
buffer_row: MultiBufferRow,
|
||||||
) -> Option<(Range<Point>, FoldPlaceholder)> {
|
) -> Option<(Range<Point>, FoldPlaceholder)> {
|
||||||
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
||||||
if let Some(flap) = self
|
if let Some(crease) = self
|
||||||
.flap_snapshot
|
.crease_snapshot
|
||||||
.query_row(buffer_row, &self.buffer_snapshot)
|
.query_row(buffer_row, &self.buffer_snapshot)
|
||||||
{
|
{
|
||||||
Some((
|
Some((
|
||||||
flap.range.to_point(&self.buffer_snapshot),
|
crease.range.to_point(&self.buffer_snapshot),
|
||||||
flap.placeholder.clone(),
|
crease.placeholder.clone(),
|
||||||
))
|
))
|
||||||
} else if self.starts_indent(MultiBufferRow(start.row))
|
} else if self.starts_indent(MultiBufferRow(start.row))
|
||||||
&& !self.is_line_folded(MultiBufferRow(start.row))
|
&& !self.is_line_folded(MultiBufferRow(start.row))
|
||||||
@@ -983,8 +1002,23 @@ impl DisplaySnapshot {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let end = end.unwrap_or(max_point);
|
|
||||||
Some((start..end, self.fold_placeholder.clone()))
|
let mut row_before_line_breaks = end.unwrap_or(max_point);
|
||||||
|
while row_before_line_breaks.row > start.row
|
||||||
|
&& self
|
||||||
|
.buffer_snapshot
|
||||||
|
.is_line_blank(MultiBufferRow(row_before_line_breaks.row))
|
||||||
|
{
|
||||||
|
row_before_line_breaks.row -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
row_before_line_breaks = Point::new(
|
||||||
|
row_before_line_breaks.row,
|
||||||
|
self.buffer_snapshot
|
||||||
|
.line_len(MultiBufferRow(row_before_line_breaks.row)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some((start..row_before_line_breaks, self.fold_placeholder.clone()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -1021,6 +1055,22 @@ impl Debug for DisplayPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Add for DisplayPoint {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: Self) -> Self::Output {
|
||||||
|
DisplayPoint(BlockPoint(self.0 .0 + other.0 .0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for DisplayPoint {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, other: Self) -> Self::Output {
|
||||||
|
DisplayPoint(BlockPoint(self.0 .0 - other.0 .0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
|
#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct DisplayRow(pub u32);
|
pub struct DisplayRow(pub u32);
|
||||||
@@ -1930,7 +1980,7 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_flaps(cx: &mut gpui::AppContext) {
|
fn test_creases(cx: &mut gpui::AppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
|
let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
|
||||||
@@ -1953,8 +2003,8 @@ pub mod tests {
|
|||||||
let range =
|
let range =
|
||||||
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
|
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
|
||||||
|
|
||||||
map.flap_map.insert(
|
map.crease_map.insert(
|
||||||
[Flap::new(
|
[Crease::new(
|
||||||
range,
|
range,
|
||||||
FoldPlaceholder::test(),
|
FoldPlaceholder::test(),
|
||||||
|_row, _status, _toggle, _cx| div(),
|
|_row, _status, _toggle, _cx| div(),
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ pub struct BlockMap {
|
|||||||
excerpt_footer_height: u8,
|
excerpt_footer_height: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BlockMapReader<'a> {
|
||||||
|
blocks: &'a Vec<Arc<Block>>,
|
||||||
|
pub snapshot: BlockSnapshot,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BlockMapWriter<'a>(&'a mut BlockMap);
|
pub struct BlockMapWriter<'a>(&'a mut BlockMap);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -246,12 +251,15 @@ impl BlockMap {
|
|||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockSnapshot {
|
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
|
||||||
self.sync(&wrap_snapshot, edits);
|
self.sync(&wrap_snapshot, edits);
|
||||||
*self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
|
*self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
|
||||||
BlockSnapshot {
|
BlockMapReader {
|
||||||
wrap_snapshot,
|
blocks: &self.blocks,
|
||||||
transforms: self.transforms.borrow().clone(),
|
snapshot: BlockSnapshot {
|
||||||
|
wrap_snapshot,
|
||||||
|
transforms: self.transforms.borrow().clone(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,6 +614,62 @@ impl std::ops::DerefMut for BlockPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for BlockMapReader<'a> {
|
||||||
|
type Target = BlockSnapshot;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.snapshot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DerefMut for BlockMapReader<'a> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.snapshot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlockMapReader<'a> {
|
||||||
|
pub fn row_for_block(&self, block_id: BlockId) -> Option<BlockRow> {
|
||||||
|
let block = self.blocks.iter().find(|block| block.id == block_id)?;
|
||||||
|
let buffer_row = block
|
||||||
|
.position
|
||||||
|
.to_point(self.wrap_snapshot.buffer_snapshot())
|
||||||
|
.row;
|
||||||
|
let wrap_row = self
|
||||||
|
.wrap_snapshot
|
||||||
|
.make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
|
||||||
|
.row();
|
||||||
|
let start_wrap_row = WrapRow(
|
||||||
|
self.wrap_snapshot
|
||||||
|
.prev_row_boundary(WrapPoint::new(wrap_row, 0)),
|
||||||
|
);
|
||||||
|
let end_wrap_row = WrapRow(
|
||||||
|
self.wrap_snapshot
|
||||||
|
.next_row_boundary(WrapPoint::new(wrap_row, 0))
|
||||||
|
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
||||||
|
cursor.seek(&start_wrap_row, Bias::Left, &());
|
||||||
|
while let Some(transform) = cursor.item() {
|
||||||
|
if cursor.start().0 > end_wrap_row {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(BlockType::Custom(id)) =
|
||||||
|
transform.block.as_ref().map(|block| block.block_type())
|
||||||
|
{
|
||||||
|
if id == block_id {
|
||||||
|
return Some(cursor.start().1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.next(&());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> BlockMapWriter<'a> {
|
impl<'a> BlockMapWriter<'a> {
|
||||||
pub fn insert(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -1784,6 +1848,15 @@ mod tests {
|
|||||||
expected_block_positions
|
expected_block_positions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (block_row, block) in expected_block_positions {
|
||||||
|
if let BlockType::Custom(block_id) = block.block_type() {
|
||||||
|
assert_eq!(
|
||||||
|
blocks_snapshot.row_for_block(block_id),
|
||||||
|
Some(BlockRow(block_row))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut expected_longest_rows = Vec::new();
|
let mut expected_longest_rows = Vec::new();
|
||||||
let mut longest_line_len = -1_isize;
|
let mut longest_line_len = -1_isize;
|
||||||
for (row, line) in expected_lines.iter().enumerate() {
|
for (row, line) in expected_lines.iter().enumerate() {
|
||||||
|
|||||||
@@ -9,36 +9,36 @@ use ui::WindowContext;
|
|||||||
use crate::FoldPlaceholder;
|
use crate::FoldPlaceholder;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
pub struct FlapId(usize);
|
pub struct CreaseId(usize);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FlapMap {
|
pub struct CreaseMap {
|
||||||
snapshot: FlapSnapshot,
|
snapshot: CreaseSnapshot,
|
||||||
next_id: FlapId,
|
next_id: CreaseId,
|
||||||
id_to_range: HashMap<FlapId, Range<Anchor>>,
|
id_to_range: HashMap<CreaseId, Range<Anchor>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct FlapSnapshot {
|
pub struct CreaseSnapshot {
|
||||||
flaps: SumTree<FlapItem>,
|
creases: SumTree<CreaseItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlapSnapshot {
|
impl CreaseSnapshot {
|
||||||
/// Returns the first Flap starting on the specified buffer row.
|
/// Returns the first Crease starting on the specified buffer row.
|
||||||
pub fn query_row<'a>(
|
pub fn query_row<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
row: MultiBufferRow,
|
row: MultiBufferRow,
|
||||||
snapshot: &'a MultiBufferSnapshot,
|
snapshot: &'a MultiBufferSnapshot,
|
||||||
) -> Option<&'a Flap> {
|
) -> Option<&'a Crease> {
|
||||||
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
||||||
let mut cursor = self.flaps.cursor::<ItemSummary>();
|
let mut cursor = self.creases.cursor::<ItemSummary>();
|
||||||
cursor.seek(&start, Bias::Left, snapshot);
|
cursor.seek(&start, Bias::Left, snapshot);
|
||||||
while let Some(item) = cursor.item() {
|
while let Some(item) = cursor.item() {
|
||||||
match Ord::cmp(&item.flap.range.start.to_point(snapshot).row, &row.0) {
|
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
|
||||||
Ordering::Less => cursor.next(snapshot),
|
Ordering::Less => cursor.next(snapshot),
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
if item.flap.range.start.is_valid(snapshot) {
|
if item.crease.range.start.is_valid(snapshot) {
|
||||||
return Some(&item.flap);
|
return Some(&item.crease);
|
||||||
} else {
|
} else {
|
||||||
cursor.next(snapshot);
|
cursor.next(snapshot);
|
||||||
}
|
}
|
||||||
@@ -49,17 +49,17 @@ impl FlapSnapshot {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flap_items_with_offsets(
|
pub fn crease_items_with_offsets(
|
||||||
&self,
|
&self,
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
) -> Vec<(FlapId, Range<Point>)> {
|
) -> Vec<(CreaseId, Range<Point>)> {
|
||||||
let mut cursor = self.flaps.cursor::<ItemSummary>();
|
let mut cursor = self.creases.cursor::<ItemSummary>();
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
cursor.next(snapshot);
|
cursor.next(snapshot);
|
||||||
while let Some(item) = cursor.item() {
|
while let Some(item) = cursor.item() {
|
||||||
let start_point = item.flap.range.start.to_point(snapshot);
|
let start_point = item.crease.range.start.to_point(snapshot);
|
||||||
let end_point = item.flap.range.end.to_point(snapshot);
|
let end_point = item.crease.range.end.to_point(snapshot);
|
||||||
results.push((item.id, start_point..end_point));
|
results.push((item.id, start_point..end_point));
|
||||||
cursor.next(snapshot);
|
cursor.next(snapshot);
|
||||||
}
|
}
|
||||||
@@ -82,14 +82,14 @@ type RenderTrailerFn =
|
|||||||
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
|
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Flap {
|
pub struct Crease {
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
pub placeholder: FoldPlaceholder,
|
pub placeholder: FoldPlaceholder,
|
||||||
pub render_toggle: RenderToggleFn,
|
pub render_toggle: RenderToggleFn,
|
||||||
pub render_trailer: RenderTrailerFn,
|
pub render_trailer: RenderTrailerFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flap {
|
impl Crease {
|
||||||
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
placeholder: FoldPlaceholder,
|
placeholder: FoldPlaceholder,
|
||||||
@@ -115,7 +115,7 @@ impl Flap {
|
|||||||
+ 'static,
|
+ 'static,
|
||||||
TrailerElement: IntoElement,
|
TrailerElement: IntoElement,
|
||||||
{
|
{
|
||||||
Flap {
|
Crease {
|
||||||
range,
|
range,
|
||||||
placeholder,
|
placeholder,
|
||||||
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
||||||
@@ -128,50 +128,52 @@ impl Flap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Flap {
|
impl std::fmt::Debug for Crease {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Flap").field("range", &self.range).finish()
|
f.debug_struct("Crease")
|
||||||
|
.field("range", &self.range)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct FlapItem {
|
struct CreaseItem {
|
||||||
id: FlapId,
|
id: CreaseId,
|
||||||
flap: Flap,
|
crease: Crease,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlapMap {
|
impl CreaseMap {
|
||||||
pub fn snapshot(&self) -> FlapSnapshot {
|
pub fn snapshot(&self) -> CreaseSnapshot {
|
||||||
self.snapshot.clone()
|
self.snapshot.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
flaps: impl IntoIterator<Item = Flap>,
|
creases: impl IntoIterator<Item = Crease>,
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
) -> Vec<FlapId> {
|
) -> Vec<CreaseId> {
|
||||||
let mut new_ids = Vec::new();
|
let mut new_ids = Vec::new();
|
||||||
self.snapshot.flaps = {
|
self.snapshot.creases = {
|
||||||
let mut new_flaps = SumTree::new();
|
let mut new_creases = SumTree::new();
|
||||||
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
|
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
||||||
for flap in flaps {
|
for crease in creases {
|
||||||
new_flaps.append(cursor.slice(&flap.range, Bias::Left, snapshot), snapshot);
|
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
|
||||||
|
|
||||||
let id = self.next_id;
|
let id = self.next_id;
|
||||||
self.next_id.0 += 1;
|
self.next_id.0 += 1;
|
||||||
self.id_to_range.insert(id, flap.range.clone());
|
self.id_to_range.insert(id, crease.range.clone());
|
||||||
new_flaps.push(FlapItem { flap, id }, snapshot);
|
new_creases.push(CreaseItem { crease, id }, snapshot);
|
||||||
new_ids.push(id);
|
new_ids.push(id);
|
||||||
}
|
}
|
||||||
new_flaps.append(cursor.suffix(snapshot), snapshot);
|
new_creases.append(cursor.suffix(snapshot), snapshot);
|
||||||
new_flaps
|
new_creases
|
||||||
};
|
};
|
||||||
new_ids
|
new_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(
|
pub fn remove(
|
||||||
&mut self,
|
&mut self,
|
||||||
ids: impl IntoIterator<Item = FlapId>,
|
ids: impl IntoIterator<Item = CreaseId>,
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
) {
|
) {
|
||||||
let mut removals = Vec::new();
|
let mut removals = Vec::new();
|
||||||
@@ -184,24 +186,24 @@ impl FlapMap {
|
|||||||
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
|
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
|
||||||
});
|
});
|
||||||
|
|
||||||
self.snapshot.flaps = {
|
self.snapshot.creases = {
|
||||||
let mut new_flaps = SumTree::new();
|
let mut new_creases = SumTree::new();
|
||||||
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
|
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
||||||
|
|
||||||
for (id, range) in removals {
|
for (id, range) in removals {
|
||||||
new_flaps.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
||||||
while let Some(item) = cursor.item() {
|
while let Some(item) = cursor.item() {
|
||||||
cursor.next(snapshot);
|
cursor.next(snapshot);
|
||||||
if item.id == id {
|
if item.id == id {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
new_flaps.push(item.clone(), snapshot);
|
new_creases.push(item.clone(), snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new_flaps.append(cursor.suffix(snapshot), snapshot);
|
new_creases.append(cursor.suffix(snapshot), snapshot);
|
||||||
new_flaps
|
new_creases
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,17 +229,17 @@ impl sum_tree::Summary for ItemSummary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sum_tree::Item for FlapItem {
|
impl sum_tree::Item for CreaseItem {
|
||||||
type Summary = ItemSummary;
|
type Summary = ItemSummary;
|
||||||
|
|
||||||
fn summary(&self) -> Self::Summary {
|
fn summary(&self) -> Self::Summary {
|
||||||
ItemSummary {
|
ItemSummary {
|
||||||
range: self.flap.range.clone(),
|
range: self.crease.range.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `FlapItem`s.
|
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
|
||||||
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
|
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
|
||||||
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
|
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||||
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
|
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
|
||||||
@@ -257,48 +259,48 @@ mod test {
|
|||||||
use multi_buffer::MultiBuffer;
|
use multi_buffer::MultiBuffer;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_insert_and_remove_flaps(cx: &mut AppContext) {
|
fn test_insert_and_remove_creases(cx: &mut AppContext) {
|
||||||
let text = "line1\nline2\nline3\nline4\nline5";
|
let text = "line1\nline2\nline3\nline4\nline5";
|
||||||
let buffer = MultiBuffer::build_simple(text, cx);
|
let buffer = MultiBuffer::build_simple(text, cx);
|
||||||
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
let mut flap_map = FlapMap::default();
|
let mut crease_map = CreaseMap::default();
|
||||||
|
|
||||||
// Insert flaps
|
// Insert creases
|
||||||
let flaps = [
|
let creases = [
|
||||||
Flap::new(
|
Crease::new(
|
||||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||||
FoldPlaceholder::test(),
|
FoldPlaceholder::test(),
|
||||||
|_row, _folded, _toggle, _cx| div(),
|
|_row, _folded, _toggle, _cx| div(),
|
||||||
|_row, _folded, _cx| div(),
|
|_row, _folded, _cx| div(),
|
||||||
),
|
),
|
||||||
Flap::new(
|
Crease::new(
|
||||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||||
FoldPlaceholder::test(),
|
FoldPlaceholder::test(),
|
||||||
|_row, _folded, _toggle, _cx| div(),
|
|_row, _folded, _toggle, _cx| div(),
|
||||||
|_row, _folded, _cx| div(),
|
|_row, _folded, _cx| div(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let flap_ids = flap_map.insert(flaps, &snapshot);
|
let crease_ids = crease_map.insert(creases, &snapshot);
|
||||||
assert_eq!(flap_ids.len(), 2);
|
assert_eq!(crease_ids.len(), 2);
|
||||||
|
|
||||||
// Verify flaps are inserted
|
// Verify creases are inserted
|
||||||
let flap_snapshot = flap_map.snapshot();
|
let crease_snapshot = crease_map.snapshot();
|
||||||
assert!(flap_snapshot
|
assert!(crease_snapshot
|
||||||
.query_row(MultiBufferRow(1), &snapshot)
|
.query_row(MultiBufferRow(1), &snapshot)
|
||||||
.is_some());
|
.is_some());
|
||||||
assert!(flap_snapshot
|
assert!(crease_snapshot
|
||||||
.query_row(MultiBufferRow(3), &snapshot)
|
.query_row(MultiBufferRow(3), &snapshot)
|
||||||
.is_some());
|
.is_some());
|
||||||
|
|
||||||
// Remove flaps
|
// Remove creases
|
||||||
flap_map.remove(flap_ids, &snapshot);
|
crease_map.remove(crease_ids, &snapshot);
|
||||||
|
|
||||||
// Verify flaps are removed
|
// Verify creases are removed
|
||||||
let flap_snapshot = flap_map.snapshot();
|
let crease_snapshot = crease_map.snapshot();
|
||||||
assert!(flap_snapshot
|
assert!(crease_snapshot
|
||||||
.query_row(MultiBufferRow(1), &snapshot)
|
.query_row(MultiBufferRow(1), &snapshot)
|
||||||
.is_none());
|
.is_none());
|
||||||
assert!(flap_snapshot
|
assert!(crease_snapshot
|
||||||
.query_row(MultiBufferRow(3), &snapshot)
|
.query_row(MultiBufferRow(3), &snapshot)
|
||||||
.is_none());
|
.is_none());
|
||||||
}
|
}
|
||||||
@@ -462,11 +462,8 @@ impl InlayMap {
|
|||||||
|
|
||||||
if buffer_edits.is_empty() {
|
if buffer_edits.is_empty() {
|
||||||
if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
|
if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
|
||||||
|| snapshot.buffer.parse_count() != buffer_snapshot.parse_count()
|
|| snapshot.buffer.non_text_state_update_count()
|
||||||
|| snapshot.buffer.diagnostics_update_count()
|
!= buffer_snapshot.non_text_state_update_count()
|
||||||
!= buffer_snapshot.diagnostics_update_count()
|
|
||||||
|| snapshot.buffer.git_diff_update_count()
|
|
||||||
!= buffer_snapshot.git_diff_update_count()
|
|
||||||
|| snapshot.buffer.trailing_excerpt_update_count()
|
|| snapshot.buffer.trailing_excerpt_update_count()
|
||||||
!= buffer_snapshot.trailing_excerpt_update_count()
|
!= buffer_snapshot.trailing_excerpt_update_count()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,11 +66,12 @@ use git::diff_hunk_to_display;
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
||||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
|
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
|
||||||
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
|
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, FocusableView,
|
||||||
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, ListSizingBehavior, Model,
|
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||||
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
|
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
|
||||||
Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
|
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
|
||||||
View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
|
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
|
||||||
|
WeakView, WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||||
use hover_popover::{hide_hover, HoverState};
|
use hover_popover::{hide_hover, HoverState};
|
||||||
@@ -448,6 +449,7 @@ struct BufferOffset(usize);
|
|||||||
/// See the [module level documentation](self) for more information.
|
/// See the [module level documentation](self) for more information.
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
last_focused_descendant: Option<WeakFocusHandle>,
|
||||||
/// The text buffer being edited
|
/// The text buffer being edited
|
||||||
buffer: Model<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
/// Map of how text in the buffer should be displayed.
|
/// Map of how text in the buffer should be displayed.
|
||||||
@@ -455,6 +457,9 @@ pub struct Editor {
|
|||||||
pub display_map: Model<DisplayMap>,
|
pub display_map: Model<DisplayMap>,
|
||||||
pub selections: SelectionsCollection,
|
pub selections: SelectionsCollection,
|
||||||
pub scroll_manager: ScrollManager,
|
pub scroll_manager: ScrollManager,
|
||||||
|
/// When inline assist editors are linked, they all render cursors because
|
||||||
|
/// typing enters text into each of them, even the ones that aren't focused.
|
||||||
|
pub(crate) show_cursor_when_unfocused: bool,
|
||||||
columnar_selection_tail: Option<Anchor>,
|
columnar_selection_tail: Option<Anchor>,
|
||||||
add_selections_state: Option<AddSelectionsState>,
|
add_selections_state: Option<AddSelectionsState>,
|
||||||
select_next_state: Option<SelectNextState>,
|
select_next_state: Option<SelectNextState>,
|
||||||
@@ -479,6 +484,7 @@ pub struct Editor {
|
|||||||
show_line_numbers: Option<bool>,
|
show_line_numbers: Option<bool>,
|
||||||
show_git_diff_gutter: Option<bool>,
|
show_git_diff_gutter: Option<bool>,
|
||||||
show_code_actions: Option<bool>,
|
show_code_actions: Option<bool>,
|
||||||
|
show_runnables: Option<bool>,
|
||||||
show_wrap_guides: Option<bool>,
|
show_wrap_guides: Option<bool>,
|
||||||
show_indent_guides: Option<bool>,
|
show_indent_guides: Option<bool>,
|
||||||
placeholder_text: Option<Arc<str>>,
|
placeholder_text: Option<Arc<str>>,
|
||||||
@@ -530,11 +536,13 @@ pub struct Editor {
|
|||||||
next_editor_action_id: EditorActionId,
|
next_editor_action_id: EditorActionId,
|
||||||
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
|
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
|
||||||
use_autoclose: bool,
|
use_autoclose: bool,
|
||||||
|
use_auto_surround: bool,
|
||||||
auto_replace_emoji_shortcode: bool,
|
auto_replace_emoji_shortcode: bool,
|
||||||
show_git_blame_gutter: bool,
|
show_git_blame_gutter: bool,
|
||||||
show_git_blame_inline: bool,
|
show_git_blame_inline: bool,
|
||||||
show_git_blame_inline_delay_task: Option<Task<()>>,
|
show_git_blame_inline_delay_task: Option<Task<()>>,
|
||||||
git_blame_inline_enabled: bool,
|
git_blame_inline_enabled: bool,
|
||||||
|
show_selection_menu: Option<bool>,
|
||||||
blame: Option<Model<GitBlame>>,
|
blame: Option<Model<GitBlame>>,
|
||||||
blame_subscription: Option<Subscription>,
|
blame_subscription: Option<Subscription>,
|
||||||
custom_context_menu: Option<
|
custom_context_menu: Option<
|
||||||
@@ -549,6 +557,7 @@ pub struct Editor {
|
|||||||
tasks_update_task: Option<Task<()>>,
|
tasks_update_task: Option<Task<()>>,
|
||||||
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
||||||
file_header_size: u8,
|
file_header_size: u8,
|
||||||
|
breadcrumb_header: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -558,6 +567,7 @@ pub struct EditorSnapshot {
|
|||||||
show_line_numbers: Option<bool>,
|
show_line_numbers: Option<bool>,
|
||||||
show_git_diff_gutter: Option<bool>,
|
show_git_diff_gutter: Option<bool>,
|
||||||
show_code_actions: Option<bool>,
|
show_code_actions: Option<bool>,
|
||||||
|
show_runnables: Option<bool>,
|
||||||
render_git_blame_gutter: bool,
|
render_git_blame_gutter: bool,
|
||||||
pub display_snapshot: DisplaySnapshot,
|
pub display_snapshot: DisplaySnapshot,
|
||||||
pub placeholder_text: Option<Arc<str>>,
|
pub placeholder_text: Option<Arc<str>>,
|
||||||
@@ -1630,7 +1640,7 @@ impl Editor {
|
|||||||
clone
|
clone
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(
|
pub fn new(
|
||||||
mode: EditorMode,
|
mode: EditorMode,
|
||||||
buffer: Model<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
project: Option<Model<Project>>,
|
project: Option<Model<Project>>,
|
||||||
@@ -1735,6 +1745,8 @@ impl Editor {
|
|||||||
);
|
);
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
cx.on_focus(&focus_handle, Self::handle_focus).detach();
|
cx.on_focus(&focus_handle, Self::handle_focus).detach();
|
||||||
|
cx.on_focus_out(&focus_handle, Self::handle_focus_out)
|
||||||
|
.detach();
|
||||||
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
||||||
|
|
||||||
let show_indent_guides = if mode == EditorMode::SingleLine {
|
let show_indent_guides = if mode == EditorMode::SingleLine {
|
||||||
@@ -1745,6 +1757,8 @@ impl Editor {
|
|||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
focus_handle,
|
focus_handle,
|
||||||
|
show_cursor_when_unfocused: false,
|
||||||
|
last_focused_descendant: None,
|
||||||
buffer: buffer.clone(),
|
buffer: buffer.clone(),
|
||||||
display_map: display_map.clone(),
|
display_map: display_map.clone(),
|
||||||
selections,
|
selections,
|
||||||
@@ -1771,6 +1785,7 @@ impl Editor {
|
|||||||
show_line_numbers: None,
|
show_line_numbers: None,
|
||||||
show_git_diff_gutter: None,
|
show_git_diff_gutter: None,
|
||||||
show_code_actions: None,
|
show_code_actions: None,
|
||||||
|
show_runnables: None,
|
||||||
show_wrap_guides: None,
|
show_wrap_guides: None,
|
||||||
show_indent_guides,
|
show_indent_guides,
|
||||||
placeholder_text: None,
|
placeholder_text: None,
|
||||||
@@ -1804,6 +1819,7 @@ impl Editor {
|
|||||||
use_modal_editing: mode == EditorMode::Full,
|
use_modal_editing: mode == EditorMode::Full,
|
||||||
read_only: false,
|
read_only: false,
|
||||||
use_autoclose: true,
|
use_autoclose: true,
|
||||||
|
use_auto_surround: true,
|
||||||
auto_replace_emoji_shortcode: false,
|
auto_replace_emoji_shortcode: false,
|
||||||
leader_peer_id: None,
|
leader_peer_id: None,
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
@@ -1828,6 +1844,7 @@ impl Editor {
|
|||||||
custom_context_menu: None,
|
custom_context_menu: None,
|
||||||
show_git_blame_gutter: false,
|
show_git_blame_gutter: false,
|
||||||
show_git_blame_inline: false,
|
show_git_blame_inline: false,
|
||||||
|
show_selection_menu: None,
|
||||||
show_git_blame_inline_delay_task: None,
|
show_git_blame_inline_delay_task: None,
|
||||||
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
|
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
|
||||||
blame: None,
|
blame: None,
|
||||||
@@ -1856,6 +1873,7 @@ impl Editor {
|
|||||||
tasks_update_task: None,
|
tasks_update_task: None,
|
||||||
linked_edit_ranges: Default::default(),
|
linked_edit_ranges: Default::default(),
|
||||||
previous_search_ranges: None,
|
previous_search_ranges: None,
|
||||||
|
breadcrumb_header: None,
|
||||||
};
|
};
|
||||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||||
this._subscriptions.extend(project_subscriptions);
|
this._subscriptions.extend(project_subscriptions);
|
||||||
@@ -2017,6 +2035,7 @@ impl Editor {
|
|||||||
show_line_numbers: self.show_line_numbers,
|
show_line_numbers: self.show_line_numbers,
|
||||||
show_git_diff_gutter: self.show_git_diff_gutter,
|
show_git_diff_gutter: self.show_git_diff_gutter,
|
||||||
show_code_actions: self.show_code_actions,
|
show_code_actions: self.show_code_actions,
|
||||||
|
show_runnables: self.show_runnables,
|
||||||
render_git_blame_gutter: self.render_git_blame_gutter(cx),
|
render_git_blame_gutter: self.render_git_blame_gutter(cx),
|
||||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||||
scroll_anchor: self.scroll_manager.anchor(),
|
scroll_anchor: self.scroll_manager.anchor(),
|
||||||
@@ -2179,6 +2198,10 @@ impl Editor {
|
|||||||
self.use_autoclose = autoclose;
|
self.use_autoclose = autoclose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
|
||||||
|
self.use_auto_surround = auto_surround;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
|
pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
|
||||||
self.auto_replace_emoji_shortcode = auto_replace;
|
self.auto_replace_emoji_shortcode = auto_replace;
|
||||||
}
|
}
|
||||||
@@ -2492,6 +2515,7 @@ impl Editor {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if !self.focus_handle.is_focused(cx) {
|
if !self.focus_handle.is_focused(cx) {
|
||||||
|
self.last_focused_descendant = None;
|
||||||
cx.focus(&self.focus_handle);
|
cx.focus(&self.focus_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2540,14 +2564,47 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
|
let point_to_delete: Option<usize> = {
|
||||||
if !add {
|
let selected_points: Vec<Selection<Point>> =
|
||||||
s.clear_disjoint();
|
self.selections.disjoint_in_range(start..end, cx);
|
||||||
} else if click_count > 1 {
|
|
||||||
s.delete(newest_selection.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.set_pending_anchor_range(start..end, mode);
|
if !add || click_count > 1 {
|
||||||
|
None
|
||||||
|
} else if selected_points.len() > 0 {
|
||||||
|
Some(selected_points[0].id)
|
||||||
|
} else {
|
||||||
|
let clicked_point_already_selected =
|
||||||
|
self.selections.disjoint.iter().find(|selection| {
|
||||||
|
selection.start.to_point(buffer) == start.to_point(buffer)
|
||||||
|
|| selection.end.to_point(buffer) == end.to_point(buffer)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(selection) = clicked_point_already_selected {
|
||||||
|
Some(selection.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let selections_count = self.selections.count();
|
||||||
|
|
||||||
|
self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
|
||||||
|
if let Some(point_to_delete) = point_to_delete {
|
||||||
|
s.delete(point_to_delete);
|
||||||
|
|
||||||
|
if selections_count == 1 {
|
||||||
|
s.set_pending_anchor_range(start..end, mode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !add {
|
||||||
|
s.clear_disjoint();
|
||||||
|
} else if click_count > 1 {
|
||||||
|
s.delete(newest_selection.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.set_pending_anchor_range(start..end, mode);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2559,6 +2616,7 @@ impl Editor {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if !self.focus_handle.is_focused(cx) {
|
if !self.focus_handle.is_focused(cx) {
|
||||||
|
self.last_focused_descendant = None;
|
||||||
cx.focus(&self.focus_handle);
|
cx.focus(&self.focus_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2876,7 +2934,7 @@ impl Editor {
|
|||||||
// `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
|
// `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
|
||||||
// and they are removing the character that triggered IME popup.
|
// and they are removing the character that triggered IME popup.
|
||||||
for (pair, enabled) in scope.brackets() {
|
for (pair, enabled) in scope.brackets() {
|
||||||
if !pair.close {
|
if !pair.close && !pair.surround {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2894,9 +2952,10 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bracket_pair) = bracket_pair {
|
if let Some(bracket_pair) = bracket_pair {
|
||||||
let autoclose = self.use_autoclose
|
let snapshot_settings = snapshot.settings_at(selection.start, cx);
|
||||||
&& snapshot.settings_at(selection.start, cx).use_autoclose;
|
let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
|
||||||
|
let auto_surround =
|
||||||
|
self.use_auto_surround && snapshot_settings.use_auto_surround;
|
||||||
if selection.is_empty() {
|
if selection.is_empty() {
|
||||||
if is_bracket_pair_start {
|
if is_bracket_pair_start {
|
||||||
let prefix_len = bracket_pair.start.len() - text.len();
|
let prefix_len = bracket_pair.start.len() - text.len();
|
||||||
@@ -2918,6 +2977,7 @@ impl Editor {
|
|||||||
&bracket_pair.start[..prefix_len],
|
&bracket_pair.start[..prefix_len],
|
||||||
));
|
));
|
||||||
if autoclose
|
if autoclose
|
||||||
|
&& bracket_pair.close
|
||||||
&& following_text_allows_autoclose
|
&& following_text_allows_autoclose
|
||||||
&& preceding_text_matches_prefix
|
&& preceding_text_matches_prefix
|
||||||
{
|
{
|
||||||
@@ -2969,7 +3029,8 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
// If an opening bracket is 1 character long and is typed while
|
// If an opening bracket is 1 character long and is typed while
|
||||||
// text is selected, then surround that text with the bracket pair.
|
// text is selected, then surround that text with the bracket pair.
|
||||||
else if autoclose
|
else if auto_surround
|
||||||
|
&& bracket_pair.surround
|
||||||
&& is_bracket_pair_start
|
&& is_bracket_pair_start
|
||||||
&& bracket_pair.start.chars().count() == 1
|
&& bracket_pair.start.chars().count() == 1
|
||||||
{
|
{
|
||||||
@@ -6413,82 +6474,96 @@ impl Editor {
|
|||||||
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
pub fn do_paste(
|
||||||
|
&mut self,
|
||||||
|
text: &String,
|
||||||
|
clipboard_selections: Option<Vec<ClipboardSelection>>,
|
||||||
|
handle_entire_lines: bool,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
if self.read_only(cx) {
|
if self.read_only(cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let clipboard_text = Cow::Borrowed(text);
|
||||||
|
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
if let Some(item) = cx.read_from_clipboard() {
|
if let Some(mut clipboard_selections) = clipboard_selections {
|
||||||
let clipboard_text = Cow::Borrowed(item.text());
|
let old_selections = this.selections.all::<usize>(cx);
|
||||||
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
|
let all_selections_were_entire_line =
|
||||||
let old_selections = this.selections.all::<usize>(cx);
|
clipboard_selections.iter().all(|s| s.is_entire_line);
|
||||||
let all_selections_were_entire_line =
|
let first_selection_indent_column =
|
||||||
clipboard_selections.iter().all(|s| s.is_entire_line);
|
clipboard_selections.first().map(|s| s.first_line_indent);
|
||||||
let first_selection_indent_column =
|
if clipboard_selections.len() != old_selections.len() {
|
||||||
clipboard_selections.first().map(|s| s.first_line_indent);
|
clipboard_selections.drain(..);
|
||||||
if clipboard_selections.len() != old_selections.len() {
|
|
||||||
clipboard_selections.drain(..);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buffer.update(cx, |buffer, cx| {
|
|
||||||
let snapshot = buffer.read(cx);
|
|
||||||
let mut start_offset = 0;
|
|
||||||
let mut edits = Vec::new();
|
|
||||||
let mut original_indent_columns = Vec::new();
|
|
||||||
let line_mode = this.selections.line_mode;
|
|
||||||
for (ix, selection) in old_selections.iter().enumerate() {
|
|
||||||
let to_insert;
|
|
||||||
let entire_line;
|
|
||||||
let original_indent_column;
|
|
||||||
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
|
|
||||||
let end_offset = start_offset + clipboard_selection.len;
|
|
||||||
to_insert = &clipboard_text[start_offset..end_offset];
|
|
||||||
entire_line = clipboard_selection.is_entire_line;
|
|
||||||
start_offset = end_offset + 1;
|
|
||||||
original_indent_column =
|
|
||||||
Some(clipboard_selection.first_line_indent);
|
|
||||||
} else {
|
|
||||||
to_insert = clipboard_text.as_str();
|
|
||||||
entire_line = all_selections_were_entire_line;
|
|
||||||
original_indent_column = first_selection_indent_column
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the corresponding selection was empty when this slice of the
|
|
||||||
// clipboard text was written, then the entire line containing the
|
|
||||||
// selection was copied. If this selection is also currently empty,
|
|
||||||
// then paste the line before the current line of the buffer.
|
|
||||||
let range = if selection.is_empty() && !line_mode && entire_line {
|
|
||||||
let column = selection.start.to_point(&snapshot).column as usize;
|
|
||||||
let line_start = selection.start - column;
|
|
||||||
line_start..line_start
|
|
||||||
} else {
|
|
||||||
selection.range()
|
|
||||||
};
|
|
||||||
|
|
||||||
edits.push((range, to_insert));
|
|
||||||
original_indent_columns.extend(original_indent_column);
|
|
||||||
}
|
|
||||||
drop(snapshot);
|
|
||||||
|
|
||||||
buffer.edit(
|
|
||||||
edits,
|
|
||||||
Some(AutoindentMode::Block {
|
|
||||||
original_indent_columns,
|
|
||||||
}),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let selections = this.selections.all::<usize>(cx);
|
|
||||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
|
||||||
} else {
|
|
||||||
this.insert(&clipboard_text, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
|
let snapshot = buffer.read(cx);
|
||||||
|
let mut start_offset = 0;
|
||||||
|
let mut edits = Vec::new();
|
||||||
|
let mut original_indent_columns = Vec::new();
|
||||||
|
for (ix, selection) in old_selections.iter().enumerate() {
|
||||||
|
let to_insert;
|
||||||
|
let entire_line;
|
||||||
|
let original_indent_column;
|
||||||
|
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
|
||||||
|
let end_offset = start_offset + clipboard_selection.len;
|
||||||
|
to_insert = &clipboard_text[start_offset..end_offset];
|
||||||
|
entire_line = clipboard_selection.is_entire_line;
|
||||||
|
start_offset = end_offset + 1;
|
||||||
|
original_indent_column = Some(clipboard_selection.first_line_indent);
|
||||||
|
} else {
|
||||||
|
to_insert = clipboard_text.as_str();
|
||||||
|
entire_line = all_selections_were_entire_line;
|
||||||
|
original_indent_column = first_selection_indent_column
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the corresponding selection was empty when this slice of the
|
||||||
|
// clipboard text was written, then the entire line containing the
|
||||||
|
// selection was copied. If this selection is also currently empty,
|
||||||
|
// then paste the line before the current line of the buffer.
|
||||||
|
let range = if selection.is_empty() && handle_entire_lines && entire_line {
|
||||||
|
let column = selection.start.to_point(&snapshot).column as usize;
|
||||||
|
let line_start = selection.start - column;
|
||||||
|
line_start..line_start
|
||||||
|
} else {
|
||||||
|
selection.range()
|
||||||
|
};
|
||||||
|
|
||||||
|
edits.push((range, to_insert));
|
||||||
|
original_indent_columns.extend(original_indent_column);
|
||||||
|
}
|
||||||
|
drop(snapshot);
|
||||||
|
|
||||||
|
buffer.edit(
|
||||||
|
edits,
|
||||||
|
Some(AutoindentMode::Block {
|
||||||
|
original_indent_columns,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let selections = this.selections.all::<usize>(cx);
|
||||||
|
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||||
|
} else {
|
||||||
|
this.insert(&clipboard_text, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(item) = cx.read_from_clipboard() {
|
||||||
|
self.do_paste(
|
||||||
|
item.text(),
|
||||||
|
item.metadata::<Vec<ClipboardSelection>>(),
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
||||||
if self.read_only(cx) {
|
if self.read_only(cx) {
|
||||||
return;
|
return;
|
||||||
@@ -6697,6 +6772,20 @@ impl Editor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_page_up(&mut self, _: &SelectPageUp, cx: &mut ViewContext<Self>) {
|
||||||
|
let Some(row_count) = self.visible_row_count() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_layout_details = &self.text_layout_details(cx);
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_heads_with(|map, head, goal| {
|
||||||
|
movement::up_by_rows(map, head, row_count, goal, false, &text_layout_details)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
|
pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
|
||||||
if self.take_rename(true, cx).is_some() {
|
if self.take_rename(true, cx).is_some() {
|
||||||
return;
|
return;
|
||||||
@@ -6707,9 +6796,7 @@ impl Editor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row_count = if let Some(row_count) = self.visible_line_count() {
|
let Some(row_count) = self.visible_row_count() else {
|
||||||
row_count as u32 - 1
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6784,6 +6871,20 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_page_down(&mut self, _: &SelectPageDown, cx: &mut ViewContext<Self>) {
|
||||||
|
let Some(row_count) = self.visible_row_count() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_layout_details = &self.text_layout_details(cx);
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_heads_with(|map, head, goal| {
|
||||||
|
movement::down_by_rows(map, head, row_count, goal, false, &text_layout_details)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
|
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
|
||||||
if self.take_rename(true, cx).is_some() {
|
if self.take_rename(true, cx).is_some() {
|
||||||
return;
|
return;
|
||||||
@@ -6804,9 +6905,7 @@ impl Editor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row_count = if let Some(row_count) = self.visible_line_count() {
|
let Some(row_count) = self.visible_row_count() else {
|
||||||
row_count as u32 - 1
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8127,6 +8226,58 @@ impl Editor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_enclosing_symbol(
|
||||||
|
&mut self,
|
||||||
|
_: &SelectEnclosingSymbol,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
|
let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
|
||||||
|
|
||||||
|
fn update_selection(
|
||||||
|
selection: &Selection<usize>,
|
||||||
|
buffer_snap: &MultiBufferSnapshot,
|
||||||
|
) -> Option<Selection<usize>> {
|
||||||
|
let cursor = selection.head();
|
||||||
|
let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
|
||||||
|
for symbol in symbols.iter().rev() {
|
||||||
|
let start = symbol.range.start.to_offset(&buffer_snap);
|
||||||
|
let end = symbol.range.end.to_offset(&buffer_snap);
|
||||||
|
let new_range = start..end;
|
||||||
|
if start < selection.start || end > selection.end {
|
||||||
|
return Some(Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: new_range.start,
|
||||||
|
end: new_range.end,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
reversed: selection.reversed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut selected_larger_symbol = false;
|
||||||
|
let new_selections = old_selections
|
||||||
|
.iter()
|
||||||
|
.map(|selection| match update_selection(selection, &buffer) {
|
||||||
|
Some(new_selection) => {
|
||||||
|
if new_selection.range() != selection.range() {
|
||||||
|
selected_larger_symbol = true;
|
||||||
|
}
|
||||||
|
new_selection
|
||||||
|
}
|
||||||
|
None => selection.clone(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if selected_larger_symbol {
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.select(new_selections);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select_larger_syntax_node(
|
pub fn select_larger_syntax_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &SelectLargerSyntaxNode,
|
_: &SelectLargerSyntaxNode,
|
||||||
@@ -8189,6 +8340,10 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
|
fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||||
|
if !EditorSettings::get_global(cx).gutter.runnables {
|
||||||
|
self.clear_tasks();
|
||||||
|
return Task::ready(());
|
||||||
|
}
|
||||||
let project = self.project.clone();
|
let project = self.project.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
|
let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
|
||||||
@@ -8950,7 +9105,7 @@ impl Editor {
|
|||||||
});
|
});
|
||||||
language_server_name.map(|language_server_name| {
|
language_server_name.map(|language_server_name| {
|
||||||
project.open_local_buffer_via_lsp(
|
project.open_local_buffer_via_lsp(
|
||||||
lsp::Uri::from(lsp_location.uri.clone()),
|
lsp_location.uri.clone(),
|
||||||
server_id,
|
server_id,
|
||||||
language_server_name,
|
language_server_name,
|
||||||
cx,
|
cx,
|
||||||
@@ -9111,6 +9266,12 @@ impl Editor {
|
|||||||
Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), true, cx)
|
Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), true, cx)
|
||||||
});
|
});
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
if let Some(first_range) = ranges_to_highlight.first() {
|
||||||
|
editor.change_selections(None, cx, |selections| {
|
||||||
|
selections.clear_disjoint();
|
||||||
|
selections.select_anchor_ranges(std::iter::once(first_range.clone()));
|
||||||
|
});
|
||||||
|
}
|
||||||
editor.highlight_background::<Self>(
|
editor.highlight_background::<Self>(
|
||||||
&ranges_to_highlight,
|
&ranges_to_highlight,
|
||||||
|theme| theme.editor_highlighted_line_background,
|
|theme| theme.editor_highlighted_line_background,
|
||||||
@@ -9473,6 +9634,20 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cancel_language_server_work(
|
||||||
|
&mut self,
|
||||||
|
_: &CancelLanguageServerWork,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(project) = self.project.clone() {
|
||||||
|
self.buffer.update(cx, |multi_buffer, cx| {
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||||
cx.show_character_palette();
|
cx.show_character_palette();
|
||||||
}
|
}
|
||||||
@@ -9888,22 +10063,31 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_flaps(
|
pub fn row_for_block(
|
||||||
&mut self,
|
&self,
|
||||||
flaps: impl IntoIterator<Item = Flap>,
|
block_id: BlockId,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Vec<FlapId> {
|
) -> Option<DisplayRow> {
|
||||||
self.display_map
|
self.display_map
|
||||||
.update(cx, |map, cx| map.insert_flaps(flaps, cx))
|
.update(cx, |map, cx| map.row_for_block(block_id, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_flaps(
|
pub fn insert_creases(
|
||||||
&mut self,
|
&mut self,
|
||||||
ids: impl IntoIterator<Item = FlapId>,
|
creases: impl IntoIterator<Item = Crease>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Vec<CreaseId> {
|
||||||
|
self.display_map
|
||||||
|
.update(cx, |map, cx| map.insert_creases(creases, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_creases(
|
||||||
|
&mut self,
|
||||||
|
ids: impl IntoIterator<Item = CreaseId>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.display_map
|
self.display_map
|
||||||
.update(cx, |map, cx| map.remove_flaps(ids, cx));
|
.update(cx, |map, cx| map.remove_creases(ids, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
|
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
|
||||||
@@ -10086,6 +10270,11 @@ impl Editor {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut ViewContext<Self>) {
|
||||||
|
self.show_runnables = Some(show_runnables);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
|
pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
|
||||||
self.show_wrap_guides = Some(show_wrap_guides);
|
self.show_wrap_guides = Some(show_wrap_guides);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -10147,6 +10336,20 @@ impl Editor {
|
|||||||
self.git_blame_inline_enabled
|
self.git_blame_inline_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
|
||||||
|
self.show_selection_menu = self
|
||||||
|
.show_selection_menu
|
||||||
|
.map(|show_selections_menu| !show_selections_menu)
|
||||||
|
.or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool {
|
||||||
|
self.show_selection_menu
|
||||||
|
.unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
|
||||||
|
}
|
||||||
|
|
||||||
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
|
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(project) = self.project.as_ref() {
|
if let Some(project) = self.project.as_ref() {
|
||||||
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
|
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
|
||||||
@@ -10454,6 +10657,10 @@ impl Editor {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_breadcrumb_header(&mut self, new_header: String) {
|
||||||
|
self.breadcrumb_header = Some(new_header);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
|
pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.clear_background_highlights::<SearchWithinRange>(cx);
|
self.clear_background_highlights::<SearchWithinRange>(cx);
|
||||||
}
|
}
|
||||||
@@ -10808,6 +11015,11 @@ impl Editor {
|
|||||||
&& self.focus_handle.is_focused(cx)
|
&& self.focus_handle.is_focused(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut ViewContext<Self>) {
|
||||||
|
self.show_cursor_when_unfocused = is_enabled;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
|
fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -10927,6 +11139,7 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
|
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
self.tasks_update_task = Some(self.refresh_runnables(cx));
|
||||||
self.refresh_inline_completion(true, cx);
|
self.refresh_inline_completion(true, cx);
|
||||||
self.refresh_inlay_hints(
|
self.refresh_inlay_hints(
|
||||||
InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
|
InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
|
||||||
@@ -11301,9 +11514,13 @@ impl Editor {
|
|||||||
|
|
||||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(EditorEvent::Focused);
|
cx.emit(EditorEvent::Focused);
|
||||||
if let Some(rename) = self.pending_rename.as_ref() {
|
|
||||||
let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
|
if let Some(descendant) = self
|
||||||
cx.focus(&rename_editor_focus_handle);
|
.last_focused_descendant
|
||||||
|
.take()
|
||||||
|
.and_then(|descendant| descendant.upgrade())
|
||||||
|
{
|
||||||
|
cx.focus(&descendant);
|
||||||
} else {
|
} else {
|
||||||
if let Some(blame) = self.blame.as_ref() {
|
if let Some(blame) = self.blame.as_ref() {
|
||||||
blame.update(cx, GitBlame::focus)
|
blame.update(cx, GitBlame::focus)
|
||||||
@@ -11325,6 +11542,12 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
|
||||||
|
if event.blurred != self.focus_handle {
|
||||||
|
self.last_focused_descendant = Some(event.blurred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
|
pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.blink_manager.update(cx, BlinkManager::disable);
|
self.blink_manager.update(cx, BlinkManager::disable);
|
||||||
self.buffer
|
self.buffer
|
||||||
@@ -11617,7 +11840,7 @@ impl EditorSnapshot {
|
|||||||
.map(|(_, collaborator)| (collaborator.replica_id, collaborator))
|
.map(|(_, collaborator)| (collaborator.replica_id, collaborator))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
self.buffer_snapshot
|
self.buffer_snapshot
|
||||||
.remote_selections_in_range(range)
|
.selections_in_range(range, false)
|
||||||
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
|
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
|
||||||
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
|
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
|
||||||
let participant_index = participant_indices.get(&collaborator.user_id).copied();
|
let participant_index = participant_indices.get(&collaborator.user_id).copied();
|
||||||
@@ -11672,7 +11895,7 @@ impl EditorSnapshot {
|
|||||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||||
let show_line_numbers = self
|
let show_line_numbers = self
|
||||||
.show_line_numbers
|
.show_line_numbers
|
||||||
.unwrap_or_else(|| gutter_settings.line_numbers);
|
.unwrap_or(gutter_settings.line_numbers);
|
||||||
let line_gutter_width = if show_line_numbers {
|
let line_gutter_width = if show_line_numbers {
|
||||||
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
|
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
|
||||||
let min_width_for_number_on_gutter = em_width * 4.0;
|
let min_width_for_number_on_gutter = em_width * 4.0;
|
||||||
@@ -11683,14 +11906,16 @@ impl EditorSnapshot {
|
|||||||
|
|
||||||
let show_code_actions = self
|
let show_code_actions = self
|
||||||
.show_code_actions
|
.show_code_actions
|
||||||
.unwrap_or_else(|| gutter_settings.code_actions);
|
.unwrap_or(gutter_settings.code_actions);
|
||||||
|
|
||||||
|
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
|
||||||
|
|
||||||
let git_blame_entries_width = self
|
let git_blame_entries_width = self
|
||||||
.render_git_blame_gutter
|
.render_git_blame_gutter
|
||||||
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
|
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
|
||||||
|
|
||||||
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
|
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
|
||||||
left_padding += if show_code_actions {
|
left_padding += if show_code_actions || show_runnables {
|
||||||
em_width * 3.0
|
em_width * 3.0
|
||||||
} else if show_git_gutter && show_line_numbers {
|
} else if show_git_gutter && show_line_numbers {
|
||||||
em_width * 2.0
|
em_width * 2.0
|
||||||
@@ -11728,8 +11953,8 @@ impl EditorSnapshot {
|
|||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let folded = self.is_line_folded(buffer_row);
|
let folded = self.is_line_folded(buffer_row);
|
||||||
|
|
||||||
if let Some(flap) = self
|
if let Some(crease) = self
|
||||||
.flap_snapshot
|
.crease_snapshot
|
||||||
.query_row(buffer_row, &self.buffer_snapshot)
|
.query_row(buffer_row, &self.buffer_snapshot)
|
||||||
{
|
{
|
||||||
let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
|
let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
|
||||||
@@ -11744,7 +11969,7 @@ impl EditorSnapshot {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Some((flap.render_toggle)(
|
Some((crease.render_toggle)(
|
||||||
buffer_row,
|
buffer_row,
|
||||||
folded,
|
folded,
|
||||||
toggle_callback,
|
toggle_callback,
|
||||||
@@ -11770,16 +11995,16 @@ impl EditorSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_flap_trailer(
|
pub fn render_crease_trailer(
|
||||||
&self,
|
&self,
|
||||||
buffer_row: MultiBufferRow,
|
buffer_row: MultiBufferRow,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let folded = self.is_line_folded(buffer_row);
|
let folded = self.is_line_folded(buffer_row);
|
||||||
let flap = self
|
let crease = self
|
||||||
.flap_snapshot
|
.crease_snapshot
|
||||||
.query_row(buffer_row, &self.buffer_snapshot)?;
|
.query_row(buffer_row, &self.buffer_snapshot)?;
|
||||||
Some((flap.render_trailer)(buffer_row, folded, cx))
|
Some((crease.render_trailer)(buffer_row, folded, cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12097,9 +12322,12 @@ impl ViewInputHandler for Editor {
|
|||||||
|
|
||||||
// Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
|
// Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
|
||||||
let use_autoclose = this.use_autoclose;
|
let use_autoclose = this.use_autoclose;
|
||||||
|
let use_auto_surround = this.use_auto_surround;
|
||||||
this.set_use_autoclose(false);
|
this.set_use_autoclose(false);
|
||||||
|
this.set_use_auto_surround(false);
|
||||||
this.handle_input(text, cx);
|
this.handle_input(text, cx);
|
||||||
this.set_use_autoclose(use_autoclose);
|
this.set_use_autoclose(use_autoclose);
|
||||||
|
this.set_use_auto_surround(use_auto_surround);
|
||||||
|
|
||||||
if let Some(new_selected_range) = new_selected_range_utf16 {
|
if let Some(new_selected_range) = new_selected_range_utf16 {
|
||||||
let snapshot = this.buffer.read(cx).read(cx);
|
let snapshot = this.buffer.read(cx).read(cx);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub struct EditorSettings {
|
|||||||
pub toolbar: Toolbar,
|
pub toolbar: Toolbar,
|
||||||
pub scrollbar: Scrollbar,
|
pub scrollbar: Scrollbar,
|
||||||
pub gutter: Gutter,
|
pub gutter: Gutter,
|
||||||
|
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
||||||
pub vertical_scroll_margin: f32,
|
pub vertical_scroll_margin: f32,
|
||||||
pub scroll_sensitivity: f32,
|
pub scroll_sensitivity: f32,
|
||||||
pub relative_line_numbers: bool,
|
pub relative_line_numbers: bool,
|
||||||
@@ -67,6 +68,7 @@ pub enum DoubleClickInMultibuffer {
|
|||||||
pub struct Toolbar {
|
pub struct Toolbar {
|
||||||
pub breadcrumbs: bool,
|
pub breadcrumbs: bool,
|
||||||
pub quick_actions: bool,
|
pub quick_actions: bool,
|
||||||
|
pub selections_menu: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
@@ -83,6 +85,7 @@ pub struct Scrollbar {
|
|||||||
pub struct Gutter {
|
pub struct Gutter {
|
||||||
pub line_numbers: bool,
|
pub line_numbers: bool,
|
||||||
pub code_actions: bool,
|
pub code_actions: bool,
|
||||||
|
pub runnables: bool,
|
||||||
pub folds: bool,
|
pub folds: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +117,22 @@ pub enum MultiCursorModifier {
|
|||||||
CmdOrCtrl,
|
CmdOrCtrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the editor will scroll beyond the last line.
|
||||||
|
///
|
||||||
|
/// Default: one_page
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ScrollBeyondLastLine {
|
||||||
|
/// The editor will not scroll beyond the last line.
|
||||||
|
Off,
|
||||||
|
|
||||||
|
/// The editor will scroll beyond the last line by one page.
|
||||||
|
OnePage,
|
||||||
|
|
||||||
|
/// The editor will scroll beyond the last line by the same number of lines as vertical_scroll_margin.
|
||||||
|
VerticalScrollMargin,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct EditorSettingsContent {
|
pub struct EditorSettingsContent {
|
||||||
/// Whether the cursor blinks in the editor.
|
/// Whether the cursor blinks in the editor.
|
||||||
@@ -129,6 +148,7 @@ pub struct EditorSettingsContent {
|
|||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub hover_popover_enabled: Option<bool>,
|
pub hover_popover_enabled: Option<bool>,
|
||||||
|
|
||||||
/// Whether to pop the completions menu while typing in an editor without
|
/// Whether to pop the completions menu while typing in an editor without
|
||||||
/// explicitly requesting it.
|
/// explicitly requesting it.
|
||||||
///
|
///
|
||||||
@@ -155,6 +175,10 @@ pub struct EditorSettingsContent {
|
|||||||
pub scrollbar: Option<ScrollbarContent>,
|
pub scrollbar: Option<ScrollbarContent>,
|
||||||
/// Gutter related settings
|
/// Gutter related settings
|
||||||
pub gutter: Option<GutterContent>,
|
pub gutter: Option<GutterContent>,
|
||||||
|
/// Whether the editor will scroll beyond the last line.
|
||||||
|
///
|
||||||
|
/// Default: one_page
|
||||||
|
pub scroll_beyond_last_line: Option<ScrollBeyondLastLine>,
|
||||||
/// The number of lines to keep above/below the cursor when auto-scrolling.
|
/// The number of lines to keep above/below the cursor when auto-scrolling.
|
||||||
///
|
///
|
||||||
/// Default: 3.
|
/// Default: 3.
|
||||||
@@ -202,10 +226,15 @@ pub struct ToolbarContent {
|
|||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub breadcrumbs: Option<bool>,
|
pub breadcrumbs: Option<bool>,
|
||||||
/// Whether to display quik action buttons in the editor toolbar.
|
/// Whether to display quick action buttons in the editor toolbar.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub quick_actions: Option<bool>,
|
pub quick_actions: Option<bool>,
|
||||||
|
|
||||||
|
/// Whether to show the selections menu in the editor toolbar
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub selections_menu: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrollbar related settings
|
/// Scrollbar related settings
|
||||||
@@ -248,6 +277,10 @@ pub struct GutterContent {
|
|||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub code_actions: Option<bool>,
|
pub code_actions: Option<bool>,
|
||||||
|
/// Whether to show runnable buttons in the gutter.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub runnables: Option<bool>,
|
||||||
/// Whether to show fold buttons in the gutter.
|
/// Whether to show fold buttons in the gutter.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
|
|||||||
@@ -436,6 +436,57 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let editor = cx.add_window(|cx| {
|
||||||
|
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
|
||||||
|
build_editor(buffer, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = editor.update(cx, |view, cx| {
|
||||||
|
view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = editor.update(cx, |view, cx| {
|
||||||
|
view.end_selection(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = editor.update(cx, |view, cx| {
|
||||||
|
view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = editor.update(cx, |view, cx| {
|
||||||
|
view.end_selection(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
editor
|
||||||
|
.update(cx, |view, cx| view.selections.display_ranges(cx))
|
||||||
|
.unwrap(),
|
||||||
|
[
|
||||||
|
DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
|
||||||
|
DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
_ = editor.update(cx, |view, cx| {
|
||||||
|
view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = editor.update(cx, |view, cx| {
|
||||||
|
view.end_selection(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
editor
|
||||||
|
.update(cx, |view, cx| view.selections.display_ranges(cx))
|
||||||
|
.unwrap(),
|
||||||
|
[DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
|
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
@@ -858,6 +909,175 @@ fn test_fold_action(cx: &mut TestAppContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let view = cx.add_window(|cx| {
|
||||||
|
let buffer = MultiBuffer::build_simple(
|
||||||
|
&"
|
||||||
|
class Foo:
|
||||||
|
# Hello!
|
||||||
|
|
||||||
|
def a():
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
def b():
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
def c():
|
||||||
|
print(3)
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
build_editor(buffer.clone(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = view.update(cx, |view, cx| {
|
||||||
|
view.change_selections(None, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
view.fold(&Fold, cx);
|
||||||
|
assert_eq!(
|
||||||
|
view.display_text(cx),
|
||||||
|
"
|
||||||
|
class Foo:
|
||||||
|
# Hello!
|
||||||
|
|
||||||
|
def a():
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
def b():⋯
|
||||||
|
|
||||||
|
def c():⋯
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
view.fold(&Fold, cx);
|
||||||
|
assert_eq!(
|
||||||
|
view.display_text(cx),
|
||||||
|
"
|
||||||
|
class Foo:⋯
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
view.unfold_lines(&UnfoldLines, cx);
|
||||||
|
assert_eq!(
|
||||||
|
view.display_text(cx),
|
||||||
|
"
|
||||||
|
class Foo:
|
||||||
|
# Hello!
|
||||||
|
|
||||||
|
def a():
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
def b():⋯
|
||||||
|
|
||||||
|
def c():⋯
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
view.unfold_lines(&UnfoldLines, cx);
|
||||||
|
assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let view = cx.add_window(|cx| {
|
||||||
|
let buffer = MultiBuffer::build_simple(
|
||||||
|
&"
|
||||||
|
class Foo:
|
||||||
|
# Hello!
|
||||||
|
|
||||||
|
def a():
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
def b():
|
||||||
|
print(2)
|
||||||
|
|
||||||
|
|
||||||
|
def c():
|
||||||
|
print(3)
|
||||||
|
|
||||||
|
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
build_editor(buffer.clone(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = view.update(cx, |view, cx| {
|
||||||
|
view.change_selections(None, cx, |s| {
|
||||||
|
s.select_display_ranges([
|
||||||
|
DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
view.fold(&Fold, cx);
|
||||||
|
assert_eq!(
|
||||||
|
view.display_text(cx),
|
||||||
|
"
|
||||||
|
class Foo:
|
||||||
|
# Hello!
|
||||||
|
|
||||||
|
def a():
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
def b():⋯
|
||||||
|
|
||||||
|
|
||||||
|
def c():⋯
|
||||||
|
|
||||||
|
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
view.fold(&Fold, cx);
|
||||||
|
assert_eq!(
|
||||||
|
view.display_text(cx),
|
||||||
|
"
|
||||||
|
class Foo:⋯
|
||||||
|
|
||||||
|
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
view.unfold_lines(&UnfoldLines, cx);
|
||||||
|
assert_eq!(
|
||||||
|
view.display_text(cx),
|
||||||
|
"
|
||||||
|
class Foo:
|
||||||
|
# Hello!
|
||||||
|
|
||||||
|
def a():
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
def b():⋯
|
||||||
|
|
||||||
|
|
||||||
|
def c():⋯
|
||||||
|
|
||||||
|
|
||||||
|
"
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
view.unfold_lines(&UnfoldLines, cx);
|
||||||
|
assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_move_cursor(cx: &mut TestAppContext) {
|
fn test_move_cursor(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
@@ -4635,12 +4855,14 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: false,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "(".to_string(),
|
start: "(".to_string(),
|
||||||
end: ")".to_string(),
|
end: ")".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: false,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -4684,7 +4906,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
|
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let mut cx = EditorTestContext::new(cx).await;
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
@@ -4697,32 +4919,44 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "(".to_string(),
|
start: "(".to_string(),
|
||||||
end: ")".to_string(),
|
end: ")".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "/*".to_string(),
|
start: "/*".to_string(),
|
||||||
end: " */".to_string(),
|
end: " */".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "[".to_string(),
|
start: "[".to_string(),
|
||||||
end: "]".to_string(),
|
end: "]".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: false,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "\"".to_string(),
|
start: "\"".to_string(),
|
||||||
end: "\"".to_string(),
|
end: "\"".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: false,
|
newline: false,
|
||||||
},
|
},
|
||||||
|
BracketPair {
|
||||||
|
start: "<".to_string(),
|
||||||
|
end: ">".to_string(),
|
||||||
|
close: false,
|
||||||
|
surround: true,
|
||||||
|
newline: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -4850,6 +5084,16 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
|
|||||||
cx.assert_editor_state("a\"ˇ\"");
|
cx.assert_editor_state("a\"ˇ\"");
|
||||||
cx.update_editor(|view, cx| view.handle_input("\"", cx));
|
cx.update_editor(|view, cx| view.handle_input("\"", cx));
|
||||||
cx.assert_editor_state("a\"\"ˇ");
|
cx.assert_editor_state("a\"\"ˇ");
|
||||||
|
|
||||||
|
// Don't autoclose pair if autoclose is disabled
|
||||||
|
cx.set_state("ˇ");
|
||||||
|
cx.update_editor(|view, cx| view.handle_input("<", cx));
|
||||||
|
cx.assert_editor_state("<ˇ");
|
||||||
|
|
||||||
|
// Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
|
||||||
|
cx.set_state("«aˇ» b");
|
||||||
|
cx.update_editor(|view, cx| view.handle_input("<", cx));
|
||||||
|
cx.assert_editor_state("<«aˇ»> b");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@@ -4868,18 +5112,21 @@ async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestA
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "(".to_string(),
|
start: "(".to_string(),
|
||||||
end: ")".to_string(),
|
end: ")".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "[".to_string(),
|
start: "[".to_string(),
|
||||||
end: "]".to_string(),
|
end: "]".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: false,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -5293,12 +5540,14 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "/* ".to_string(),
|
start: "/* ".to_string(),
|
||||||
end: "*/".to_string(),
|
end: "*/".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -5447,6 +5696,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -5558,18 +5808,21 @@ async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppC
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "(".to_string(),
|
start: "(".to_string(),
|
||||||
end: ")".to_string(),
|
end: ")".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "[".to_string(),
|
start: "[".to_string(),
|
||||||
end: "]".to_string(),
|
end: "]".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -5861,7 +6114,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(params.options.tab_size, 4);
|
assert_eq!(params.options.tab_size, 4);
|
||||||
Ok(Some(vec![lsp::TextEdit::new(
|
Ok(Some(vec![lsp::TextEdit::new(
|
||||||
@@ -5887,7 +6140,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
futures::future::pending::<()>().await;
|
futures::future::pending::<()>().await;
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -5936,7 +6189,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(params.options.tab_size, 8);
|
assert_eq!(params.options.tab_size, 8);
|
||||||
Ok(Some(vec![]))
|
Ok(Some(vec![]))
|
||||||
@@ -6139,7 +6392,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
.on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
.on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
Ok(Some(vec![lsp::TextEdit::new(
|
Ok(Some(vec![lsp::TextEdit::new(
|
||||||
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
|
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
|
||||||
format!("[{} formatted]", params.text_document.uri.as_str()),
|
format!("[{} formatted]", params.text_document.uri),
|
||||||
)]))
|
)]))
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@@ -6213,7 +6466,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(params.options.tab_size, 4);
|
assert_eq!(params.options.tab_size, 4);
|
||||||
Ok(Some(vec![lsp::TextEdit::new(
|
Ok(Some(vec![lsp::TextEdit::new(
|
||||||
@@ -6239,7 +6492,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
move |params, _| async move {
|
move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
futures::future::pending::<()>().await;
|
futures::future::pending::<()>().await;
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -6289,7 +6542,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(params.options.tab_size, 8);
|
assert_eq!(params.options.tab_size, 8);
|
||||||
Ok(Some(vec![]))
|
Ok(Some(vec![]))
|
||||||
@@ -6363,7 +6616,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
|||||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(params.options.tab_size, 4);
|
assert_eq!(params.options.tab_size, 4);
|
||||||
Ok(Some(vec![lsp::TextEdit::new(
|
Ok(Some(vec![lsp::TextEdit::new(
|
||||||
@@ -6385,7 +6638,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
|||||||
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document.uri,
|
params.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||||
);
|
);
|
||||||
futures::future::pending::<()>().await;
|
futures::future::pending::<()>().await;
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -7537,12 +7790,14 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "/* ".to_string(),
|
start: "/* ".to_string(),
|
||||||
end: " */".to_string(),
|
end: " */".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -8028,7 +8283,7 @@ async fn go_to_prev_overlapping_diagnostic(
|
|||||||
.update_diagnostics(
|
.update_diagnostics(
|
||||||
LanguageServerId(0),
|
LanguageServerId(0),
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: lsp::Uri::from_file_path("/root/file").unwrap().into(),
|
uri: lsp::Url::from_file_path("/root/file").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
diagnostics: vec![
|
diagnostics: vec![
|
||||||
lsp::Diagnostic {
|
lsp::Diagnostic {
|
||||||
@@ -8344,6 +8599,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: true,
|
close: true,
|
||||||
|
surround: true,
|
||||||
newline: true,
|
newline: true,
|
||||||
}],
|
}],
|
||||||
disabled_scopes_by_bracket_ix: Vec::new(),
|
disabled_scopes_by_bracket_ix: Vec::new(),
|
||||||
@@ -8400,7 +8656,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
|||||||
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.text_document.uri,
|
params.text_document_position.text_document.uri,
|
||||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
@@ -11603,6 +11859,7 @@ fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -
|
|||||||
settings: IndentGuideSettings {
|
settings: IndentGuideSettings {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
line_width: 1,
|
line_width: 1,
|
||||||
|
active_line_width: 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -12049,7 +12306,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let editor = cx.add_window(|cx| {
|
let editor = cx.add_window(|cx| {
|
||||||
@@ -12070,7 +12327,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
|||||||
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let flap = Flap::new(
|
let crease = Crease::new(
|
||||||
range,
|
range,
|
||||||
FoldPlaceholder::test(),
|
FoldPlaceholder::test(),
|
||||||
{
|
{
|
||||||
@@ -12087,7 +12344,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
|||||||
|_row, _folded, _cx| div(),
|
|_row, _folded, _cx| div(),
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.insert_flaps(Some(flap), cx);
|
editor.insert_creases(Some(crease), cx);
|
||||||
let snapshot = editor.snapshot(cx);
|
let snapshot = editor.snapshot(cx);
|
||||||
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
|
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
|
||||||
snapshot
|
snapshot
|
||||||
@@ -12151,10 +12408,7 @@ pub fn handle_completion_request(
|
|||||||
let completions = completions.clone();
|
let completions = completions.clone();
|
||||||
counter.fetch_add(1, atomic::Ordering::Release);
|
counter.fetch_add(1, atomic::Ordering::Release);
|
||||||
async move {
|
async move {
|
||||||
assert_eq!(
|
assert_eq!(params.text_document_position.text_document.uri, url.clone());
|
||||||
params.text_document_position.text_document.uri,
|
|
||||||
url.clone().into()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
params.text_document_position.position,
|
params.text_document_position.position,
|
||||||
complete_from_position
|
complete_from_position
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::editor_settings::ScrollBeyondLastLine;
|
||||||
use crate::{
|
use crate::{
|
||||||
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
||||||
display_map::{
|
display_map::{
|
||||||
@@ -167,6 +168,8 @@ impl EditorElement {
|
|||||||
register_action(view, cx, Editor::move_up);
|
register_action(view, cx, Editor::move_up);
|
||||||
register_action(view, cx, Editor::move_up_by_lines);
|
register_action(view, cx, Editor::move_up_by_lines);
|
||||||
register_action(view, cx, Editor::select_up_by_lines);
|
register_action(view, cx, Editor::select_up_by_lines);
|
||||||
|
register_action(view, cx, Editor::select_page_down);
|
||||||
|
register_action(view, cx, Editor::select_page_up);
|
||||||
register_action(view, cx, Editor::cancel);
|
register_action(view, cx, Editor::cancel);
|
||||||
register_action(view, cx, Editor::newline);
|
register_action(view, cx, Editor::newline);
|
||||||
register_action(view, cx, Editor::newline_above);
|
register_action(view, cx, Editor::newline_above);
|
||||||
@@ -273,6 +276,7 @@ impl EditorElement {
|
|||||||
register_action(view, cx, Editor::toggle_comments);
|
register_action(view, cx, Editor::toggle_comments);
|
||||||
register_action(view, cx, Editor::select_larger_syntax_node);
|
register_action(view, cx, Editor::select_larger_syntax_node);
|
||||||
register_action(view, cx, Editor::select_smaller_syntax_node);
|
register_action(view, cx, Editor::select_smaller_syntax_node);
|
||||||
|
register_action(view, cx, Editor::select_enclosing_symbol);
|
||||||
register_action(view, cx, Editor::move_to_enclosing_bracket);
|
register_action(view, cx, Editor::move_to_enclosing_bracket);
|
||||||
register_action(view, cx, Editor::undo_selection);
|
register_action(view, cx, Editor::undo_selection);
|
||||||
register_action(view, cx, Editor::redo_selection);
|
register_action(view, cx, Editor::redo_selection);
|
||||||
@@ -341,6 +345,7 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
register_action(view, cx, Editor::restart_language_server);
|
register_action(view, cx, Editor::restart_language_server);
|
||||||
|
register_action(view, cx, Editor::cancel_language_server_work);
|
||||||
register_action(view, cx, Editor::show_character_palette);
|
register_action(view, cx, Editor::show_character_palette);
|
||||||
register_action(view, cx, |editor, action, cx| {
|
register_action(view, cx, |editor, action, cx| {
|
||||||
if let Some(task) = editor.confirm_completion(action, cx) {
|
if let Some(task) = editor.confirm_completion(action, cx) {
|
||||||
@@ -855,6 +860,28 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selections.extend(remote_selections.into_values());
|
selections.extend(remote_selections.into_values());
|
||||||
|
} else if !editor.is_focused(cx) && editor.show_cursor_when_unfocused {
|
||||||
|
let player = if editor.read_only(cx) {
|
||||||
|
cx.theme().players().read_only()
|
||||||
|
} else {
|
||||||
|
self.style.local_player
|
||||||
|
};
|
||||||
|
let layouts = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.selections_in_range(&(start_anchor..end_anchor), true)
|
||||||
|
.map(move |(_, line_mode, cursor_shape, selection)| {
|
||||||
|
SelectionLayout::new(
|
||||||
|
selection,
|
||||||
|
line_mode,
|
||||||
|
cursor_shape,
|
||||||
|
&snapshot.display_snapshot,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
selections.push((player, layouts));
|
||||||
}
|
}
|
||||||
(selections, active_rows, newest_selection_head)
|
(selections, active_rows, newest_selection_head)
|
||||||
}
|
}
|
||||||
@@ -1086,11 +1113,17 @@ impl EditorElement {
|
|||||||
point(bounds.lower_right().x, bounds.lower_left().y),
|
point(bounds.lower_right().x, bounds.lower_left().y),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let settings = EditorSettings::get_global(cx);
|
||||||
|
let scroll_beyond_last_line: f32 = match settings.scroll_beyond_last_line {
|
||||||
|
ScrollBeyondLastLine::OnePage => rows_per_page,
|
||||||
|
ScrollBeyondLastLine::Off => 1.0,
|
||||||
|
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
|
||||||
|
};
|
||||||
|
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
|
||||||
let height = bounds.size.height;
|
let height = bounds.size.height;
|
||||||
let total_rows = snapshot.max_point().row().as_f32() + rows_per_page;
|
|
||||||
let px_per_row = height / total_rows;
|
let px_per_row = height / total_rows;
|
||||||
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
|
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
|
||||||
let row_height = (height - thumb_height) / snapshot.max_point().row().as_f32();
|
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
|
||||||
|
|
||||||
Some(ScrollbarLayout {
|
Some(ScrollbarLayout {
|
||||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||||
@@ -1136,7 +1169,7 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn prepaint_flap_trailers(
|
fn prepaint_crease_trailers(
|
||||||
&self,
|
&self,
|
||||||
trailers: Vec<Option<AnyElement>>,
|
trailers: Vec<Option<AnyElement>>,
|
||||||
lines: &[LineWithInvisibles],
|
lines: &[LineWithInvisibles],
|
||||||
@@ -1145,7 +1178,7 @@ impl EditorElement {
|
|||||||
scroll_pixel_position: gpui::Point<Pixels>,
|
scroll_pixel_position: gpui::Point<Pixels>,
|
||||||
em_width: Pixels,
|
em_width: Pixels,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Vec<Option<FlapTrailerLayout>> {
|
) -> Vec<Option<CreaseTrailerLayout>> {
|
||||||
trailers
|
trailers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -1170,7 +1203,7 @@ impl EditorElement {
|
|||||||
let centering_offset = point(px(0.), (line_height - size.height) / 2.);
|
let centering_offset = point(px(0.), (line_height - size.height) / 2.);
|
||||||
let origin = content_origin + position + centering_offset;
|
let origin = content_origin + position + centering_offset;
|
||||||
element.prepaint_as_root(origin, available_space, cx);
|
element.prepaint_as_root(origin, available_space, cx);
|
||||||
Some(FlapTrailerLayout {
|
Some(CreaseTrailerLayout {
|
||||||
element,
|
element,
|
||||||
bounds: Bounds::new(origin, size),
|
bounds: Bounds::new(origin, size),
|
||||||
})
|
})
|
||||||
@@ -1266,7 +1299,7 @@ impl EditorElement {
|
|||||||
display_row: DisplayRow,
|
display_row: DisplayRow,
|
||||||
display_snapshot: &DisplaySnapshot,
|
display_snapshot: &DisplaySnapshot,
|
||||||
line_layout: &LineWithInvisibles,
|
line_layout: &LineWithInvisibles,
|
||||||
flap_trailer: Option<&FlapTrailerLayout>,
|
crease_trailer: Option<&CreaseTrailerLayout>,
|
||||||
em_width: Pixels,
|
em_width: Pixels,
|
||||||
content_origin: gpui::Point<Pixels>,
|
content_origin: gpui::Point<Pixels>,
|
||||||
scroll_pixel_position: gpui::Point<Pixels>,
|
scroll_pixel_position: gpui::Point<Pixels>,
|
||||||
@@ -1306,8 +1339,8 @@ impl EditorElement {
|
|||||||
let start_x = {
|
let start_x = {
|
||||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||||
|
|
||||||
let line_end = if let Some(flap_trailer) = flap_trailer {
|
let line_end = if let Some(crease_trailer) = crease_trailer {
|
||||||
flap_trailer.bounds.right()
|
crease_trailer.bounds.right()
|
||||||
} else {
|
} else {
|
||||||
content_origin.x - scroll_pixel_position.x + line_layout.width
|
content_origin.x - scroll_pixel_position.x + line_layout.width
|
||||||
};
|
};
|
||||||
@@ -1779,7 +1812,7 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_flap_trailers(
|
fn layout_crease_trailers(
|
||||||
&self,
|
&self,
|
||||||
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
@@ -1789,7 +1822,7 @@ impl EditorElement {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
if let Some(multibuffer_row) = row {
|
if let Some(multibuffer_row) = row {
|
||||||
snapshot.render_flap_trailer(multibuffer_row, cx)
|
snapshot.render_crease_trailer(multibuffer_row, cx)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -2781,7 +2814,12 @@ impl EditorElement {
|
|||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let requested_line_width = settings.line_width.clamp(1, 10);
|
let requested_line_width = if indent_guide.active {
|
||||||
|
settings.active_line_width
|
||||||
|
} else {
|
||||||
|
settings.line_width
|
||||||
|
}
|
||||||
|
.clamp(1, 10);
|
||||||
let mut line_indicator_width = 0.;
|
let mut line_indicator_width = 0.;
|
||||||
if let Some(color) = line_color {
|
if let Some(color) = line_color {
|
||||||
cx.paint_quad(fill(
|
cx.paint_quad(fill(
|
||||||
@@ -2810,38 +2848,12 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
fn paint_line_numbers(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
let line_height = layout.position_map.line_height;
|
let line_height = layout.position_map.line_height;
|
||||||
|
|
||||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||||
let scroll_top = scroll_position.y * line_height;
|
let scroll_top = scroll_position.y * line_height;
|
||||||
|
|
||||||
cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
|
cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
|
||||||
for (_, hunk_hitbox) in &layout.display_hunks {
|
|
||||||
if let Some(hunk_hitbox) = hunk_hitbox {
|
|
||||||
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let show_git_gutter = layout
|
|
||||||
.position_map
|
|
||||||
.snapshot
|
|
||||||
.show_git_diff_gutter
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
matches!(
|
|
||||||
ProjectSettings::get_global(cx).git.git_gutter,
|
|
||||||
Some(GitGutterSetting::TrackedFiles)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
if show_git_gutter {
|
|
||||||
Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.paint_gutter_highlights(layout, cx);
|
|
||||||
|
|
||||||
if layout.blamed_display_rows.is_some() {
|
|
||||||
self.paint_blamed_display_rows(layout, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ix, line) in layout.line_numbers.iter().enumerate() {
|
for (ix, line) in layout.line_numbers.iter().enumerate() {
|
||||||
if let Some(line) = line {
|
if let Some(line) = line {
|
||||||
@@ -2856,29 +2868,9 @@ impl EditorElement {
|
|||||||
line.paint(line_origin, line_height, cx).log_err();
|
line.paint(line_origin, line_height, cx).log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
|
||||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
|
||||||
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
|
|
||||||
fold_indicator.paint(cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for test_indicators in layout.test_indicators.iter_mut() {
|
|
||||||
test_indicators.paint(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
|
||||||
indicator.paint(cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_diff_hunks(
|
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
|
||||||
gutter_bounds: Bounds<Pixels>,
|
|
||||||
layout: &EditorLayout,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) {
|
|
||||||
if layout.display_hunks.is_empty() {
|
if layout.display_hunks.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2891,7 +2883,7 @@ impl EditorElement {
|
|||||||
let hunk_bounds = Self::diff_hunk_bounds(
|
let hunk_bounds = Self::diff_hunk_bounds(
|
||||||
&layout.position_map.snapshot,
|
&layout.position_map.snapshot,
|
||||||
line_height,
|
line_height,
|
||||||
gutter_bounds,
|
layout.gutter_hitbox.bounds,
|
||||||
&hunk,
|
&hunk,
|
||||||
);
|
);
|
||||||
Some((
|
Some((
|
||||||
@@ -3008,7 +3000,45 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn paint_gutter_indicators(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
|
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||||
|
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||||
|
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
|
||||||
|
fold_indicator.paint(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for test_indicators in layout.test_indicators.iter_mut() {
|
||||||
|
test_indicators.paint(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
||||||
|
indicator.paint(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
|
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||||
|
for (_, hunk_hitbox) in &layout.display_hunks {
|
||||||
|
if let Some(hunk_hitbox) = hunk_hitbox {
|
||||||
|
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let show_git_gutter = layout
|
||||||
|
.position_map
|
||||||
|
.snapshot
|
||||||
|
.show_git_diff_gutter
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
matches!(
|
||||||
|
ProjectSettings::get_global(cx).git.git_gutter,
|
||||||
|
Some(GitGutterSetting::TrackedFiles)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if show_git_gutter {
|
||||||
|
Self::paint_diff_hunks(layout, cx)
|
||||||
|
}
|
||||||
|
|
||||||
let highlight_width = 0.275 * layout.position_map.line_height;
|
let highlight_width = 0.275 * layout.position_map.line_height;
|
||||||
let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
|
let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
|
||||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||||
@@ -3075,8 +3105,8 @@ impl EditorElement {
|
|||||||
self.paint_redactions(layout, cx);
|
self.paint_redactions(layout, cx);
|
||||||
self.paint_cursors(layout, cx);
|
self.paint_cursors(layout, cx);
|
||||||
self.paint_inline_blame(layout, cx);
|
self.paint_inline_blame(layout, cx);
|
||||||
cx.with_element_namespace("flap_trailers", |cx| {
|
cx.with_element_namespace("crease_trailers", |cx| {
|
||||||
for trailer in layout.flap_trailers.iter_mut().flatten() {
|
for trailer in layout.crease_trailers.iter_mut().flatten() {
|
||||||
trailer.element.paint(cx);
|
trailer.element.paint(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3624,12 +3654,12 @@ impl EditorElement {
|
|||||||
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
|
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
|
||||||
if forbid_vertical_scroll {
|
if forbid_vertical_scroll {
|
||||||
scroll_position.y = current_scroll_position.y;
|
scroll_position.y = current_scroll_position.y;
|
||||||
if scroll_position == current_scroll_position {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
editor.scroll(scroll_position, axis, cx);
|
|
||||||
cx.stop_propagation();
|
if scroll_position != current_scroll_position {
|
||||||
|
editor.scroll(scroll_position, axis, cx);
|
||||||
|
cx.stop_propagation();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4614,13 +4644,29 @@ impl Element for EditorElement {
|
|||||||
let content_origin =
|
let content_origin =
|
||||||
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
||||||
|
|
||||||
|
let height_in_lines = bounds.size.height / line_height;
|
||||||
|
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
|
||||||
|
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
|
||||||
|
} else {
|
||||||
|
let settings = EditorSettings::get_global(cx);
|
||||||
|
let max_row = snapshot.max_point().row().as_f32();
|
||||||
|
match settings.scroll_beyond_last_line {
|
||||||
|
ScrollBeyondLastLine::OnePage => max_row,
|
||||||
|
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
|
||||||
|
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||||
|
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
|
||||||
|
.max(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut autoscroll_containing_element = false;
|
let mut autoscroll_containing_element = false;
|
||||||
let mut autoscroll_horizontally = false;
|
let mut autoscroll_horizontally = false;
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
autoscroll_containing_element =
|
autoscroll_containing_element =
|
||||||
editor.autoscroll_requested() || editor.has_pending_selection();
|
editor.autoscroll_requested() || editor.has_pending_selection();
|
||||||
autoscroll_horizontally =
|
autoscroll_horizontally =
|
||||||
editor.autoscroll_vertically(bounds, line_height, cx);
|
editor.autoscroll_vertically(bounds, line_height, max_scroll_top, cx);
|
||||||
snapshot = editor.snapshot(cx);
|
snapshot = editor.snapshot(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4628,7 +4674,6 @@ impl Element for EditorElement {
|
|||||||
// The scroll position is a fractional point, the whole number of which represents
|
// The scroll position is a fractional point, the whole number of which represents
|
||||||
// the top of the window in terms of display rows.
|
// the top of the window in terms of display rows.
|
||||||
let start_row = DisplayRow(scroll_position.y as u32);
|
let start_row = DisplayRow(scroll_position.y as u32);
|
||||||
let height_in_lines = bounds.size.height / line_height;
|
|
||||||
let max_row = snapshot.max_point().row();
|
let max_row = snapshot.max_point().row();
|
||||||
let end_row = cmp::min(
|
let end_row = cmp::min(
|
||||||
(scroll_position.y + height_in_lines).ceil() as u32,
|
(scroll_position.y + height_in_lines).ceil() as u32,
|
||||||
@@ -4705,8 +4750,8 @@ impl Element for EditorElement {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
|
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
|
||||||
self.layout_flap_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let display_hunks = self.layout_git_gutters(
|
let display_hunks = self.layout_git_gutters(
|
||||||
@@ -4767,9 +4812,9 @@ impl Element for EditorElement {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
|
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
|
||||||
self.prepaint_flap_trailers(
|
self.prepaint_crease_trailers(
|
||||||
flap_trailers,
|
crease_trailers,
|
||||||
&line_layouts,
|
&line_layouts,
|
||||||
line_height,
|
line_height,
|
||||||
content_origin,
|
content_origin,
|
||||||
@@ -4785,12 +4830,12 @@ impl Element for EditorElement {
|
|||||||
if (start_row..end_row).contains(&display_row) {
|
if (start_row..end_row).contains(&display_row) {
|
||||||
let line_ix = display_row.minus(start_row) as usize;
|
let line_ix = display_row.minus(start_row) as usize;
|
||||||
let line_layout = &line_layouts[line_ix];
|
let line_layout = &line_layouts[line_ix];
|
||||||
let flap_trailer_layout = flap_trailers[line_ix].as_ref();
|
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
|
||||||
inline_blame = self.layout_inline_blame(
|
inline_blame = self.layout_inline_blame(
|
||||||
display_row,
|
display_row,
|
||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
line_layout,
|
line_layout,
|
||||||
flap_trailer_layout,
|
crease_trailer_layout,
|
||||||
em_width,
|
em_width,
|
||||||
content_origin,
|
content_origin,
|
||||||
scroll_pixel_position,
|
scroll_pixel_position,
|
||||||
@@ -4812,7 +4857,7 @@ impl Element for EditorElement {
|
|||||||
|
|
||||||
let scroll_max = point(
|
let scroll_max = point(
|
||||||
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
|
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
|
||||||
max_row.as_f32(),
|
max_scroll_top,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
@@ -4936,14 +4981,18 @@ impl Element for EditorElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_indicators = self.layout_run_indicators(
|
let test_indicators = if gutter_settings.runnables {
|
||||||
line_height,
|
self.layout_run_indicators(
|
||||||
scroll_pixel_position,
|
line_height,
|
||||||
&gutter_dimensions,
|
scroll_pixel_position,
|
||||||
&gutter_hitbox,
|
&gutter_dimensions,
|
||||||
&snapshot,
|
&gutter_hitbox,
|
||||||
cx,
|
&snapshot,
|
||||||
);
|
cx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
if !cx.has_active_drag() {
|
if !cx.has_active_drag() {
|
||||||
self.layout_hover_popovers(
|
self.layout_hover_popovers(
|
||||||
@@ -5045,7 +5094,7 @@ impl Element for EditorElement {
|
|||||||
test_indicators,
|
test_indicators,
|
||||||
code_actions_indicator,
|
code_actions_indicator,
|
||||||
gutter_fold_toggles,
|
gutter_fold_toggles,
|
||||||
flap_trailers,
|
crease_trailers,
|
||||||
tab_invisible,
|
tab_invisible,
|
||||||
space_invisible,
|
space_invisible,
|
||||||
}
|
}
|
||||||
@@ -5112,8 +5161,10 @@ impl Element for EditorElement {
|
|||||||
self.paint_mouse_listeners(layout, hovered_hunk, cx);
|
self.paint_mouse_listeners(layout, hovered_hunk, cx);
|
||||||
self.paint_background(layout, cx);
|
self.paint_background(layout, cx);
|
||||||
self.paint_indent_guides(layout, cx);
|
self.paint_indent_guides(layout, cx);
|
||||||
|
|
||||||
if layout.gutter_hitbox.size.width > Pixels::ZERO {
|
if layout.gutter_hitbox.size.width > Pixels::ZERO {
|
||||||
self.paint_gutter(layout, cx)
|
self.paint_blamed_display_rows(layout, cx);
|
||||||
|
self.paint_line_numbers(layout, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.paint_text(layout, cx);
|
self.paint_text(layout, cx);
|
||||||
@@ -5124,6 +5175,11 @@ impl Element for EditorElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if layout.gutter_hitbox.size.width > Pixels::ZERO {
|
||||||
|
self.paint_gutter_highlights(layout, cx);
|
||||||
|
self.paint_gutter_indicators(layout, cx);
|
||||||
|
}
|
||||||
|
|
||||||
self.paint_scrollbar(layout, cx);
|
self.paint_scrollbar(layout, cx);
|
||||||
self.paint_mouse_context_menu(layout, cx);
|
self.paint_mouse_context_menu(layout, cx);
|
||||||
});
|
});
|
||||||
@@ -5169,7 +5225,7 @@ pub struct EditorLayout {
|
|||||||
code_actions_indicator: Option<AnyElement>,
|
code_actions_indicator: Option<AnyElement>,
|
||||||
test_indicators: Vec<AnyElement>,
|
test_indicators: Vec<AnyElement>,
|
||||||
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
||||||
flap_trailers: Vec<Option<FlapTrailerLayout>>,
|
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
|
||||||
mouse_context_menu: Option<AnyElement>,
|
mouse_context_menu: Option<AnyElement>,
|
||||||
tab_invisible: ShapedLine,
|
tab_invisible: ShapedLine,
|
||||||
space_invisible: ShapedLine,
|
space_invisible: ShapedLine,
|
||||||
@@ -5293,7 +5349,7 @@ impl ScrollbarLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FlapTrailerLayout {
|
struct CreaseTrailerLayout {
|
||||||
element: AnyElement,
|
element: AnyElement,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,12 +55,14 @@ mod tests {
|
|||||||
start: "{".to_string(),
|
start: "{".to_string(),
|
||||||
end: "}".to_string(),
|
end: "}".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: false,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
BracketPair {
|
BracketPair {
|
||||||
start: "(".to_string(),
|
start: "(".to_string(),
|
||||||
end: ")".to_string(),
|
end: ")".to_string(),
|
||||||
close: false,
|
close: false,
|
||||||
|
surround: false,
|
||||||
newline: true,
|
newline: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -741,7 +741,7 @@ mod tests {
|
|||||||
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
|
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
|
||||||
lsp::LocationLink {
|
lsp::LocationLink {
|
||||||
origin_selection_range: Some(symbol_range),
|
origin_selection_range: Some(symbol_range),
|
||||||
target_uri: url.clone().into(),
|
target_uri: url.clone(),
|
||||||
target_range,
|
target_range,
|
||||||
target_selection_range: target_range,
|
target_selection_range: target_range,
|
||||||
},
|
},
|
||||||
@@ -815,7 +815,7 @@ mod tests {
|
|||||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
lsp::LocationLink {
|
lsp::LocationLink {
|
||||||
origin_selection_range: Some(symbol_range),
|
origin_selection_range: Some(symbol_range),
|
||||||
target_uri: url.clone().into(),
|
target_uri: url.clone(),
|
||||||
target_range,
|
target_range,
|
||||||
target_selection_range: target_range,
|
target_selection_range: target_range,
|
||||||
},
|
},
|
||||||
@@ -841,7 +841,7 @@ mod tests {
|
|||||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
lsp::LocationLink {
|
lsp::LocationLink {
|
||||||
origin_selection_range: Some(symbol_range),
|
origin_selection_range: Some(symbol_range),
|
||||||
target_uri: url.clone().into(),
|
target_uri: url.clone(),
|
||||||
target_range,
|
target_range,
|
||||||
target_selection_range: target_range,
|
target_selection_range: target_range,
|
||||||
},
|
},
|
||||||
@@ -904,7 +904,7 @@ mod tests {
|
|||||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
lsp::LocationLink {
|
lsp::LocationLink {
|
||||||
origin_selection_range: Some(symbol_range),
|
origin_selection_range: Some(symbol_range),
|
||||||
target_uri: url.into(),
|
target_uri: url,
|
||||||
target_range,
|
target_range,
|
||||||
target_selection_range: target_range,
|
target_selection_range: target_range,
|
||||||
},
|
},
|
||||||
@@ -980,7 +980,7 @@ mod tests {
|
|||||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
lsp::LocationLink {
|
lsp::LocationLink {
|
||||||
origin_selection_range: None,
|
origin_selection_range: None,
|
||||||
target_uri: url.into(),
|
target_uri: url,
|
||||||
target_range,
|
target_range,
|
||||||
target_selection_range: target_range,
|
target_selection_range: target_range,
|
||||||
},
|
},
|
||||||
@@ -1008,7 +1008,7 @@ mod tests {
|
|||||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
lsp::LocationLink {
|
lsp::LocationLink {
|
||||||
origin_selection_range: None,
|
origin_selection_range: None,
|
||||||
target_uri: url.into(),
|
target_uri: url,
|
||||||
target_range,
|
target_range,
|
||||||
target_selection_range: target_range,
|
target_selection_range: target_range,
|
||||||
},
|
},
|
||||||
@@ -1088,7 +1088,7 @@ mod tests {
|
|||||||
let hint_label = ": TestStruct";
|
let hint_label = ": TestStruct";
|
||||||
cx.lsp
|
cx.lsp
|
||||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||||
let expected_uri = expected_uri.clone().into();
|
let expected_uri = expected_uri.clone();
|
||||||
async move {
|
async move {
|
||||||
assert_eq!(params.text_document.uri, expected_uri);
|
assert_eq!(params.text_document.uri, expected_uri);
|
||||||
Ok(Some(vec![lsp::InlayHint {
|
Ok(Some(vec![lsp::InlayHint {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user