Compare commits
61 Commits
new-diff-m
...
jump-into-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2673bb667 | ||
|
|
3632b36fde | ||
|
|
97e11fd5d2 | ||
|
|
150aa03c5f | ||
|
|
ebf6115c3c | ||
|
|
394af7481d | ||
|
|
7b0d63fffb | ||
|
|
f64bfe8c1d | ||
|
|
5b86845605 | ||
|
|
9782abf3c5 | ||
|
|
6231072d85 | ||
|
|
2094d50514 | ||
|
|
1e2fa3b022 | ||
|
|
8e81070091 | ||
|
|
1071814d41 | ||
|
|
3d3d8f20eb | ||
|
|
536a958c58 | ||
|
|
96ad022cd7 | ||
|
|
11260e6d37 | ||
|
|
d54662e683 | ||
|
|
2a17274ec2 | ||
|
|
b93cee8d27 | ||
|
|
837bbc851f | ||
|
|
0fe88a88b1 | ||
|
|
2b4f0deff5 | ||
|
|
4f2ab812fb | ||
|
|
3f40d76be4 | ||
|
|
6bb21b1e5e | ||
|
|
f7a7866d4a | ||
|
|
298b9df589 | ||
|
|
56d20fc0a3 | ||
|
|
fc00eaa161 | ||
|
|
7414e91a85 | ||
|
|
b51a162d22 | ||
|
|
78dde63337 | ||
|
|
c0b40d0bd0 | ||
|
|
0acb743dac | ||
|
|
8b2afab0d3 | ||
|
|
b79117c882 | ||
|
|
77abf13f42 | ||
|
|
433cb99170 | ||
|
|
670ade9546 | ||
|
|
4a6f071fde | ||
|
|
2ecbd97fe8 | ||
|
|
a0a095c6a3 | ||
|
|
4bfc107e3a | ||
|
|
6192c83f23 | ||
|
|
e8c9283700 | ||
|
|
2469122784 | ||
|
|
6898a31f06 | ||
|
|
94bfb93d35 | ||
|
|
1b83020dc8 | ||
|
|
613deb6421 | ||
|
|
672fc76832 | ||
|
|
5d7b6141fd | ||
|
|
6aad616165 | ||
|
|
918866b7de | ||
|
|
5b2653a1d1 | ||
|
|
ba44db7f49 | ||
|
|
ce97e4ddc1 | ||
|
|
63d8a43f9d |
7
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
7
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -18,8 +18,11 @@ body:
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
label: Zed Version and System Specs
|
||||
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
|
||||
placeholder: |
|
||||
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
|
||||
<!-- Alternatively spawn `request feature` and this field will be autopopulated -->
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
9
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -20,8 +20,13 @@ body:
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
|
||||
label: Zed Version and System Specs
|
||||
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
|
||||
placeholder: |
|
||||
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
|
||||
<!-- Alternatively spawn `file bug report` and this field will be autopopulated -->
|
||||
<!-- If Zed won't launch, include the equivalent with other relevant details (e.g. video card driver version for display bugs, etc) -->
|
||||
<!-- Zed Version: 0.xxx.x; Channel: Stable, OS: macOS xx.xx, RAM: XXGB, Architecture: x86_64"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
54
.github/workflows/ci.yml
vendored
54
.github/workflows/ci.yml
vendored
@@ -7,14 +7,10 @@ on:
|
||||
- "v[0-9]+.[0-9]+.x"
|
||||
tags:
|
||||
- "v*"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- "docs/**/*"
|
||||
- ".github/workflows/community_*"
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
@@ -28,6 +24,23 @@ env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
jobs:
|
||||
check_docs_only:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docs_only: ${{ steps.check_changes.outputs.docs_only }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for non-docs changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "docs_only=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
migration_checks:
|
||||
name: Check Postgres and Protobuf migrations, mergability
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
@@ -96,6 +109,7 @@ jobs:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
@@ -103,29 +117,35 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: cargo clippy
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Check unused dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: bnjbvr/cargo-machete@main
|
||||
|
||||
- name: Check licenses
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: |
|
||||
script/check-licenses
|
||||
script/generate-licenses /tmp/zed_licenses_output
|
||||
|
||||
- name: Check for new vulnerable dependencies
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
|
||||
with:
|
||||
license-check: false
|
||||
|
||||
- name: Run tests
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build collab
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: cargo build -p collab
|
||||
|
||||
- name: Build other binaries and features
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: |
|
||||
cargo build --workspace --bins --all-features
|
||||
cargo check -p gpui --features "macos-blade"
|
||||
@@ -139,6 +159,7 @@ jobs:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -149,21 +170,26 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/linux
|
||||
|
||||
- name: cargo clippy
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Run tests
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build other binaries and features
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: |
|
||||
cargo build -p zed
|
||||
cargo check -p workspace
|
||||
@@ -174,6 +200,7 @@ jobs:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -184,15 +211,18 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
|
||||
- name: Install Clang & Mold
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/remote-server && ./script/install-mold 2.34.0
|
||||
|
||||
- name: Build Remote Server
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: cargo build -p remote_server
|
||||
|
||||
# todo(windows): Actually run the tests
|
||||
@@ -201,6 +231,7 @@ jobs:
|
||||
name: (Windows) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: hosted-windows-1
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
@@ -211,16 +242,19 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: cargo clippy
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||
run: cargo xtask clippy
|
||||
|
||||
- name: Build Zed
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: cargo build
|
||||
|
||||
bundle-mac:
|
||||
@@ -289,14 +323,14 @@ jobs:
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -345,7 +379,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -391,7 +425,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
fi
|
||||
echo "::set-output name=URL::$URL"
|
||||
- name: Get content
|
||||
uses: 2428392/gh-truncate-string-action@e6b5885fb83c81ca9a700a91b079baec2133be3e # v1.4.0
|
||||
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
|
||||
id: get-content
|
||||
with:
|
||||
stringToTruncate: |
|
||||
|
||||
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
check_formatting:
|
||||
|
||||
194
Cargo.lock
generated
194
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "activity_indicator"
|
||||
@@ -934,7 +934,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"http-types",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"hyper-rustls 0.24.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1008,9 +1008,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-tungstenite"
|
||||
version = "0.28.1"
|
||||
version = "0.28.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6cd055774e8b7f2ff9e5f9646efb298f180c3b886cdf20ef27f5a0bfbe2677b"
|
||||
checksum = "1c348fb0b6d132c596eca3dcd941df48fb597aafcb07a738ec41c004b087dc99"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tls",
|
||||
@@ -1166,9 +1166,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.10"
|
||||
version = "1.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924"
|
||||
checksum = "a5d1c2c88936a73c699225d0bc00684a534166b0cebc2659c3cdf08de8edc64c"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1177,7 +1177,7 @@ dependencies = [
|
||||
"aws-sdk-sts",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json 0.60.7",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
@@ -1208,9 +1208,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.4.4"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea"
|
||||
checksum = "300a12520b4e6d08b73f77680f12c16e8ae43250d55100e0b2be46d78da16a48"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-sigv4",
|
||||
@@ -1234,15 +1234,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.52.0"
|
||||
version = "1.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e5d4932ecd8754ec808b57c13b5ab4965d2b568ae1c1984d1823a4e2aa3e7bc"
|
||||
checksum = "cb367ea65d5a59b230d7e670ba59d68d1e51fc53802bf0219effafed21dca23f"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json 0.61.1",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
@@ -1256,9 +1256,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.65.0"
|
||||
version = "1.66.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3ba2c5c0f2618937ce3d4a5ad574b86775576fa24006bcb3128c6e2cbf3c34e"
|
||||
checksum = "154488d16ab0d627d15ab2832b57e68a16684c8c902f14cb8a75ec933fc94852"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1267,7 +1267,7 @@ dependencies = [
|
||||
"aws-smithy-checksums",
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json 0.61.1",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
@@ -1290,15 +1290,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.50.0"
|
||||
version = "1.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab"
|
||||
checksum = "74995133da38f109a0eb8e8c886f9e80c713b6e9f2e6e5a6a1ba4450ce2ffc46"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json 0.61.1",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
@@ -1312,15 +1312,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.51.0"
|
||||
version = "1.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0"
|
||||
checksum = "e7062a779685cbf3b2401eb36151e2c6589fd5f3569b8a6bc2d199e5aaa1d059"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json 0.61.1",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
@@ -1334,15 +1334,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.51.0"
|
||||
version = "1.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf"
|
||||
checksum = "299dae7b1dc0ee50434453fa5a229dc4b22bd3ee50409ff16becf1f7346e0193"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json 0.61.1",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-query",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
@@ -1386,9 +1386,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-async"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c"
|
||||
checksum = "8aa8ff1492fd9fb99ae28e8467af0dbbb7c31512b16fabf1a0f10d7bb6ef78bb"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
@@ -1448,15 +1448,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-json"
|
||||
version = "0.60.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6"
|
||||
dependencies = [
|
||||
"aws-smithy-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-json"
|
||||
version = "0.61.1"
|
||||
@@ -1478,9 +1469,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime"
|
||||
version = "1.7.4"
|
||||
version = "1.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45"
|
||||
checksum = "431a10d0e07e09091284ef04453dae4069283aa108d209974d67e77ae1caa658"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
@@ -1493,7 +1484,7 @@ dependencies = [
|
||||
"http-body 0.4.6",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"hyper-rustls 0.24.2",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
@@ -1522,9 +1513,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.2.9"
|
||||
version = "1.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510"
|
||||
checksum = "8ecbf4d5dfb169812e2b240a4350f15ad3c6b03a54074e5712818801615f2dc5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes 1.9.0",
|
||||
@@ -1584,7 +1575,7 @@ dependencies = [
|
||||
"headers",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
@@ -1750,15 +1741,6 @@ dependencies = [
|
||||
"bit-vec 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
|
||||
dependencies = [
|
||||
"bit-vec 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -1774,12 +1756,6 @@ version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
@@ -1828,7 +1804,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
|
||||
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -1858,7 +1834,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
|
||||
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1868,7 +1844,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
|
||||
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -2115,6 +2091,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -2255,9 +2232,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.20.5"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0"
|
||||
checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"toml 0.8.19",
|
||||
@@ -2522,7 +2499,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-native-tls",
|
||||
"async-recursion 0.3.2",
|
||||
"async-tungstenite 0.28.1",
|
||||
"async-tungstenite 0.28.2",
|
||||
"chrono",
|
||||
"clock",
|
||||
"cocoa 0.26.0",
|
||||
@@ -2655,7 +2632,7 @@ dependencies = [
|
||||
"assistant_tool",
|
||||
"async-stripe",
|
||||
"async-trait",
|
||||
"async-tungstenite 0.28.1",
|
||||
"async-tungstenite 0.28.2",
|
||||
"audio",
|
||||
"aws-config",
|
||||
"aws-sdk-kinesis",
|
||||
@@ -2687,7 +2664,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"hex",
|
||||
"http_client",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"indoc",
|
||||
"jsonwebtoken",
|
||||
"language",
|
||||
@@ -2782,6 +2759,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
"telemetry",
|
||||
"theme",
|
||||
"time",
|
||||
"time_format",
|
||||
@@ -4410,6 +4388,7 @@ dependencies = [
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"telemetry",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -4426,12 +4405,13 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05"
|
||||
checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2"
|
||||
dependencies = [
|
||||
"bit-set 0.5.3",
|
||||
"regex",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4803,11 +4783,13 @@ dependencies = [
|
||||
"rope",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"text",
|
||||
"time",
|
||||
"util",
|
||||
"which 6.0.3",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
@@ -5608,9 +5590,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "heed"
|
||||
version = "0.20.5"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d4f449bab7320c56003d37732a917e18798e2f1709d80263face2b4f9436ddb"
|
||||
checksum = "bd54745cfacb7b97dee45e8fdb91814b62bccddb481debb7de0f9ee6b7bf5b43"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"byteorder",
|
||||
@@ -5633,9 +5615,9 @@ checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff"
|
||||
|
||||
[[package]]
|
||||
name = "heed-types"
|
||||
version = "0.20.1"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d3f528b053a6d700b2734eabcd0fd49cb8230647aa72958467527b0b7917114"
|
||||
checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder",
|
||||
@@ -5862,9 +5844,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.31"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes 1.9.0",
|
||||
"futures-channel",
|
||||
@@ -5877,7 +5859,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.8",
|
||||
"socket2 0.4.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -5912,7 +5894,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"log",
|
||||
"rustls 0.21.12",
|
||||
"rustls-native-certs 0.6.3",
|
||||
@@ -5945,7 +5927,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes 1.9.0",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
@@ -6623,7 +6605,7 @@ checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"async-tungstenite 0.28.1",
|
||||
"async-tungstenite 0.28.2",
|
||||
"futures 0.3.31",
|
||||
"jupyter-protocol",
|
||||
"serde",
|
||||
@@ -7000,7 +6982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7590,9 +7572,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
|
||||
version = "0.30.0"
|
||||
source = "git+https://github.com/gfx-rs/metal-rs?rev=ef768ff9d742ae6a0f4e83ddc8031264e7d460c4#ef768ff9d742ae6a0f4e83ddc8031264e7d460c4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
@@ -7702,13 +7683,11 @@ dependencies = [
|
||||
"ctor",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"language",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"settings",
|
||||
@@ -7733,12 +7712,11 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "22.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad"
|
||||
version = "23.0.0"
|
||||
source = "git+https://github.com/gfx-rs/wgpu?rev=1a643291c2e8854ba7e4f5445a4388202731bfa1#1a643291c2e8854ba7e4f5445a4388202731bfa1"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set 0.6.0",
|
||||
"bit-set 0.8.0",
|
||||
"bitflags 2.6.0",
|
||||
"cfg_aliases 0.1.1",
|
||||
"codespan-reporting",
|
||||
@@ -9607,6 +9585,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"terminal",
|
||||
"text",
|
||||
"toml 0.8.19",
|
||||
"unindent",
|
||||
"url",
|
||||
"util",
|
||||
@@ -10374,7 +10353,7 @@ dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
"async-tungstenite 0.28.1",
|
||||
"async-tungstenite 0.28.2",
|
||||
"base64 0.22.1",
|
||||
"client",
|
||||
"collections",
|
||||
@@ -10432,7 +10411,7 @@ dependencies = [
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
@@ -10677,7 +10656,7 @@ name = "rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tungstenite 0.28.1",
|
||||
"async-tungstenite 0.28.2",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"collections",
|
||||
@@ -11306,9 +11285,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -12877,7 +12856,6 @@ dependencies = [
|
||||
name = "theme_selector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
@@ -12885,6 +12863,7 @@ dependencies = [
|
||||
"picker",
|
||||
"serde",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -12955,16 +12934,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiktoken-rs"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c314e7ce51440f9e8f5a497394682a57b7c323d0f4d0a6b1b13c429056e0e234"
|
||||
checksum = "44075987ee2486402f0808505dd65692163d243a337fc54363d49afac41087f6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
"bstr",
|
||||
"fancy-regex 0.12.0",
|
||||
"fancy-regex 0.13.0",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
@@ -13114,6 +13094,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
"telemetry",
|
||||
"theme",
|
||||
"tree-sitter-md",
|
||||
"ui",
|
||||
@@ -13487,9 +13468,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-c"
|
||||
version = "0.23.2"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db56fadd8c3c6bc880dffcf1177c9d1c54a71a5207716db8660189082e63b587"
|
||||
checksum = "afd2b1bf1585dc2ef6d69e87d01db8adb059006649dd5f96f31aa789ee6e9c71"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -13604,9 +13585,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-json"
|
||||
version = "0.23.0"
|
||||
version = "0.24.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86a5d6b3ea17e06e7a34aabeadd68f5866c0d0f9359155d432095f8b751865e4"
|
||||
checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -14247,7 +14228,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.31",
|
||||
"hyper 0.14.32",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
@@ -14892,6 +14873,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"ui",
|
||||
"util",
|
||||
"vim_mode_setting",
|
||||
@@ -14997,7 +14979,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15998,7 +15980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.167.0"
|
||||
version = "0.168.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -16095,6 +16077,7 @@ dependencies = [
|
||||
"tab_switcher",
|
||||
"task",
|
||||
"tasks_ui",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
@@ -16131,7 +16114,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_astro"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.1.0",
|
||||
@@ -16160,7 +16143,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_elixir"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -16467,6 +16450,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"similar",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"theme",
|
||||
"tree-sitter-go",
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -355,13 +355,13 @@ async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
|
||||
blake3 = "1.5.3"
|
||||
bytes = "1.0"
|
||||
cargo_metadata = "0.19"
|
||||
cargo_toml = "0.20"
|
||||
cargo_toml = "0.21"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
cocoa = "0.26"
|
||||
@@ -385,7 +385,7 @@ futures-lite = "1.13"
|
||||
git2 = { version = "0.19", default-features = false }
|
||||
globset = "0.4"
|
||||
handlebars = "4.3"
|
||||
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
html5ever = "0.27.0"
|
||||
hyper = "0.14"
|
||||
@@ -472,7 +472,7 @@ sys-locale = "0.3.1"
|
||||
sysinfo = "0.31.0"
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.9"
|
||||
tiktoken-rs = "0.6.0"
|
||||
time = { version = "0.3", features = [
|
||||
"macros",
|
||||
"parsing",
|
||||
@@ -498,7 +498,7 @@ tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex",
|
||||
tree-sitter-diff = "0.1.0"
|
||||
tree-sitter-html = "0.20"
|
||||
tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.23"
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-regex = "0.23"
|
||||
@@ -525,6 +525,12 @@ wasmtime-wasi = "24"
|
||||
which = "6.0.0"
|
||||
wit-component = "0.201"
|
||||
zstd = "0.11"
|
||||
# Custom metal-rs is only needed for "macos-blade" feature of GPUI
|
||||
#TODO: switch to crates once these are published:
|
||||
# - https://github.com/gfx-rs/metal-rs/pull/335
|
||||
# - https://github.com/gfx-rs/metal-rs/pull/336
|
||||
# - https://github.com/gfx-rs/metal-rs/pull/337
|
||||
metal = { git = "https://github.com/gfx-rs/metal-rs", rev = "ef768ff9d742ae6a0f4e83ddc8031264e7d460c4" }
|
||||
|
||||
[workspace.dependencies.async-stripe]
|
||||
git = "https://github.com/zed-industries/async-stripe"
|
||||
|
||||
5
assets/icons/zed_assistant_2.svg
Normal file
5
assets/icons/zed_assistant_2.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.27772 1.38585L4.39187 4.07909C4.34653 4.21692 4.26946 4.34219 4.16685 4.44479C4.06425 4.5474 3.93898 4.62447 3.80115 4.66981L1.10791 5.55566L3.80115 6.44151C3.93898 6.48685 4.06425 6.56392 4.16685 6.66653C4.26946 6.76913 4.34653 6.8944 4.39187 7.03223L5.27772 9.72547L6.16357 7.03223C6.20891 6.8944 6.28598 6.76913 6.38859 6.66653C6.49119 6.56392 6.61646 6.48685 6.7543 6.44151L9.44753 5.55566L6.7543 4.66981C6.61646 4.62447 6.49119 4.5474 6.38859 4.44479C6.28598 4.34219 6.20891 4.21692 6.16357 4.07909L5.27772 1.38585Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.35938 12.3555C8.35938 12.0664 8.52734 11.8086 9.00781 11.3594L10.2031 10.2344C10.6094 9.85156 10.7891 9.60156 10.7891 9.34375C10.7891 9.05469 10.5781 8.85938 10.2734 8.85938C10.0391 8.85938 9.87109 8.95312 9.66406 9.21094C9.42578 9.50781 9.25391 9.60938 8.99219 9.60938C8.61719 9.60938 8.35156 9.35938 8.35156 9.01172C8.35156 8.25 9.26953 7.57812 10.3594 7.57812C11.4961 7.57812 12.3438 8.26172 12.3438 9.17969C12.3438 9.75391 12.0391 10.3008 11.418 10.8516L10.4961 11.6719V11.7344H11.8047C12.2578 11.7344 12.5391 11.9766 12.5391 12.3711C12.5391 12.7656 12.2656 13 11.8047 13H9.08203C8.65234 13 8.35938 12.7383 8.35938 12.3555Z" fill="black"/>
|
||||
<path d="M11.0834 1.38585V3.71918M9.91675 2.55248H12.2501" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -230,8 +230,8 @@
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode",
|
||||
"[ x": "vim::SelectLargerSyntaxNode",
|
||||
"] x": "vim::SelectSmallerSyntaxNode",
|
||||
"] d": "editor::GoToDiagnostic",
|
||||
"[ d": "editor::GoToPrevDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
|
||||
@@ -160,6 +160,9 @@
|
||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
"show_signature_help_after_edits": false,
|
||||
/// Whether to show the inline completions next to the completions provided by a language server.
|
||||
/// Only has an effect if inline completion provider supports it.
|
||||
"show_inline_completions_in_menu": true,
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||
@@ -478,8 +481,10 @@
|
||||
"default_width": 240
|
||||
},
|
||||
"chat_panel": {
|
||||
// Whether to show the chat panel button in the status bar.
|
||||
"button": true,
|
||||
// When to show the chat panel button in the status bar.
|
||||
// Can be 'never', 'always', or 'when_in_call',
|
||||
// or a boolean (interpreted as 'never'/'always').
|
||||
"button": "when_in_call",
|
||||
// Where to the chat panel. Can be 'left' or 'right'.
|
||||
"dock": "right",
|
||||
// Default width of the chat panel.
|
||||
|
||||
@@ -22,6 +22,7 @@ use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use rpc::AnyProtoClient;
|
||||
use std::sync::LazyLock;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
ffi::OsStr,
|
||||
@@ -753,8 +754,8 @@ impl ContextStore {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pattern = r" - \d+.zed.json$";
|
||||
let re = Regex::new(pattern).unwrap();
|
||||
static ASSISTANT_CONTEXT_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r" - \d+.zed.json$").unwrap());
|
||||
|
||||
let metadata = fs.metadata(&path).await?;
|
||||
if let Some((file_name, metadata)) = path
|
||||
@@ -763,11 +764,15 @@ impl ContextStore {
|
||||
.zip(metadata)
|
||||
{
|
||||
// This is used to filter out contexts saved by the new assistant.
|
||||
if !re.is_match(file_name) {
|
||||
if !ASSISTANT_CONTEXT_REGEX.is_match(file_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(title) = re.replace(file_name, "").lines().next() {
|
||||
if let Some(title) = ASSISTANT_CONTEXT_REGEX
|
||||
.replace(file_name, "")
|
||||
.lines()
|
||||
.next()
|
||||
{
|
||||
contexts.push(SavedContextMetadata {
|
||||
title: title.to_string(),
|
||||
path,
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
mod active_thread;
|
||||
mod assistant_panel;
|
||||
mod assistant_settings;
|
||||
mod buffer_codegen;
|
||||
mod context;
|
||||
mod context_picker;
|
||||
mod context_store;
|
||||
mod context_strip;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod message_editor;
|
||||
mod prompts;
|
||||
mod streaming_diff;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
mod thread_history;
|
||||
|
||||
@@ -27,9 +27,22 @@ use crate::{NewThread, OpenHistory, ToggleFocus};
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
});
|
||||
workspace
|
||||
.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
})
|
||||
.register_action(|workspace, _: &NewThread, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| panel.new_thread(cx));
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenHistory, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_history(cx));
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
@@ -157,6 +170,12 @@ impl AssistantPanel {
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.active_view = ActiveView::History;
|
||||
self.history.focus_handle(cx).focus(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
|
||||
let Some(thread) = self
|
||||
.thread_store
|
||||
@@ -257,7 +276,7 @@ impl Panel for AssistantPanel {
|
||||
}
|
||||
|
||||
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
|
||||
Some(IconName::ZedAssistant)
|
||||
Some(IconName::ZedAssistant2)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
@@ -572,9 +591,7 @@ impl Render for AssistantPanel {
|
||||
this.new_thread(cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
|
||||
this.active_view = ActiveView::History;
|
||||
this.history.focus_handle(cx).focus(cx);
|
||||
cx.notify();
|
||||
this.open_history(cx);
|
||||
}))
|
||||
.child(self.render_toolbar(cx))
|
||||
.map(|parent| match self.active_view {
|
||||
|
||||
1475
crates/assistant2/src/buffer_codegen.rs
Normal file
1475
crates/assistant2/src/buffer_codegen.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,12 @@ use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ConfirmBehavior {
|
||||
KeepOpen,
|
||||
Close,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ContextPickerMode {
|
||||
Default,
|
||||
@@ -41,6 +47,7 @@ impl ContextPicker {
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let mut entries = vec![
|
||||
@@ -74,6 +81,7 @@ impl ContextPicker {
|
||||
workspace,
|
||||
thread_store,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
entries,
|
||||
selected_ix: 0,
|
||||
};
|
||||
@@ -136,6 +144,7 @@ pub(crate) struct ContextPickerDelegate {
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
entries: Vec<ContextPickerEntry>,
|
||||
selected_ix: usize,
|
||||
}
|
||||
@@ -175,6 +184,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
@@ -185,6 +195,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
@@ -195,6 +206,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
@@ -206,6 +218,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
thread_store.clone(),
|
||||
self.context_picker.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
||||
@@ -11,7 +11,7 @@ use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct DirectoryContextPicker {
|
||||
@@ -23,10 +23,15 @@ impl DirectoryContextPicker {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate =
|
||||
DirectoryContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||
let delegate = DirectoryContextPickerDelegate::new(
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
@@ -49,6 +54,7 @@ pub struct DirectoryContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<PathMatch>,
|
||||
selected_index: usize,
|
||||
}
|
||||
@@ -58,11 +64,13 @@ impl DirectoryContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
@@ -93,8 +101,12 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, _cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
// TODO: Implement this once we fix the issues with the file context picker.
|
||||
match self.confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => self.dismissed(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
|
||||
@@ -12,7 +12,7 @@ use ui::{prelude::*, ListItem, ViewContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FetchContextPicker {
|
||||
@@ -24,9 +24,15 @@ impl FetchContextPicker {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||
let delegate = FetchContextPickerDelegate::new(
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
@@ -56,6 +62,7 @@ pub struct FetchContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
url: String,
|
||||
}
|
||||
|
||||
@@ -64,11 +71,13 @@ impl FetchContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
FetchContextPickerDelegate {
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
url: String::new(),
|
||||
}
|
||||
}
|
||||
@@ -184,6 +193,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
|
||||
let http_client = workspace.read(cx).client().http_client().clone();
|
||||
let url = self.url.clone();
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let text = Self::build_message(http_client, &url).await?;
|
||||
|
||||
@@ -192,7 +202,14 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
context_store.insert_context(ContextKind::FetchedUrl, url, text);
|
||||
})
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -13,7 +13,7 @@ use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FileContextPicker {
|
||||
@@ -25,9 +25,15 @@ impl FileContextPicker {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_store);
|
||||
let delegate = FileContextPickerDelegate::new(
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
@@ -50,6 +56,7 @@ pub struct FileContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<PathMatch>,
|
||||
selected_index: usize,
|
||||
}
|
||||
@@ -59,11 +66,13 @@ impl FileContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_picker,
|
||||
workspace,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
@@ -79,44 +88,37 @@ impl FileContextPickerDelegate {
|
||||
if query.is_empty() {
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let entries = workspace.recent_navigation_history(Some(10), cx);
|
||||
|
||||
let entries = entries
|
||||
let recent_matches = workspace
|
||||
.recent_navigation_history(Some(10), cx)
|
||||
.into_iter()
|
||||
.map(|entries| entries.0)
|
||||
.chain(project.worktrees(cx).flat_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let id = worktree.id();
|
||||
worktree
|
||||
.child_entries(Path::new(""))
|
||||
.filter(|entry| entry.kind.is_file())
|
||||
.map(move |entry| project::ProjectPath {
|
||||
worktree_id: id,
|
||||
path: entry.path.clone(),
|
||||
})
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let path_prefix: Arc<str> = Arc::default();
|
||||
Task::ready(
|
||||
entries
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let worktree = project.worktree_for_id(entry.worktree_id, cx)?;
|
||||
let mut full_path = PathBuf::from(worktree.read(cx).root_name());
|
||||
full_path.push(&entry.path);
|
||||
Some(PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: entry.worktree_id.to_usize(),
|
||||
path: full_path.into(),
|
||||
path_prefix: path_prefix.clone(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
})
|
||||
.filter_map(|(project_path, _)| {
|
||||
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
Some(PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: project_path.worktree_id.to_usize(),
|
||||
path: project_path.path,
|
||||
path_prefix: worktree.read(cx).root_name().into(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
});
|
||||
|
||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
||||
worktree.files(true, 0).map(move |entry| PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree.id().to_usize(),
|
||||
path: entry.path.clone(),
|
||||
path_prefix: path_prefix.clone(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
})
|
||||
});
|
||||
|
||||
Task::ready(recent_matches.chain(file_matches).collect())
|
||||
} else {
|
||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
||||
let candidate_sets = worktrees
|
||||
@@ -201,6 +203,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
};
|
||||
let path = mat.path.clone();
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let Some(open_buffer_task) = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
@@ -214,22 +217,31 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.context_store.update(cx, |context_store, cx| {
|
||||
let mut text = String::new();
|
||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
||||
text.push_str(&buffer.read(cx).text());
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
let mut text = String::new();
|
||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
||||
text.push_str(&buffer.read(cx).text());
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
text.push_str("```\n");
|
||||
text.push_str("```\n");
|
||||
|
||||
context_store.insert_context(
|
||||
ContextKind::File,
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
);
|
||||
})
|
||||
context_store.insert_context(
|
||||
ContextKind::File,
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
);
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
@@ -253,16 +265,30 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
let file_name = path_match
|
||||
.path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let directory = path_match
|
||||
.path
|
||||
.parent()
|
||||
.map(|directory| format!("{}/", directory.to_string_lossy()));
|
||||
|
||||
let (file_name, directory) = if path_match.path.as_ref() == Path::new("") {
|
||||
(SharedString::from(path_match.path_prefix.clone()), None)
|
||||
} else {
|
||||
let file_name = path_match
|
||||
.path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
let mut directory = format!("{}/", path_match.path_prefix);
|
||||
if let Some(parent) = path_match
|
||||
.path
|
||||
.parent()
|
||||
.filter(|parent| parent != &Path::new(""))
|
||||
{
|
||||
directory.push_str(&parent.to_string_lossy());
|
||||
directory.push('/');
|
||||
}
|
||||
|
||||
(file_name, Some(directory))
|
||||
};
|
||||
|
||||
Some(
|
||||
ListItem::new(ix).inset(true).toggle_state(selected).child(
|
||||
|
||||
@@ -6,7 +6,7 @@ use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem};
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store;
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::ThreadStore;
|
||||
@@ -20,10 +20,15 @@ impl ThreadContextPicker {
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate =
|
||||
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
|
||||
let delegate = ThreadContextPickerDelegate::new(
|
||||
thread_store,
|
||||
context_picker,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
ThreadContextPicker { picker }
|
||||
@@ -52,6 +57,7 @@ pub struct ThreadContextPickerDelegate {
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<ThreadContextEntry>,
|
||||
selected_index: usize,
|
||||
}
|
||||
@@ -61,11 +67,13 @@ impl ThreadContextPickerDelegate {
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
ThreadContextPickerDelegate {
|
||||
thread_store,
|
||||
context_picker,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
}
|
||||
@@ -180,6 +188,11 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
context_store.insert_context(ContextKind::Thread, entry.summary.clone(), text);
|
||||
})
|
||||
.ok();
|
||||
|
||||
match self.confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => self.dismissed(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
|
||||
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::ContextPill;
|
||||
@@ -33,6 +33,7 @@ impl ContextStrip {
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
context_store.downgrade(),
|
||||
ConfirmBehavior::KeepOpen,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1127
crates/assistant2/src/inline_prompt_editor.rs
Normal file
1127
crates/assistant2/src/inline_prompt_editor.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,24 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakModel, WeakView};
|
||||
use gpui::{
|
||||
AppContext, DismissEvent, FocusableView, Model, Subscription, TextStyle, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use rope::Point;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
||||
Tooltip,
|
||||
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenu,
|
||||
PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
@@ -27,9 +31,12 @@ pub struct MessageEditor {
|
||||
context_store: Model<ContextStore>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
inline_context_picker: View<ContextPicker>,
|
||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
use_tools: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl MessageEditor {
|
||||
@@ -42,14 +49,31 @@ impl MessageEditor {
|
||||
) -> Self {
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(80, cx);
|
||||
let mut editor = Editor::auto_height(10, cx);
|
||||
editor.set_placeholder_text("Ask anything, @ to add context", cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
|
||||
editor
|
||||
});
|
||||
let inline_context_picker = cx.new_view(|cx| {
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
Some(thread_store.clone()),
|
||||
context_store.downgrade(),
|
||||
ConfirmBehavior::Close,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let subscriptions = vec![
|
||||
cx.subscribe(&editor, Self::handle_editor_event),
|
||||
cx.subscribe(
|
||||
&inline_context_picker,
|
||||
Self::handle_inline_context_picker_event,
|
||||
),
|
||||
];
|
||||
|
||||
Self {
|
||||
thread,
|
||||
@@ -66,6 +90,8 @@ impl MessageEditor {
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
@@ -81,6 +107,7 @@ impl MessageEditor {
|
||||
}),
|
||||
language_model_selector_menu_handle: PopoverMenuHandle::default(),
|
||||
use_tools: false,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +170,40 @@ impl MessageEditor {
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_editor_event(
|
||||
&mut self,
|
||||
editor: View<Editor>,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
||||
if newest_cursor.column > 0 {
|
||||
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
|
||||
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
|
||||
if char_behind_cursor == Some('@') {
|
||||
self.inline_context_picker_menu_handle.show(cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_inline_context_picker_event(
|
||||
&mut self,
|
||||
_inline_context_picker: View<ContextPicker>,
|
||||
_event: &DismissEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
|
||||
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.language_model_selector.focus_handle(cx).clone();
|
||||
@@ -199,6 +260,7 @@ impl Render for MessageEditor {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let inline_context_picker = self.inline_context_picker.clone();
|
||||
let bg_color = cx.theme().colors().editor_background;
|
||||
|
||||
v_flex()
|
||||
@@ -211,7 +273,7 @@ impl Render for MessageEditor {
|
||||
.p_2()
|
||||
.bg(bg_color)
|
||||
.child(self.context_strip.clone())
|
||||
.child(div().id("thread_editor").overflow_y_scroll().h_12().child({
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
@@ -232,7 +294,18 @@ impl Render for MessageEditor {
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}))
|
||||
})
|
||||
.child(
|
||||
PopoverMenu::new("inline-context-picker")
|
||||
.menu(move |_cx| Some(inline_context_picker.clone()))
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
|
||||
192
crates/assistant2/src/terminal_codegen.rs
Normal file
192
crates/assistant2/src/terminal_codegen.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use client::telemetry::Telemetry;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui::{AppContext, EventEmitter, Model, ModelContext, Task};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequest};
|
||||
use language_models::report_assistant_event;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
|
||||
pub struct TerminalCodegen {
|
||||
pub status: CodegenStatus,
|
||||
pub telemetry: Option<Arc<Telemetry>>,
|
||||
terminal: Model<Terminal>,
|
||||
generation: Task<()>,
|
||||
pub message_id: Option<String>,
|
||||
transaction: Option<TerminalTransaction>,
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
|
||||
|
||||
impl TerminalCodegen {
|
||||
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
|
||||
Self {
|
||||
terminal,
|
||||
telemetry,
|
||||
status: CodegenStatus::Idle,
|
||||
generation: Task::ready(()),
|
||||
message_id: None,
|
||||
transaction: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let model_api_key = model.api_key(cx);
|
||||
let http_client = cx.http_client();
|
||||
let telemetry = self.telemetry.clone();
|
||||
self.status = CodegenStatus::Pending;
|
||||
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
||||
self.generation = cx.spawn(|this, mut cx| async move {
|
||||
let model_telemetry_id = model.telemetry_id();
|
||||
let model_provider_id = model.provider_id();
|
||||
let response = model.stream_completion_text(prompt, &cx).await;
|
||||
let generate = async {
|
||||
let message_id = response
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|response| response.message_id.clone());
|
||||
|
||||
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
|
||||
|
||||
let task = cx.background_executor().spawn({
|
||||
let message_id = message_id.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
async move {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
let task = async {
|
||||
let mut chunks = response?.stream;
|
||||
while let Some(chunk) = chunks.next().await {
|
||||
if response_latency.is_none() {
|
||||
response_latency = Some(request_start.elapsed());
|
||||
}
|
||||
let chunk = chunk?;
|
||||
hunks_tx.send(chunk).await?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let result = task.await;
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id,
|
||||
phase: AssistantPhase::Response,
|
||||
model: model_telemetry_id,
|
||||
model_provider: model_provider_id.to_string(),
|
||||
response_latency,
|
||||
error_message,
|
||||
language_name: None,
|
||||
},
|
||||
telemetry,
|
||||
http_client,
|
||||
model_api_key,
|
||||
&executor,
|
||||
);
|
||||
|
||||
result?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.message_id = message_id;
|
||||
})?;
|
||||
|
||||
while let Some(hunk) = hunks_rx.next().await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(transaction) = &mut this.transaction {
|
||||
transaction.push(hunk, cx);
|
||||
cx.notify();
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
task.await?;
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let result = generate.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Err(error) = result {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
} else {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.status = CodegenStatus::Done;
|
||||
self.generation = Task::ready(());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(transaction) = self.transaction.take() {
|
||||
transaction.complete(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(transaction) = self.transaction.take() {
|
||||
transaction.undo(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CodegenEvent {
|
||||
Finished,
|
||||
}
|
||||
|
||||
pub const CLEAR_INPUT: &str = "\x15";
|
||||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
terminal: Model<Terminal>,
|
||||
}
|
||||
|
||||
impl TerminalTransaction {
|
||||
pub fn start(terminal: Model<Terminal>) -> Self {
|
||||
Self { terminal }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
|
||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||
let input = Self::sanitize_input(hunk);
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(input));
|
||||
}
|
||||
|
||||
pub fn undo(&self, cx: &mut AppContext) {
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
||||
}
|
||||
|
||||
pub fn complete(&self, cx: &mut AppContext) {
|
||||
self.terminal.update(cx, |terminal, _| {
|
||||
terminal.input(CARRIAGE_RETURN.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
fn sanitize_input(input: String) -> String {
|
||||
input.replace(['\r', '\n'], "")
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,29 @@
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::context::attach_context_to_message;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::inline_prompt_editor::{
|
||||
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
||||
};
|
||||
use crate::prompts::PromptBuilder;
|
||||
use crate::terminal_codegen::{CodegenEvent, TerminalCodegen, CLEAR_INPUT};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ToggleContextPicker;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{
|
||||
actions::{MoveDown, MoveUp, SelectAll},
|
||||
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
};
|
||||
use editor::{actions::SelectAll, MultiBuffer};
|
||||
use fs::Fs;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui::{
|
||||
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
|
||||
Subscription, Task, TextStyle, UpdateGlobal, View, WeakModel, WeakView,
|
||||
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_models::report_assistant_event;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::{cmp, sync::Arc, time::Instant};
|
||||
use std::sync::Arc;
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, text_for_action, IconButtonShape, PopoverMenuHandle, Tooltip};
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::{notifications::NotificationId, Toast, Workspace};
|
||||
|
||||
@@ -47,17 +39,6 @@ pub fn init(
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
const PROMPT_HISTORY_MAX_LEN: usize = 20;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
|
||||
struct TerminalInlineAssistId(usize);
|
||||
|
||||
impl TerminalInlineAssistId {
|
||||
fn post_inc(&mut self) -> TerminalInlineAssistId {
|
||||
let id = *self;
|
||||
self.0 += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TerminalInlineAssistant {
|
||||
next_assist_id: TerminalInlineAssistId,
|
||||
assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>,
|
||||
@@ -98,10 +79,10 @@ impl TerminalInlineAssistant {
|
||||
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
|
||||
});
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
|
||||
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
PromptEditor::new(
|
||||
PromptEditor::new_terminal(
|
||||
assist_id,
|
||||
self.prompt_history.clone(),
|
||||
prompt_buffer.clone(),
|
||||
@@ -150,11 +131,11 @@ impl TerminalInlineAssistant {
|
||||
|
||||
fn handle_prompt_editor_event(
|
||||
&mut self,
|
||||
prompt_editor: View<PromptEditor>,
|
||||
prompt_editor: View<PromptEditor<TerminalCodegen>>,
|
||||
event: &PromptEditorEvent,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let assist_id = prompt_editor.read(cx).id;
|
||||
let assist_id = prompt_editor.read(cx).id();
|
||||
match event {
|
||||
PromptEditorEvent::StartRequested => {
|
||||
self.start_assist(assist_id, cx);
|
||||
@@ -380,8 +361,8 @@ impl TerminalInlineAssistant {
|
||||
|
||||
struct TerminalInlineAssist {
|
||||
terminal: WeakView<TerminalView>,
|
||||
prompt_editor: Option<View<PromptEditor>>,
|
||||
codegen: Model<Codegen>,
|
||||
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
|
||||
codegen: Model<TerminalCodegen>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: Model<ContextStore>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
@@ -391,12 +372,12 @@ impl TerminalInlineAssist {
|
||||
pub fn new(
|
||||
assist_id: TerminalInlineAssistId,
|
||||
terminal: &View<TerminalView>,
|
||||
prompt_editor: View<PromptEditor>,
|
||||
prompt_editor: View<PromptEditor<TerminalCodegen>>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: Model<ContextStore>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
let codegen = prompt_editor.read(cx).codegen.clone();
|
||||
let codegen = prompt_editor.read(cx).codegen().clone();
|
||||
Self {
|
||||
terminal: terminal.downgrade(),
|
||||
prompt_editor: Some(prompt_editor.clone()),
|
||||
@@ -447,660 +428,3 @@ impl TerminalInlineAssist {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PromptEditorEvent {
|
||||
StartRequested,
|
||||
StopRequested,
|
||||
ConfirmRequested { execute: bool },
|
||||
CancelRequested,
|
||||
DismissRequested,
|
||||
Resized { height_in_lines: u8 },
|
||||
}
|
||||
|
||||
struct PromptEditor {
|
||||
id: TerminalInlineAssistId,
|
||||
height_in_lines: u8,
|
||||
editor: View<Editor>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_history_ix: Option<usize>,
|
||||
pending_prompt: String,
|
||||
codegen: Model<Codegen>,
|
||||
_codegen_subscription: Subscription,
|
||||
editor_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let mut buttons = Vec::new();
|
||||
|
||||
buttons.extend(match status {
|
||||
CodegenStatus::Idle => vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element(),
|
||||
IconButton::new("start", IconName::SparkleAlt)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
|
||||
.into_any_element(),
|
||||
],
|
||||
CodegenStatus::Pending => vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element(),
|
||||
IconButton::new("stop", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Interrupt Generation",
|
||||
Some(&menu::Cancel),
|
||||
"Changes won't be discarded",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||
.into_any_element(),
|
||||
],
|
||||
CodegenStatus::Error(_) | CodegenStatus::Done => {
|
||||
let cancel = IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element();
|
||||
|
||||
let has_error = matches!(status, CodegenStatus::Error(_));
|
||||
if has_error || self.edited_since_done {
|
||||
vec![
|
||||
cancel,
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Restart Generation",
|
||||
Some(&menu::Confirm),
|
||||
"Changes will be discarded",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}))
|
||||
.into_any_element(),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
cancel,
|
||||
IconButton::new("accept", IconName::Check)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
}))
|
||||
.into_any_element(),
|
||||
IconButton::new("confirm", IconName::Play)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Execute Generated Command",
|
||||
&menu::SecondaryConfirm,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
||||
}))
|
||||
.into_any_element(),
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.py_2()
|
||||
.size_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::secondary_confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_12()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
Some(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(error_message.clone(), cx)
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(div().flex_1().child(self.render_prompt_editor(cx)))
|
||||
.child(h_flex().gap_1().pr_4().children(buttons)),
|
||||
)
|
||||
.child(h_flex().child(self.context_strip.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for PromptEditor {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl PromptEditor {
|
||||
const MAX_LINES: u8 = 8;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
id: TerminalInlineAssistId,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_buffer: Model<MultiBuffer>,
|
||||
codegen: Model<Codegen>,
|
||||
fs: Arc<dyn Fs>,
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
},
|
||||
prompt_buffer,
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let mut this = Self {
|
||||
id,
|
||||
height_in_lines: 1,
|
||||
editor: prompt_editor.clone(),
|
||||
context_strip: cx.new_view(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store,
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
pending_prompt: String::new(),
|
||||
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
|
||||
editor_subscriptions: Vec::new(),
|
||||
codegen,
|
||||
};
|
||||
this.count_lines(cx);
|
||||
this.subscribe_to_editor(cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn placeholder_text(cx: &WindowContext) -> String {
|
||||
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
|
||||
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||
.unwrap_or_default();
|
||||
|
||||
format!("Generate…{context_keybinding} ↓↑ for history")
|
||||
}
|
||||
|
||||
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor_subscriptions.clear();
|
||||
self.editor_subscriptions
|
||||
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
|
||||
self.editor_subscriptions
|
||||
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
|
||||
}
|
||||
|
||||
fn prompt(&self, cx: &AppContext) -> String {
|
||||
self.editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let height_in_lines = cmp::max(
|
||||
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
||||
cmp::min(
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1),
|
||||
Self::MAX_LINES as u32,
|
||||
),
|
||||
) as u8;
|
||||
|
||||
if height_in_lines != self.height_in_lines {
|
||||
self.height_in_lines = height_in_lines;
|
||||
cx.emit(PromptEditorEvent::Resized { height_in_lines });
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||
self.count_lines(cx);
|
||||
}
|
||||
|
||||
fn handle_prompt_editor_events(
|
||||
&mut self,
|
||||
_: View<Editor>,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } => {
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
if self
|
||||
.prompt_history_ix
|
||||
.map_or(true, |ix| self.prompt_history[ix] != prompt)
|
||||
{
|
||||
self.prompt_history_ix.take();
|
||||
self.pending_prompt = prompt;
|
||||
}
|
||||
|
||||
self.edited_since_done = true;
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(true));
|
||||
}
|
||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
self.edited_since_done = false;
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
cx.emit(PromptEditorEvent::CancelRequested);
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
cx.emit(PromptEditorEvent::StopRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
if !self.editor.read(cx).text(cx).trim().is_empty() {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
cx.emit(PromptEditorEvent::DismissRequested);
|
||||
}
|
||||
CodegenStatus::Done => {
|
||||
if self.edited_since_done {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
} else {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
}
|
||||
}
|
||||
CodegenStatus::Error(_) => {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
|
||||
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
||||
}
|
||||
}
|
||||
|
||||
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.prompt_history_ix {
|
||||
if ix > 0 {
|
||||
self.prompt_history_ix = Some(ix - 1);
|
||||
let prompt = self.prompt_history[ix - 1].as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_beginning(&Default::default(), cx);
|
||||
});
|
||||
}
|
||||
} else if !self.prompt_history.is_empty() {
|
||||
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
||||
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_beginning(&Default::default(), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.prompt_history_ix {
|
||||
if ix < self.prompt_history.len() - 1 {
|
||||
self.prompt_history_ix = Some(ix + 1);
|
||||
let prompt = self.prompt_history[ix + 1].as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
});
|
||||
} else {
|
||||
self.prompt_history_ix = None;
|
||||
let prompt = self.pending_prompt.as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if self.editor.read(cx).read_only(cx) {
|
||||
cx.theme().colors().text_disabled
|
||||
} else {
|
||||
cx.theme().colors().text
|
||||
},
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CodegenEvent {
|
||||
Finished,
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for Codegen {}
|
||||
|
||||
const CLEAR_INPUT: &str = "\x15";
|
||||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
terminal: Model<Terminal>,
|
||||
}
|
||||
|
||||
impl TerminalTransaction {
|
||||
pub fn start(terminal: Model<Terminal>) -> Self {
|
||||
Self { terminal }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
|
||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||
let input = Self::sanitize_input(hunk);
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(input));
|
||||
}
|
||||
|
||||
pub fn undo(&self, cx: &mut AppContext) {
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
||||
}
|
||||
|
||||
pub fn complete(&self, cx: &mut AppContext) {
|
||||
self.terminal.update(cx, |terminal, _| {
|
||||
terminal.input(CARRIAGE_RETURN.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
fn sanitize_input(input: String) -> String {
|
||||
input.replace(['\r', '\n'], "")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Codegen {
|
||||
status: CodegenStatus,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
terminal: Model<Terminal>,
|
||||
generation: Task<()>,
|
||||
message_id: Option<String>,
|
||||
transaction: Option<TerminalTransaction>,
|
||||
}
|
||||
|
||||
impl Codegen {
|
||||
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
|
||||
Self {
|
||||
terminal,
|
||||
telemetry,
|
||||
status: CodegenStatus::Idle,
|
||||
generation: Task::ready(()),
|
||||
message_id: None,
|
||||
transaction: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let model_api_key = model.api_key(cx);
|
||||
let http_client = cx.http_client();
|
||||
let telemetry = self.telemetry.clone();
|
||||
self.status = CodegenStatus::Pending;
|
||||
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
||||
self.generation = cx.spawn(|this, mut cx| async move {
|
||||
let model_telemetry_id = model.telemetry_id();
|
||||
let model_provider_id = model.provider_id();
|
||||
let response = model.stream_completion_text(prompt, &cx).await;
|
||||
let generate = async {
|
||||
let message_id = response
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|response| response.message_id.clone());
|
||||
|
||||
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
|
||||
|
||||
let task = cx.background_executor().spawn({
|
||||
let message_id = message_id.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
async move {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
let task = async {
|
||||
let mut chunks = response?.stream;
|
||||
while let Some(chunk) = chunks.next().await {
|
||||
if response_latency.is_none() {
|
||||
response_latency = Some(request_start.elapsed());
|
||||
}
|
||||
let chunk = chunk?;
|
||||
hunks_tx.send(chunk).await?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let result = task.await;
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
report_assistant_event(
|
||||
AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id,
|
||||
phase: AssistantPhase::Response,
|
||||
model: model_telemetry_id,
|
||||
model_provider: model_provider_id.to_string(),
|
||||
response_latency,
|
||||
error_message,
|
||||
language_name: None,
|
||||
},
|
||||
telemetry,
|
||||
http_client,
|
||||
model_api_key,
|
||||
&executor,
|
||||
);
|
||||
|
||||
result?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.message_id = message_id;
|
||||
})?;
|
||||
|
||||
while let Some(hunk) = hunks_rx.next().await {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(transaction) = &mut this.transaction {
|
||||
transaction.push(hunk, cx);
|
||||
cx.notify();
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
task.await?;
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let result = generate.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Err(error) = result {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
} else {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.status = CodegenStatus::Done;
|
||||
self.generation = Task::ready(());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(transaction) = self.transaction.take() {
|
||||
transaction.complete(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(transaction) = self.transaction.take() {
|
||||
transaction.undo(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CodegenStatus {
|
||||
Idle,
|
||||
Pending,
|
||||
Done,
|
||||
Error(anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings.workspace = true
|
||||
telemetry.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
||||
@@ -250,7 +250,9 @@ impl ActiveCall {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = invite.await;
|
||||
if result.is_ok() {
|
||||
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("Participant Invited", cx)
|
||||
})?;
|
||||
} else {
|
||||
//TODO: report collaboration error
|
||||
log::error!("invite failed: {:?}", result);
|
||||
@@ -318,7 +320,7 @@ impl ActiveCall {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("accept incoming", cx)
|
||||
this.report_call_event("Incoming Call Accepted", cx)
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -331,7 +333,7 @@ impl ActiveCall {
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.ok_or_else(|| anyhow!("no incoming call"))?;
|
||||
report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
|
||||
telemetry::event!("Incoming Call Declined", room_id = call.room_id);
|
||||
self.client.send(proto::DeclineCall {
|
||||
room_id: call.room_id,
|
||||
})?;
|
||||
@@ -366,7 +368,7 @@ impl ActiveCall {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("join channel", cx)
|
||||
this.report_call_event("Channel Joined", cx)
|
||||
})?;
|
||||
Ok(room)
|
||||
})
|
||||
@@ -374,7 +376,7 @@ impl ActiveCall {
|
||||
|
||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.report_call_event("hang up", cx);
|
||||
self.report_call_event("Call Ended", cx);
|
||||
|
||||
Audio::end_call(cx);
|
||||
|
||||
@@ -393,7 +395,7 @@ impl ActiveCall {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("share project", cx);
|
||||
self.report_call_event("Project Shared", cx);
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
@@ -406,7 +408,7 @@ impl ActiveCall {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("unshare project", cx);
|
||||
self.report_call_event("Project Unshared", cx);
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
} else {
|
||||
Err(anyhow!("no active call"))
|
||||
@@ -486,35 +488,15 @@ impl ActiveCall {
|
||||
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
|
||||
if let Some(room) = self.room() {
|
||||
let room = room.read(cx);
|
||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
|
||||
telemetry::event!(
|
||||
operation,
|
||||
room_id = room.id(),
|
||||
channel_id = room.channel_id()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_call_event_for_room(
|
||||
operation: &'static str,
|
||||
room_id: u64,
|
||||
channel_id: Option<ChannelId>,
|
||||
client: &Arc<Client>,
|
||||
) {
|
||||
let telemetry = client.telemetry();
|
||||
|
||||
telemetry.report_call_event(operation, Some(room_id), channel_id)
|
||||
}
|
||||
|
||||
pub fn report_call_event_for_channel(
|
||||
operation: &'static str,
|
||||
channel_id: ChannelId,
|
||||
client: &Arc<Client>,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
let room = ActiveCall::global(cx).read(cx).room();
|
||||
|
||||
let telemetry = client.telemetry();
|
||||
|
||||
telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
@@ -243,7 +243,9 @@ impl ActiveCall {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = invite.await;
|
||||
if result.is_ok() {
|
||||
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("Participant Invited", cx)
|
||||
})?;
|
||||
} else {
|
||||
//TODO: report collaboration error
|
||||
log::error!("invite failed: {:?}", result);
|
||||
@@ -311,7 +313,7 @@ impl ActiveCall {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("accept incoming", cx)
|
||||
this.report_call_event("Incoming Call Accepted", cx)
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -324,7 +326,7 @@ impl ActiveCall {
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.ok_or_else(|| anyhow!("no incoming call"))?;
|
||||
report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
|
||||
telemetry::event!("Incoming Call Declined", room_id = call.room_id);
|
||||
self.client.send(proto::DeclineCall {
|
||||
room_id: call.room_id,
|
||||
})?;
|
||||
@@ -359,7 +361,7 @@ impl ActiveCall {
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("join channel", cx)
|
||||
this.report_call_event("Channel Joined", cx)
|
||||
})?;
|
||||
Ok(room)
|
||||
})
|
||||
@@ -367,7 +369,7 @@ impl ActiveCall {
|
||||
|
||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.report_call_event("hang up", cx);
|
||||
self.report_call_event("Call Ended", cx);
|
||||
|
||||
Audio::end_call(cx);
|
||||
|
||||
@@ -386,7 +388,7 @@ impl ActiveCall {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("share project", cx);
|
||||
self.report_call_event("Project Shared", cx);
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
@@ -399,7 +401,7 @@ impl ActiveCall {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("unshare project", cx);
|
||||
self.report_call_event("Project Unshared", cx);
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
} else {
|
||||
Err(anyhow!("no active call"))
|
||||
@@ -479,35 +481,15 @@ impl ActiveCall {
|
||||
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
|
||||
if let Some(room) = self.room() {
|
||||
let room = room.read(cx);
|
||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
|
||||
telemetry::event!(
|
||||
operation,
|
||||
room_id = room.id(),
|
||||
channel_id = room.channel_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_call_event_for_room(
|
||||
operation: &'static str,
|
||||
room_id: u64,
|
||||
channel_id: Option<ChannelId>,
|
||||
client: &Arc<Client>,
|
||||
) {
|
||||
let telemetry = client.telemetry();
|
||||
|
||||
telemetry.report_call_event(operation, Some(room_id), channel_id)
|
||||
}
|
||||
|
||||
pub fn report_call_event_for_channel(
|
||||
operation: &'static str,
|
||||
channel_id: ChannelId,
|
||||
client: &Arc<Client>,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
let room = ActiveCall::global(cx).read(cx).room();
|
||||
|
||||
let telemetry = client.telemetry();
|
||||
|
||||
telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::time::Instant;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use telemetry_events::{
|
||||
AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
|
||||
InlineCompletionEvent, InlineCompletionRating, InlineCompletionRatingEvent, SettingEvent,
|
||||
InlineCompletionEvent,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
@@ -349,24 +349,6 @@ impl Telemetry {
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_inline_completion_rating_event(
|
||||
self: &Arc<Self>,
|
||||
rating: InlineCompletionRating,
|
||||
input_events: Arc<str>,
|
||||
input_excerpt: Arc<str>,
|
||||
output_excerpt: Arc<str>,
|
||||
feedback: String,
|
||||
) {
|
||||
let event = Event::InlineCompletionRating(InlineCompletionRatingEvent {
|
||||
rating,
|
||||
input_events,
|
||||
input_excerpt,
|
||||
output_excerpt,
|
||||
feedback,
|
||||
});
|
||||
self.report_event(event);
|
||||
}
|
||||
|
||||
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
|
||||
self.report_event(Event::Assistant(event));
|
||||
}
|
||||
@@ -394,15 +376,6 @@ impl Telemetry {
|
||||
event
|
||||
}
|
||||
|
||||
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
|
||||
let event = Event::Setting(SettingEvent {
|
||||
setting: setting.to_string(),
|
||||
value,
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn log_edit_event(self: &Arc<Self>, environment: &'static str, is_via_ssh: bool) {
|
||||
let mut state = self.state.lock();
|
||||
let period_data = state.event_coalescer.log_event(environment);
|
||||
|
||||
@@ -16,7 +16,9 @@ use util::TryFutureExt as _;
|
||||
|
||||
pub type UserId = u64;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
pub struct ChannelId(pub u64);
|
||||
|
||||
impl std::fmt::Display for ChannelId {
|
||||
|
||||
@@ -8,7 +8,12 @@ It contains our back-end logic for collaboration, to which we connect from the Z
|
||||
|
||||
## Database setup
|
||||
|
||||
Before you can run the collab server locally, you'll need to set up a zed Postgres database.
|
||||
Before you can run the collab server locally, you'll need to set up a zed Postgres database. Follow the steps sequentially:
|
||||
|
||||
1. Ensure you have postgres installed. If not, install with `brew install postgresql@15`.
|
||||
2. Follow the steps on Brew's formula and verify your `$PATH` contains `/opt/homebrew/opt/postgresql@15/bin`.
|
||||
3. If you hadn't done it before, create the `postgres` user with `createuser -s postgres`.
|
||||
4. You are now ready to run the `bootstrap` script:
|
||||
|
||||
```sh
|
||||
script/bootstrap
|
||||
|
||||
@@ -10,7 +10,7 @@ use editor::{
|
||||
ToggleCodeActions, Undo,
|
||||
},
|
||||
test::editor_test_context::{AssertionContextManager, EditorTestContext},
|
||||
Editor, RowInfo,
|
||||
Editor,
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
@@ -20,6 +20,7 @@ use language::{
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
FakeLspAdapter,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use project::{
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
@@ -2074,15 +2075,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&(0..4)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
@@ -2121,15 +2114,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&(0..4)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
@@ -2156,15 +2141,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
let blame = editor_b.blame().expect("editor_b should have blame now");
|
||||
let entries = blame.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&(0..4)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx,
|
||||
)
|
||||
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
telemetry.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use call::report_call_event_for_channel;
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore};
|
||||
use client::{
|
||||
proto::{self, PeerId},
|
||||
@@ -66,11 +66,13 @@ impl ChannelView {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let channel_view = channel_view.await?;
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
report_call_event_for_channel(
|
||||
"open channel notes",
|
||||
telemetry::event!(
|
||||
"Channel Notes Opened",
|
||||
channel_id,
|
||||
&workspace.read(cx).app_state().client,
|
||||
cx,
|
||||
room_id = ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
.map(|r| r.read(cx).id())
|
||||
);
|
||||
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
|
||||
})?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{collab_panel, ChatPanelSettings};
|
||||
use crate::{collab_panel, ChatPanelButton, ChatPanelSettings};
|
||||
use anyhow::Result;
|
||||
use call::{room, ActiveCall};
|
||||
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore};
|
||||
@@ -1135,7 +1135,14 @@ impl Panel for ChatPanel {
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
|
||||
Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
|
||||
match ChatPanelSettings::get_global(cx).button {
|
||||
ChatPanelButton::Never => None,
|
||||
ChatPanelButton::Always => Some(ui::IconName::MessageBubbles),
|
||||
ChatPanelButton::WhenInCall => ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
.map(|_| ui::IconName::MessageBubbles),
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
|
||||
@@ -14,7 +14,7 @@ use gpui::{
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
pub use panel_settings::{
|
||||
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
|
||||
ChatPanelButton, ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
|
||||
};
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::Pixels;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use workspace::dock::DockPosition;
|
||||
|
||||
@@ -11,13 +11,82 @@ pub struct CollaborationPanelSettings {
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Serialize, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChatPanelButton {
|
||||
Never,
|
||||
Always,
|
||||
#[default]
|
||||
WhenInCall,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ChatPanelButton {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = ChatPanelButton;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"a boolean or one of "never", "always", "when_in_call""#
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match b {
|
||||
false => Ok(ChatPanelButton::Never),
|
||||
true => Ok(ChatPanelButton::Always),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match s {
|
||||
"never" => Ok(ChatPanelButton::Never),
|
||||
"always" => Ok(ChatPanelButton::Always),
|
||||
"when_in_call" => Ok(ChatPanelButton::WhenInCall),
|
||||
_ => Err(E::unknown_variant(s, &["never", "always", "when_in_call"])),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ChatPanelSettings {
|
||||
pub button: bool,
|
||||
pub button: ChatPanelButton,
|
||||
pub dock: DockPosition,
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct ChatPanelSettingsContent {
|
||||
/// When to show the panel button in the status bar.
|
||||
///
|
||||
/// Default: only when in a call
|
||||
pub button: Option<ChatPanelButton>,
|
||||
/// Where to dock the panel.
|
||||
///
|
||||
/// Default: right
|
||||
pub dock: Option<DockPosition>,
|
||||
/// Default width of the panel in pixels.
|
||||
///
|
||||
/// Default: 240
|
||||
pub default_width: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NotificationPanelSettings {
|
||||
pub button: bool,
|
||||
@@ -66,7 +135,7 @@ impl Settings for CollaborationPanelSettings {
|
||||
impl Settings for ChatPanelSettings {
|
||||
const KEY: Option<&'static str> = Some("chat_panel");
|
||||
|
||||
type FileContent = PanelSettingsContent;
|
||||
type FileContent = ChatPanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
|
||||
@@ -36,21 +36,26 @@ pub struct CommandPalette {
|
||||
picker: View<Picker<CommandPaletteDelegate>>,
|
||||
}
|
||||
|
||||
fn trim_consecutive_whitespaces(input: &str) -> String {
|
||||
/// Removes subsequent whitespace characters and double colons from the query.
|
||||
///
|
||||
/// This improves the likelihood of a match by either humanized name or keymap-style name.
|
||||
fn normalize_query(input: &str) -> String {
|
||||
let mut result = String::with_capacity(input.len());
|
||||
let mut last_char_was_whitespace = false;
|
||||
let mut last_char = None;
|
||||
|
||||
for char in input.trim().chars() {
|
||||
if char.is_whitespace() {
|
||||
if !last_char_was_whitespace {
|
||||
result.push(char);
|
||||
match (last_char, char) {
|
||||
(Some(':'), ':') => continue,
|
||||
(Some(last_char), char) if last_char.is_whitespace() && char.is_whitespace() => {
|
||||
continue
|
||||
}
|
||||
_ => {
|
||||
last_char = Some(char);
|
||||
}
|
||||
last_char_was_whitespace = true;
|
||||
} else {
|
||||
result.push(char);
|
||||
last_char_was_whitespace = false;
|
||||
}
|
||||
result.push(char);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -258,7 +263,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
let mut commands = self.all_commands.clone();
|
||||
let hit_counts = cx.global::<HitCounts>().clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
let query = trim_consecutive_whitespaces(query.as_str());
|
||||
let query = normalize_query(query.as_str());
|
||||
async move {
|
||||
commands.sort_by_key(|action| {
|
||||
(
|
||||
@@ -463,6 +468,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_query() {
|
||||
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
|
||||
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
|
||||
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
|
||||
assert_eq!(
|
||||
normalize_query("editor::GoToDefinition"),
|
||||
"editor:GoToDefinition"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_query("editor::::GoToDefinition"),
|
||||
"editor:GoToDefinition"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_query("editor: :GoToDefinition"),
|
||||
"editor: :GoToDefinition"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_command_palette(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
@@ -533,6 +557,40 @@ mod tests {
|
||||
assert!(palette.delegate.matches.is_empty())
|
||||
});
|
||||
}
|
||||
#[gpui::test]
|
||||
async fn test_normalized_matches(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_text("abc", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
|
||||
editor.update(cx, |editor, cx| editor.focus(cx))
|
||||
});
|
||||
|
||||
// Test normalize (trimming whitespace and double colons)
|
||||
cx.simulate_keystrokes("cmd-shift-p");
|
||||
|
||||
let palette = workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_modal::<CommandPalette>(cx)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.picker
|
||||
.clone()
|
||||
});
|
||||
|
||||
cx.simulate_input("Editor:: Backspace");
|
||||
palette.update(cx, |palette, _| {
|
||||
assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_go_to_line(cx: &mut TestAppContext) {
|
||||
|
||||
@@ -59,6 +59,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
"Copilot"
|
||||
}
|
||||
|
||||
fn show_completions_in_menu() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -326,17 +330,15 @@ mod tests {
|
||||
);
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
// We want to show both: the inline completion and the completion menu
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert!(!editor.context_menu_contains_inline_completion());
|
||||
assert!(!editor.has_active_inline_completion());
|
||||
// Since we have both, the copilot suggestion is not shown inline
|
||||
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||
assert_eq!(editor.display_text(cx), "one.\ntwo\nthree\n");
|
||||
|
||||
// Confirming a non-copilot completion inserts it and hides the context menu, without showing
|
||||
// the copilot suggestion afterwards.
|
||||
editor.context_menu_next(&Default::default(), cx);
|
||||
editor
|
||||
.confirm_completion(&Default::default(), cx)
|
||||
.unwrap()
|
||||
@@ -384,23 +386,12 @@ mod tests {
|
||||
|
||||
// Ensure existing inline completion is interpolated when inserting again.
|
||||
cx.simulate_keystroke("c");
|
||||
drop(handle_completion_request(
|
||||
&mut cx,
|
||||
indoc! {"
|
||||
one.c|<>
|
||||
two
|
||||
three
|
||||
"},
|
||||
vec!["completion_a", "completion_b"],
|
||||
));
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
// Since we have an LSP completion too, the inline completion is
|
||||
// shown in the menu now
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(!editor.context_menu_contains_inline_completion());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
});
|
||||
|
||||
@@ -416,16 +407,9 @@ mod tests {
|
||||
);
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
|
||||
// Canceling should first hide the menu and make Copilot suggestion visible.
|
||||
editor.cancel(&Default::default(), cx);
|
||||
assert!(!editor.context_menu_visible());
|
||||
assert!(editor.has_active_inline_completion());
|
||||
assert!(!editor.context_menu_contains_inline_completion());
|
||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||
|
||||
@@ -928,8 +912,8 @@ mod tests {
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor.context_menu_visible());
|
||||
assert!(editor.context_menu_contains_inline_completion());
|
||||
assert!(editor.has_active_inline_completion(),);
|
||||
assert!(!editor.context_menu_contains_inline_completion());
|
||||
assert!(!editor.has_active_inline_completion(),);
|
||||
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ use editor::{
|
||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
|
||||
highlight_diagnostic_message,
|
||||
scroll::Autoscroll,
|
||||
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||
Anchor, AnchorRangeExt, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
|
||||
RangeToAnchorExt, ToOffset,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
||||
@@ -21,7 +22,8 @@ use gpui::{
|
||||
WeakView, WindowContext,
|
||||
};
|
||||
use language::{
|
||||
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
|
||||
Bias, Buffer, BufferId, Diagnostic, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal,
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||
@@ -38,9 +40,10 @@ use std::{
|
||||
use theme::ActiveTheme;
|
||||
pub use toolbar_controls::ToolbarControls;
|
||||
use ui::{h_flex, prelude::*, Icon, IconName, Label};
|
||||
use util::ResultExt;
|
||||
use util::{maybe, RangeExt, ResultExt};
|
||||
use workspace::{
|
||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
|
||||
searchable::SearchableItemHandle,
|
||||
ItemNavHistory, ToolbarItemLocation, Workspace,
|
||||
};
|
||||
|
||||
@@ -255,26 +258,69 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
workspace.activate_item(&existing, true, true, cx);
|
||||
} else {
|
||||
let workspace_handle = cx.view().downgrade();
|
||||
let diagnostic_to_select = maybe!({
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
// Already on diagnostics, so don't update position / selection within.
|
||||
if active_item.downcast::<ProjectDiagnosticsEditor>().is_some() {
|
||||
None
|
||||
} else {
|
||||
let active_editor = active_item.act_as::<Editor>(cx)?.read(cx);
|
||||
let snapshot = active_editor.buffer().read(cx).snapshot(cx);
|
||||
let newest_selection = active_editor.selections.newest::<usize>(cx).range();
|
||||
let mut diagnostics =
|
||||
snapshot.diagnostics_in_range(newest_selection.clone(), false);
|
||||
let diagnostic_entry: DiagnosticEntry<usize> = dbg!(diagnostics.next())?;
|
||||
let buffer_id = diagnostic_entry
|
||||
.range
|
||||
.to_anchors(&snapshot)
|
||||
.start
|
||||
.buffer_id?;
|
||||
Some((buffer_id, diagnostic_entry.range, newest_selection))
|
||||
}
|
||||
});
|
||||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
let diagnostics =
|
||||
if let Some(diagnostics) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
// todo! dedupe
|
||||
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.set_selection_that_intersects_diagnostic(
|
||||
buffer_id,
|
||||
diagnostic_range,
|
||||
selection_range,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
workspace.activate_item(&diagnostics, true, true, cx);
|
||||
} else {
|
||||
let workspace_handle = cx.view().downgrade();
|
||||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
};
|
||||
|
||||
let diagnostics = cx.new_view(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
workspace.project().clone(),
|
||||
include_warnings,
|
||||
workspace_handle,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.set_selection_that_intersects_diagnostic(
|
||||
buffer_id,
|
||||
diagnostic_range,
|
||||
selection_range,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||
};
|
||||
|
||||
let diagnostics = cx.new_view(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
workspace.project().clone(),
|
||||
include_warnings,
|
||||
workspace_handle,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
||||
@@ -631,6 +677,30 @@ impl ProjectDiagnosticsEditor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_selection_that_intersects_diagnostic(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
diagnostic_range: Range<usize>,
|
||||
selection_range: Range<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.excerpts_for_range(range)
|
||||
/*
|
||||
let excerpts = buffer.excerpts_for_buffer_id(buffer_id, cx);
|
||||
for (excerpt_id, excerpt_range) in excerpts {
|
||||
let excerpt_range = excerpt_range.context.to_offset(snapshot.excerpt_containing(range));
|
||||
if excerpt_range.contains_inclusive(&diagnostic_range.to_offset(&snapshot)) {}
|
||||
}
|
||||
*/
|
||||
// editor.change_selections(Some(Autoscroll::center()), cx, |selections| {
|
||||
// selections.select_ranges(vec![diagnostic_range]);
|
||||
// })
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_invariants(&self, cx: &mut ViewContext<Self>) {
|
||||
let mut excerpts = Vec::new();
|
||||
@@ -810,6 +880,10 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::PrimaryLeft
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::Cell, cmp::Reverse, ops::Range, rc::Rc};
|
||||
use std::{cmp::Reverse, ops::Range, rc::Rc};
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
@@ -14,8 +14,8 @@ use multi_buffer::{Anchor, ExcerptId};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{CodeAction, Completion, TaskSourceKind};
|
||||
use task::ResolvedTask;
|
||||
use ui::{prelude::*, Color, IntoElement, ListItem, Popover, Styled};
|
||||
use util::ResultExt as _;
|
||||
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
@@ -26,6 +26,8 @@ use crate::{
|
||||
};
|
||||
use crate::{AcceptInlineCompletion, InlineCompletionMenuHint, InlineCompletionText};
|
||||
|
||||
pub const MAX_COMPLETIONS_ASIDE_WIDTH: Pixels = px(500.);
|
||||
|
||||
pub enum CodeContextMenu {
|
||||
Completions(CompletionsMenu),
|
||||
CodeActions(CodeActionsMenu),
|
||||
@@ -109,20 +111,33 @@ impl CodeContextMenu {
|
||||
CodeContextMenu::CodeActions(menu) => menu.origin(cursor_position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height_in_lines: u32,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> AnyElement {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => {
|
||||
menu.render(style, max_height_in_lines, workspace, cx)
|
||||
}
|
||||
CodeContextMenu::Completions(menu) => menu.render(style, max_height_in_lines, cx),
|
||||
CodeContextMenu::CodeActions(menu) => menu.render(style, max_height_in_lines, cx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_aside(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<AnyElement> {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => {
|
||||
menu.render_aside(style, max_height, workspace, cx)
|
||||
}
|
||||
CodeContextMenu::CodeActions(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuOrigin {
|
||||
@@ -142,7 +157,6 @@ pub struct CompletionsMenu {
|
||||
pub selected_item: usize,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
resolve_completions: bool,
|
||||
pub aside_was_displayed: Cell<bool>,
|
||||
show_completion_documentation: bool,
|
||||
}
|
||||
|
||||
@@ -160,7 +174,6 @@ impl CompletionsMenu {
|
||||
initial_position: Anchor,
|
||||
buffer: Model<Buffer>,
|
||||
completions: Box<[Completion]>,
|
||||
aside_was_displayed: bool,
|
||||
) -> Self {
|
||||
let match_candidates = completions
|
||||
.iter()
|
||||
@@ -180,7 +193,6 @@ impl CompletionsMenu {
|
||||
selected_item: 0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
resolve_completions: true,
|
||||
aside_was_displayed: Cell::new(aside_was_displayed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +248,6 @@ impl CompletionsMenu {
|
||||
selected_item: 0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
resolve_completions: false,
|
||||
aside_was_displayed: Cell::new(false),
|
||||
show_completion_documentation: false,
|
||||
}
|
||||
}
|
||||
@@ -314,7 +325,9 @@ impl CompletionsMenu {
|
||||
}
|
||||
}
|
||||
.into();
|
||||
self.selected_item = 0;
|
||||
if self.selected_item != 0 && self.selected_item + 1 < self.entries.len() {
|
||||
self.selected_item += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_selected_completion(
|
||||
@@ -362,11 +375,8 @@ impl CompletionsMenu {
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height_in_lines: u32,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> AnyElement {
|
||||
let max_height = max_height_in_lines as f32 * cx.line_height();
|
||||
|
||||
let completions = self.completions.borrow_mut();
|
||||
let show_completion_documentation = self.show_completion_documentation;
|
||||
let widest_completion_ix = self
|
||||
@@ -393,71 +403,12 @@ impl CompletionsMenu {
|
||||
}) => provider_name.len(),
|
||||
})
|
||||
.map(|(ix, _)| ix);
|
||||
drop(completions);
|
||||
|
||||
let selected_item = self.selected_item;
|
||||
let style = style.clone();
|
||||
|
||||
let multiline_docs = match &self.entries[selected_item] {
|
||||
CompletionEntry::Match(mat) if show_completion_documentation => {
|
||||
match &completions[mat.candidate_id].documentation {
|
||||
Some(Documentation::MultiLinePlainText(text)) => {
|
||||
Some(div().child(SharedString::from(text.clone())))
|
||||
}
|
||||
Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
|
||||
Some(div().child(render_parsed_markdown(
|
||||
"completions_markdown",
|
||||
parsed,
|
||||
&style,
|
||||
workspace,
|
||||
cx,
|
||||
)))
|
||||
}
|
||||
Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
|
||||
Some(div().child("No documentation"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
CompletionEntry::InlineCompletionHint(hint) => Some(match &hint.text {
|
||||
InlineCompletionText::Edit { text, highlights } => div()
|
||||
.my_1()
|
||||
.rounded(px(6.))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
gpui::StyledText::new(text.clone())
|
||||
.with_highlights(&style.text, highlights.clone()),
|
||||
),
|
||||
InlineCompletionText::Move(text) => div().child(text.clone()),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let aside_contents = if let Some(multiline_docs) = multiline_docs {
|
||||
Some(multiline_docs)
|
||||
} else if show_completion_documentation && self.aside_was_displayed.get() {
|
||||
Some(div().child("Fetching documentation..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.aside_was_displayed.set(aside_contents.is_some());
|
||||
|
||||
let aside_contents = aside_contents.map(|div| {
|
||||
div.id("multiline_docs")
|
||||
.max_h(max_height)
|
||||
.flex_1()
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.max_w(px(640.))
|
||||
.w(px(450.))
|
||||
.overflow_y_scroll()
|
||||
.occlude()
|
||||
});
|
||||
|
||||
drop(completions);
|
||||
let completions = self.completions.clone();
|
||||
let matches = self.entries.clone();
|
||||
let style = style.clone();
|
||||
let list = uniform_list(
|
||||
cx.view().clone(),
|
||||
"completions",
|
||||
@@ -588,12 +539,71 @@ impl CompletionsMenu {
|
||||
.with_width_from_item(widest_completion_ix)
|
||||
.with_sizing_behavior(ListSizingBehavior::Infer);
|
||||
|
||||
Popover::new()
|
||||
.child(list)
|
||||
.when_some(aside_contents, |popover, aside_contents| {
|
||||
popover.aside(aside_contents)
|
||||
})
|
||||
.into_any_element()
|
||||
Popover::new().child(list).into_any_element()
|
||||
}
|
||||
|
||||
fn render_aside(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<AnyElement> {
|
||||
if !self.show_completion_documentation {
|
||||
return None;
|
||||
}
|
||||
|
||||
let multiline_docs = match &self.entries[self.selected_item] {
|
||||
CompletionEntry::Match(mat) => {
|
||||
match self.completions.borrow_mut()[mat.candidate_id]
|
||||
.documentation
|
||||
.as_ref()?
|
||||
{
|
||||
Documentation::MultiLinePlainText(text) => {
|
||||
div().child(SharedString::from(text.clone()))
|
||||
}
|
||||
Documentation::MultiLineMarkdown(parsed) if !parsed.text.is_empty() => div()
|
||||
.child(render_parsed_markdown(
|
||||
"completions_markdown",
|
||||
parsed,
|
||||
&style,
|
||||
workspace,
|
||||
cx,
|
||||
)),
|
||||
Documentation::MultiLineMarkdown(_) => return None,
|
||||
Documentation::SingleLine(_) => return None,
|
||||
Documentation::Undocumented => return None,
|
||||
}
|
||||
}
|
||||
CompletionEntry::InlineCompletionHint(hint) => match &hint.text {
|
||||
InlineCompletionText::Edit { text, highlights } => div()
|
||||
.mx_1()
|
||||
.rounded(px(6.))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
gpui::StyledText::new(text.clone())
|
||||
.with_highlights(&style.text, highlights.clone()),
|
||||
),
|
||||
InlineCompletionText::Move(text) => div().child(text.clone()),
|
||||
},
|
||||
};
|
||||
|
||||
Some(
|
||||
Popover::new()
|
||||
.child(
|
||||
multiline_docs
|
||||
.id("multiline_docs")
|
||||
.max_h(max_height)
|
||||
.px_2()
|
||||
.min_w(px(260.))
|
||||
.max_w(MAX_COMPLETIONS_ASIDE_WIDTH)
|
||||
.overflow_y_scroll()
|
||||
.occlude(),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
mod block_map;
|
||||
mod crease_map;
|
||||
mod custom_highlights;
|
||||
mod diff_map;
|
||||
mod fold_map;
|
||||
mod inlay_map;
|
||||
pub(crate) mod invisibles;
|
||||
@@ -31,18 +30,16 @@ use crate::{
|
||||
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
|
||||
};
|
||||
pub use block_map::{
|
||||
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
|
||||
BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock,
|
||||
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use crease_map::*;
|
||||
use diff_map::{DiffMap, DiffMapSnapshot, DiffOffset, DiffPoint};
|
||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||
use fold_map::{FoldMap, FoldSnapshot};
|
||||
use gpui::{
|
||||
AnyElement, AppContext, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels,
|
||||
UnderlineStyle,
|
||||
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||
};
|
||||
pub(crate) use inlay_map::Inlay;
|
||||
use inlay_map::{InlayMap, InlaySnapshot};
|
||||
@@ -54,10 +51,9 @@ use language::{
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
|
||||
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
|
||||
ToOffset, ToPoint,
|
||||
};
|
||||
use project::buffer_store::BufferChangeSet;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
@@ -70,7 +66,7 @@ use std::{
|
||||
};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::{BufferId, LineIndent};
|
||||
use text::LineIndent;
|
||||
use ui::{px, SharedString, WindowContext};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wrap_map::{WrapMap, WrapSnapshot};
|
||||
@@ -100,8 +96,6 @@ pub struct DisplayMap {
|
||||
buffer_subscription: BufferSubscription,
|
||||
/// Decides where the [`Inlay`]s should be displayed.
|
||||
inlay_map: InlayMap,
|
||||
/// Decides where diff hunks should be.
|
||||
diff_map: Model<DiffMap>,
|
||||
/// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
|
||||
fold_map: FoldMap,
|
||||
/// Keeps track of hard tabs in a buffer.
|
||||
@@ -140,8 +134,7 @@ impl DisplayMap {
|
||||
let tab_size = Self::tab_size(&buffer, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let crease_map = CreaseMap::new(&buffer_snapshot);
|
||||
let (diff_map, snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (inlay_map, snapshot) = InlayMap::new(snapshot);
|
||||
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let (fold_map, snapshot) = FoldMap::new(snapshot);
|
||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
||||
@@ -160,7 +153,6 @@ impl DisplayMap {
|
||||
buffer_subscription,
|
||||
fold_map,
|
||||
inlay_map,
|
||||
diff_map,
|
||||
tab_map,
|
||||
wrap_map,
|
||||
block_map,
|
||||
@@ -176,10 +168,7 @@ impl DisplayMap {
|
||||
pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (diff_snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, edits) = self.inlay_map.sync(diff_snapshot, edits);
|
||||
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
|
||||
@@ -228,10 +217,7 @@ impl DisplayMap {
|
||||
let buffer_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.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), edits, cx)
|
||||
});
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
@@ -304,9 +290,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -336,9 +319,6 @@ impl DisplayMap {
|
||||
.collect::<Vec<_>>();
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -361,9 +341,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, 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);
|
||||
@@ -378,9 +355,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, 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);
|
||||
@@ -413,71 +387,6 @@ impl DisplayMap {
|
||||
self.crease_map.remove(crease_ids, &snapshot)
|
||||
}
|
||||
|
||||
pub fn add_change_set(
|
||||
&mut self,
|
||||
change_set: Model<BufferChangeSet>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.add_change_set(change_set, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
|
||||
self.diff_map.read(cx).has_multiple_hunks()
|
||||
}
|
||||
|
||||
pub fn has_expanded_diff_hunks_in_ranges(
|
||||
&mut self,
|
||||
ranges: &[Range<multi_buffer::Anchor>],
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> bool {
|
||||
self.diff_map
|
||||
.read(cx)
|
||||
.has_expanded_diff_hunks_in_ranges(ranges)
|
||||
}
|
||||
|
||||
pub fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.update_diff_map(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx))
|
||||
}
|
||||
|
||||
pub fn expand_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut ModelContext<Self>) {
|
||||
self.update_diff_map(cx, |diff_map, cx| diff_map.expand_diff_hunks(ranges, cx))
|
||||
}
|
||||
|
||||
pub fn collapse_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut ModelContext<Self>) {
|
||||
self.update_diff_map(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx))
|
||||
}
|
||||
|
||||
pub fn diff_base_for<'a>(
|
||||
&'a self,
|
||||
buffer_id: BufferId,
|
||||
cx: &'a AppContext,
|
||||
) -> Option<&'a Model<BufferChangeSet>> {
|
||||
self.diff_map.read(cx).diff_base_for(buffer_id)
|
||||
}
|
||||
|
||||
fn update_diff_map(
|
||||
&mut self,
|
||||
cx: &mut ModelContext<Self>,
|
||||
f: impl FnOnce(&mut DiffMap, &mut ModelContext<DiffMap>),
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
f(diff_map, cx);
|
||||
diff_map.sync(snapshot, edits, cx)
|
||||
});
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
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));
|
||||
self.block_map.write(snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn insert_blocks(
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||
@@ -486,9 +395,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, 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);
|
||||
@@ -507,9 +413,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, 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);
|
||||
@@ -528,9 +431,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, 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);
|
||||
@@ -549,9 +449,6 @@ impl DisplayMap {
|
||||
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
|
||||
.diff_map
|
||||
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, 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);
|
||||
@@ -627,10 +524,7 @@ impl DisplayMap {
|
||||
}
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), edits, cx)
|
||||
});
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot.clone(), edits);
|
||||
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
|
||||
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
@@ -816,35 +710,17 @@ impl DisplaySnapshot {
|
||||
self.fold_snapshot.fold_count()
|
||||
}
|
||||
|
||||
pub fn diff_hunks<'a>(&'a self) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_snapshot()
|
||||
.diff_hunks_in_range(0..self.buffer_snapshot.len())
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_snapshot().diff_hunks_in_range(range)
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_snapshot().diff_hunks_in_range_rev(range)
|
||||
}
|
||||
|
||||
pub fn has_diff_hunks(&self) -> bool {
|
||||
self.diff_snapshot().has_diff_hunks()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buffer_snapshot.len() == 0
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
|
||||
self.block_snapshot.row_infos(BlockRow(start_row.0))
|
||||
pub fn buffer_rows(
|
||||
&self,
|
||||
start_row: DisplayRow,
|
||||
) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
|
||||
self.block_snapshot
|
||||
.buffer_rows(BlockRow(start_row.0))
|
||||
.map(|row| row.map(MultiBufferRow))
|
||||
}
|
||||
|
||||
pub fn widest_line_number(&self) -> u32 {
|
||||
@@ -853,7 +729,7 @@ impl DisplaySnapshot {
|
||||
|
||||
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
|
||||
loop {
|
||||
let mut inlay_point = self.inlay_snapshot.make_inlay_point(point);
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
|
||||
fold_point.0.column = 0;
|
||||
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
@@ -875,7 +751,7 @@ impl DisplaySnapshot {
|
||||
) -> (MultiBufferPoint, DisplayPoint) {
|
||||
let original_point = point;
|
||||
loop {
|
||||
let mut inlay_point = self.inlay_snapshot.make_inlay_point(point);
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
|
||||
fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
|
||||
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
@@ -919,7 +795,7 @@ impl DisplaySnapshot {
|
||||
}
|
||||
|
||||
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
|
||||
let inlay_point = self.inlay_snapshot.make_inlay_point(point);
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
|
||||
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
|
||||
@@ -937,15 +813,9 @@ impl DisplaySnapshot {
|
||||
.to_offset(self.display_point_to_inlay_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn display_point_to_diff_offset(&self, point: DisplayPoint, bias: Bias) -> DiffOffset {
|
||||
self.diff_snapshot()
|
||||
.point_to_offset(self.display_point_to_diff_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
|
||||
let multibuffer_offset = anchor.to_offset(&self.buffer_snapshot);
|
||||
let diff_offset = self.diff_snapshot().to_diff_offset(multibuffer_offset);
|
||||
self.inlay_snapshot.to_inlay_offset(diff_offset)
|
||||
self.inlay_snapshot
|
||||
.to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
|
||||
}
|
||||
|
||||
pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
|
||||
@@ -961,17 +831,6 @@ impl DisplaySnapshot {
|
||||
fold_point.to_inlay_point(&self.fold_snapshot)
|
||||
}
|
||||
|
||||
fn diff_point_to_display_point(&self, diff_point: DiffPoint, bias: Bias) -> DisplayPoint {
|
||||
let inlay_point = self.inlay_snapshot.to_inlay_point(diff_point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
self.fold_point_to_display_point(fold_point)
|
||||
}
|
||||
|
||||
fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint {
|
||||
self.inlay_snapshot
|
||||
.to_diff_point(self.display_point_to_inlay_point(point, bias))
|
||||
}
|
||||
|
||||
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
|
||||
let block_point = point.0;
|
||||
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
|
||||
@@ -1209,22 +1068,6 @@ impl DisplaySnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn anchor_before(&self, point: DisplayPoint) -> DisplayAnchor {
|
||||
let diff_point = self.display_point_to_diff_point(point, Bias::Left);
|
||||
self.diff_snapshot().point_to_anchor(diff_point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn anchor_after(&self, point: DisplayPoint) -> DisplayAnchor {
|
||||
let diff_point = self.display_point_to_diff_point(point, Bias::Left);
|
||||
self.diff_snapshot()
|
||||
.point_to_anchor(diff_point, Bias::Right)
|
||||
}
|
||||
|
||||
pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DisplayPoint {
|
||||
let diff_point = self.diff_snapshot().anchor_to_point(anchor);
|
||||
self.diff_point_to_display_point(diff_point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
|
||||
let mut clipped = self.block_snapshot.clip_point(point.0, bias);
|
||||
if self.clip_at_line_ends {
|
||||
@@ -1464,51 +1307,6 @@ impl DisplaySnapshot {
|
||||
pub fn excerpt_header_height(&self) -> u32 {
|
||||
self.block_snapshot.excerpt_header_height
|
||||
}
|
||||
|
||||
pub(crate) fn diff_snapshot(&self) -> &DiffMapSnapshot {
|
||||
&self.inlay_snapshot.diff_map_snapshot
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
|
||||
pub struct DisplayAnchor {
|
||||
pub anchor: multi_buffer::Anchor,
|
||||
pub diff_base_anchor: Option<text::Anchor>,
|
||||
}
|
||||
|
||||
impl DisplayAnchor {
|
||||
pub fn min() -> Self {
|
||||
Self {
|
||||
anchor: multi_buffer::Anchor::min(),
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max() -> Self {
|
||||
Self {
|
||||
anchor: multi_buffer::Anchor::max(),
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &DisplayAnchor, map: &DisplaySnapshot) -> std::cmp::Ordering {
|
||||
map.diff_snapshot().compare_anchors(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DisplayCoordinate {
|
||||
fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor;
|
||||
}
|
||||
|
||||
impl DisplayCoordinate for DisplayAnchor {
|
||||
fn to_display_anchor(self, _: &DisplaySnapshot) -> DisplayAnchor {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl DisplayCoordinate for DisplayPoint {
|
||||
fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor {
|
||||
map.anchor_before(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use crate::{EditorStyle, GutterDimensions, RowInfo};
|
||||
use crate::{EditorStyle, GutterDimensions};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, AppContext, EntityId, Pixels, WindowContext};
|
||||
use language::{Chunk, Patch, Point};
|
||||
@@ -399,9 +399,9 @@ pub struct BlockChunks<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockRows<'a> {
|
||||
pub struct BlockBufferRows<'a> {
|
||||
transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
|
||||
input_rows: wrap_map::WrapRows<'a>,
|
||||
input_buffer_rows: wrap_map::WrapBufferRows<'a>,
|
||||
output_row: BlockRow,
|
||||
started: bool,
|
||||
}
|
||||
@@ -1360,7 +1360,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
|
||||
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&start_row, Bias::Right, &());
|
||||
let (output_start, input_start) = cursor.start();
|
||||
@@ -1373,9 +1373,9 @@ impl BlockSnapshot {
|
||||
0
|
||||
};
|
||||
let input_start_row = input_start.0 + overshoot;
|
||||
BlockRows {
|
||||
BlockBufferRows {
|
||||
transforms: cursor,
|
||||
input_rows: self.wrap_snapshot.row_infos(input_start_row),
|
||||
input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
|
||||
output_row: start_row,
|
||||
started: false,
|
||||
}
|
||||
@@ -1766,8 +1766,8 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockRows<'a> {
|
||||
type Item = RowInfo;
|
||||
impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.started {
|
||||
@@ -1796,7 +1796,7 @@ impl<'a> Iterator for BlockRows<'a> {
|
||||
.as_ref()
|
||||
.map_or(true, |block| block.is_replacement())
|
||||
{
|
||||
self.input_rows.seek(self.transforms.start().1 .0);
|
||||
self.input_buffer_rows.seek(self.transforms.start().1 .0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1804,15 +1804,15 @@ impl<'a> Iterator for BlockRows<'a> {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.is_replacement() && self.transforms.start().0 == self.output_row {
|
||||
if matches!(block, Block::FoldedBuffer { .. }) {
|
||||
Some(RowInfo::default())
|
||||
Some(None)
|
||||
} else {
|
||||
Some(self.input_rows.next().unwrap())
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
}
|
||||
} else {
|
||||
Some(RowInfo::default())
|
||||
Some(None)
|
||||
}
|
||||
} else {
|
||||
Some(self.input_rows.next().unwrap())
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1928,8 +1928,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::{
|
||||
diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap,
|
||||
wrap_map::WrapMap,
|
||||
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
|
||||
};
|
||||
use gpui::{div, font, px, AppContext, Context as _, Element};
|
||||
use itertools::Itertools;
|
||||
@@ -1963,8 +1962,7 @@ mod tests {
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) =
|
||||
@@ -2089,10 +2087,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
&[
|
||||
Some(0),
|
||||
None,
|
||||
@@ -2113,10 +2108,8 @@ mod tests {
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
|
||||
@@ -2178,8 +2171,8 @@ mod tests {
|
||||
.width;
|
||||
}
|
||||
|
||||
let (_, diff_snapshot) = DiffMap::new(multi_buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
|
||||
@@ -2217,8 +2210,7 @@ mod tests {
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (_diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
|
||||
let (_wrap_map, wraps_snapshot) =
|
||||
@@ -2320,8 +2312,7 @@ mod tests {
|
||||
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = cx.update(|cx| {
|
||||
@@ -2365,8 +2356,7 @@ mod tests {
|
||||
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
|
||||
let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let tab_size = 1.try_into().unwrap();
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
|
||||
@@ -2393,14 +2383,10 @@ mod tests {
|
||||
buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer_snapshot,
|
||||
buffer_subscription.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot.clone(), diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
buffer_subscription.consume().into_inner(),
|
||||
);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
@@ -2420,14 +2406,10 @@ mod tests {
|
||||
);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
buffer_subscription.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
buffer_subscription.consume().into_inner(),
|
||||
);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
@@ -2542,8 +2524,7 @@ mod tests {
|
||||
let buffer_id_2 = buffer_ids[1];
|
||||
let buffer_id_3 = buffer_ids[2];
|
||||
|
||||
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wrap_snapshot) =
|
||||
@@ -2556,10 +2537,7 @@ mod tests {
|
||||
"\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2635,10 +2613,7 @@ mod tests {
|
||||
"\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2713,10 +2688,7 @@ mod tests {
|
||||
"\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2781,10 +2753,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2838,10 +2807,7 @@ mod tests {
|
||||
"Should have extra newline for 111 buffer, due to a new block added when it was folded"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2895,10 +2861,7 @@ mod tests {
|
||||
"Should have a single, first buffer left after folding"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
@@ -2932,8 +2895,7 @@ mod tests {
|
||||
assert_eq!(buffer_ids.len(), 1);
|
||||
let buffer_id = buffer_ids[0];
|
||||
|
||||
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wrap_snapshot) =
|
||||
@@ -2969,10 +2931,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n");
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
vec![None, None],
|
||||
"When fully folded, should be no buffer rows"
|
||||
);
|
||||
@@ -3018,8 +2977,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (wrap_map, wraps_snapshot) = cx
|
||||
@@ -3077,10 +3035,8 @@ mod tests {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
@@ -3117,10 +3073,8 @@ mod tests {
|
||||
.map(|block| block.id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
@@ -3140,11 +3094,8 @@ mod tests {
|
||||
log::info!("Noop fold/unfold operation on a singleton buffer");
|
||||
continue;
|
||||
}
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(diff_snapshot.clone(), diff_edits);
|
||||
inlay_map.sync(buffer_snapshot.clone(), vec![]);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
@@ -3229,10 +3180,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
|
||||
@@ -3435,8 +3384,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(start_row as u32))
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.buffer_rows(BlockRow(start_row as u32))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_row..],
|
||||
"incorrect buffer_rows starting at row {:?}",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,3 @@
|
||||
use crate::RowInfo;
|
||||
|
||||
use super::{
|
||||
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
|
||||
Highlights,
|
||||
@@ -141,7 +139,7 @@ impl<'a> FoldMapWriter<'a> {
|
||||
let mut folds = Vec::new();
|
||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||
for (range, fold_text) in ranges.into_iter() {
|
||||
let buffer = snapshot.buffer();
|
||||
let buffer = &snapshot.buffer;
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
|
||||
// Ignore any empty ranges.
|
||||
@@ -163,14 +161,14 @@ impl<'a> FoldMapWriter<'a> {
|
||||
});
|
||||
|
||||
let inlay_range =
|
||||
snapshot.make_inlay_offset(range.start)..snapshot.make_inlay_offset(range.end);
|
||||
snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = snapshot.buffer();
|
||||
let buffer = &snapshot.buffer;
|
||||
folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
|
||||
|
||||
self.0.snapshot.folds = {
|
||||
@@ -222,7 +220,7 @@ impl<'a> FoldMapWriter<'a> {
|
||||
let mut edits = Vec::new();
|
||||
let mut fold_ixs_to_delete = Vec::new();
|
||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||
let buffer = snapshot.buffer();
|
||||
let buffer = &snapshot.buffer;
|
||||
for range in ranges.into_iter() {
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds_cursor =
|
||||
@@ -232,8 +230,8 @@ impl<'a> FoldMapWriter<'a> {
|
||||
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
|
||||
if should_unfold(fold) {
|
||||
if offset_range.end > offset_range.start {
|
||||
let inlay_range = snapshot.make_inlay_offset(offset_range.start)
|
||||
..snapshot.make_inlay_offset(offset_range.end);
|
||||
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
|
||||
..snapshot.to_inlay_offset(offset_range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
@@ -277,7 +275,7 @@ impl FoldMap {
|
||||
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
|
||||
let this = Self {
|
||||
snapshot: FoldSnapshot {
|
||||
folds: SumTree::new(inlay_snapshot.buffer()),
|
||||
folds: SumTree::new(&inlay_snapshot.buffer),
|
||||
transforms: SumTree::from_item(
|
||||
Transform {
|
||||
summary: TransformSummary {
|
||||
@@ -338,7 +336,9 @@ impl FoldMap {
|
||||
let mut folds = self.snapshot.folds.iter().peekable();
|
||||
while let Some(fold) = folds.next() {
|
||||
if let Some(next_fold) = folds.peek() {
|
||||
let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer());
|
||||
let comparison = fold
|
||||
.range
|
||||
.cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
|
||||
assert!(comparison.is_le());
|
||||
}
|
||||
}
|
||||
@@ -410,31 +410,31 @@ impl FoldMap {
|
||||
InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
|
||||
|
||||
let anchor = inlay_snapshot
|
||||
.buffer()
|
||||
.buffer
|
||||
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
|
||||
let mut folds_cursor = self
|
||||
.snapshot
|
||||
.folds
|
||||
.cursor::<FoldRange>(&inlay_snapshot.buffer());
|
||||
.cursor::<FoldRange>(&inlay_snapshot.buffer);
|
||||
folds_cursor.seek(
|
||||
&FoldRange(anchor..Anchor::max()),
|
||||
Bias::Left,
|
||||
&inlay_snapshot.buffer(),
|
||||
&inlay_snapshot.buffer,
|
||||
);
|
||||
|
||||
let mut folds = iter::from_fn({
|
||||
let inlay_snapshot = &inlay_snapshot;
|
||||
move || {
|
||||
let item = folds_cursor.item().map(|fold| {
|
||||
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer());
|
||||
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer());
|
||||
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
|
||||
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer);
|
||||
(
|
||||
fold.clone(),
|
||||
inlay_snapshot.make_inlay_offset(buffer_start)
|
||||
..inlay_snapshot.make_inlay_offset(buffer_end),
|
||||
inlay_snapshot.to_inlay_offset(buffer_start)
|
||||
..inlay_snapshot.to_inlay_offset(buffer_end),
|
||||
)
|
||||
});
|
||||
folds_cursor.next(&inlay_snapshot.buffer());
|
||||
folds_cursor.next(&inlay_snapshot.buffer);
|
||||
item
|
||||
}
|
||||
})
|
||||
@@ -578,10 +578,6 @@ pub struct FoldSnapshot {
|
||||
}
|
||||
|
||||
impl FoldSnapshot {
|
||||
pub fn buffer(&self) -> &MultiBufferSnapshot {
|
||||
self.inlay_snapshot.buffer()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
|
||||
@@ -591,7 +587,7 @@ impl FoldSnapshot {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn fold_count(&self) -> usize {
|
||||
self.folds.items(self.inlay_snapshot.buffer()).len()
|
||||
self.folds.items(&self.inlay_snapshot.buffer).len()
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
|
||||
@@ -645,32 +641,6 @@ impl FoldSnapshot {
|
||||
summary
|
||||
}
|
||||
|
||||
pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint {
|
||||
self.to_fold_point(self.inlay_snapshot.make_inlay_point(point), bias)
|
||||
}
|
||||
|
||||
pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset {
|
||||
self.to_fold_offset(self.inlay_snapshot.make_inlay_offset(buffer_offset), bias)
|
||||
}
|
||||
|
||||
pub fn to_fold_offset(&self, inlay_offset: InlayOffset, bias: Bias) -> FoldOffset {
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(&());
|
||||
cursor.seek(&inlay_offset, Bias::Right, &());
|
||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||
if bias == Bias::Left || inlay_offset == cursor.start().0 {
|
||||
cursor.start().1
|
||||
} else {
|
||||
cursor.end(&()).1
|
||||
}
|
||||
} else {
|
||||
let overshoot = inlay_offset.0 - cursor.start().0 .0;
|
||||
FoldOffset(cmp::min(
|
||||
cursor.start().1 .0 + overshoot,
|
||||
cursor.end(&()).1 .0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
@@ -703,7 +673,7 @@ impl FoldSnapshot {
|
||||
(line_end - line_start) as u32
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, start_row: u32) -> FoldRows {
|
||||
pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
|
||||
if start_row > self.transforms.summary().output.lines.row {
|
||||
panic!("invalid display row {}", start_row);
|
||||
}
|
||||
@@ -714,11 +684,11 @@ impl FoldSnapshot {
|
||||
|
||||
let overshoot = fold_point.0 - cursor.start().0 .0;
|
||||
let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
|
||||
let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
|
||||
let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
|
||||
|
||||
FoldRows {
|
||||
FoldBufferRows {
|
||||
fold_point,
|
||||
input_rows,
|
||||
input_buffer_rows,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
@@ -736,12 +706,12 @@ impl FoldSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer = self.inlay_snapshot.buffer();
|
||||
let buffer = &self.inlay_snapshot.buffer;
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
|
||||
iter::from_fn(move || {
|
||||
let item = folds.item();
|
||||
folds.next(buffer);
|
||||
folds.next(&self.inlay_snapshot.buffer);
|
||||
item
|
||||
})
|
||||
}
|
||||
@@ -750,8 +720,8 @@ impl FoldSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer_offset = offset.to_offset(self.inlay_snapshot.buffer());
|
||||
let inlay_offset = self.inlay_snapshot.make_inlay_offset(buffer_offset);
|
||||
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
|
||||
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
||||
let mut cursor = self.transforms.cursor::<InlayOffset>(&());
|
||||
cursor.seek(&inlay_offset, Bias::Right, &());
|
||||
cursor.item().map_or(false, |t| t.placeholder.is_some())
|
||||
@@ -760,7 +730,7 @@ impl FoldSnapshot {
|
||||
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
|
||||
let mut inlay_point = self
|
||||
.inlay_snapshot
|
||||
.make_inlay_point(Point::new(buffer_row.0, 0));
|
||||
.to_inlay_point(Point::new(buffer_row.0, 0));
|
||||
let mut cursor = self.transforms.cursor::<InlayPoint>(&());
|
||||
cursor.seek(&inlay_point, Bias::Right, &());
|
||||
loop {
|
||||
@@ -900,7 +870,7 @@ fn intersecting_folds<'a>(
|
||||
range: Range<usize>,
|
||||
inclusive: bool,
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
|
||||
@@ -1164,25 +1134,25 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FoldRows<'a> {
|
||||
pub struct FoldBufferRows<'a> {
|
||||
cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
|
||||
input_rows: InlayBufferRows<'a>,
|
||||
input_buffer_rows: InlayBufferRows<'a>,
|
||||
fold_point: FoldPoint,
|
||||
}
|
||||
|
||||
impl<'a> FoldRows<'a> {
|
||||
impl<'a> FoldBufferRows<'a> {
|
||||
pub(crate) fn seek(&mut self, row: u32) {
|
||||
let fold_point = FoldPoint::new(row, 0);
|
||||
self.cursor.seek(&fold_point, Bias::Left, &());
|
||||
let overshoot = fold_point.0 - self.cursor.start().0 .0;
|
||||
let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
|
||||
self.input_rows.seek(inlay_point.row());
|
||||
self.input_buffer_rows.seek(inlay_point.row());
|
||||
self.fold_point = fold_point;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FoldRows<'a> {
|
||||
type Item = RowInfo;
|
||||
impl<'a> Iterator for FoldBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut traversed_fold = false;
|
||||
@@ -1196,11 +1166,11 @@ impl<'a> Iterator for FoldRows<'a> {
|
||||
|
||||
if self.cursor.item().is_some() {
|
||||
if traversed_fold {
|
||||
self.input_rows.seek(self.cursor.start().1 .0.row);
|
||||
self.input_rows.next();
|
||||
self.input_buffer_rows.seek(self.cursor.start().1.row());
|
||||
self.input_buffer_rows.next();
|
||||
}
|
||||
*self.fold_point.row_mut() += 1;
|
||||
self.input_rows.next()
|
||||
self.input_buffer_rows.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1410,10 +1380,7 @@ pub type FoldEdit = Edit<FoldOffset>;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{diff_map::DiffMap, inlay_map::InlayMap},
|
||||
MultiBuffer, ToPoint,
|
||||
};
|
||||
use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
|
||||
use collections::HashSet;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
@@ -1428,8 +1395,8 @@ mod tests {
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
|
||||
@@ -1464,14 +1431,8 @@ mod tests {
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer_snapshot.clone(),
|
||||
subscription.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
|
||||
assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
|
||||
assert_eq!(
|
||||
@@ -1492,11 +1453,8 @@ mod tests {
|
||||
buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
|
||||
assert_eq!(snapshot4.text(), "123a⋯c123456eee");
|
||||
|
||||
@@ -1516,8 +1474,8 @@ mod tests {
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
|
||||
{
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
@@ -1563,10 +1521,8 @@ mod tests {
|
||||
buffer.edit([(0..1, "12345")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
|
||||
assert_eq!(snapshot.text(), "12345⋯fghijkl");
|
||||
}
|
||||
@@ -1575,8 +1531,8 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_overlapping_folds(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.fold(vec![
|
||||
@@ -1594,8 +1550,8 @@ mod tests {
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1610,10 +1566,8 @@ mod tests {
|
||||
buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
|
||||
let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
|
||||
assert_eq!(snapshot.text(), "aa⋯eeeee");
|
||||
}
|
||||
@@ -1622,8 +1576,7 @@ mod tests {
|
||||
fn test_folds_in_range(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1665,8 +1618,7 @@ mod tests {
|
||||
MultiBuffer::build_random(&mut rng, cx)
|
||||
};
|
||||
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1696,10 +1648,8 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, new_inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, new_inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
log::info!("inlay text {:?}", inlay_snapshot.text());
|
||||
|
||||
let inlay_edits = Patch::new(inlay_edits)
|
||||
@@ -1710,8 +1660,8 @@ mod tests {
|
||||
|
||||
let mut expected_text: String = inlay_snapshot.text().to_string();
|
||||
for fold_range in map.merged_folds().into_iter().rev() {
|
||||
let fold_inlay_start = inlay_snapshot.make_inlay_offset(fold_range.start);
|
||||
let fold_inlay_end = inlay_snapshot.make_inlay_offset(fold_range.end);
|
||||
let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
|
||||
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
|
||||
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
|
||||
}
|
||||
|
||||
@@ -1726,19 +1676,19 @@ mod tests {
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
for fold_range in map.merged_folds() {
|
||||
let fold_start = inlay_snapshot
|
||||
.to_point(inlay_snapshot.make_inlay_offset(fold_range.start))
|
||||
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
|
||||
.row();
|
||||
let fold_end = inlay_snapshot
|
||||
.to_point(inlay_snapshot.make_inlay_offset(fold_range.end))
|
||||
.to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
|
||||
.row();
|
||||
expected_buffer_rows.extend(
|
||||
inlay_snapshot
|
||||
.row_infos(prev_row)
|
||||
.buffer_rows(prev_row)
|
||||
.take((1 + fold_start - prev_row) as usize),
|
||||
);
|
||||
prev_row = 1 + fold_end;
|
||||
}
|
||||
expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
|
||||
expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
|
||||
|
||||
assert_eq!(
|
||||
expected_buffer_rows.len(),
|
||||
@@ -1827,7 +1777,7 @@ mod tests {
|
||||
let mut fold_row = 0;
|
||||
while fold_row < expected_buffer_rows.len() as u32 {
|
||||
assert_eq!(
|
||||
snapshot.row_infos(fold_row).collect::<Vec<_>>(),
|
||||
snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
|
||||
expected_buffer_rows[(fold_row as usize)..],
|
||||
"wrong buffer rows starting at fold row {}",
|
||||
fold_row,
|
||||
@@ -1929,8 +1879,8 @@ mod tests {
|
||||
let text = sample_text(6, 6, 'a') + "\n";
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
|
||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
@@ -1942,19 +1892,10 @@ mod tests {
|
||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.row_infos(0)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
snapshot.buffer_rows(0).collect::<Vec<_>>(),
|
||||
[Some(0), Some(3), Some(5), Some(6)]
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.row_infos(3)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
[Some(6)]
|
||||
);
|
||||
assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
@@ -1965,7 +1906,7 @@ mod tests {
|
||||
impl FoldMap {
|
||||
fn merged_folds(&self) -> Vec<Range<usize>> {
|
||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let mut folds = self.snapshot.folds.items(buffer);
|
||||
// Ensure sorting doesn't change how folds get merged and displayed.
|
||||
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||
@@ -2001,7 +1942,7 @@ mod tests {
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=39 if !self.snapshot.folds.is_empty() => {
|
||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let mut to_unfold = Vec::new();
|
||||
for _ in 0..rng.gen_range(1..=3) {
|
||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||
@@ -2017,7 +1958,7 @@ mod tests {
|
||||
}
|
||||
_ => {
|
||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||
let buffer = inlay_snapshot.buffer();
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let mut to_fold = Vec::new();
|
||||
for _ in 0..rng.gen_range(1..=2) {
|
||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{HighlightStyles, InlayId, RowInfo};
|
||||
use crate::{HighlightStyles, InlayId};
|
||||
use collections::BTreeSet;
|
||||
use language::{Chunk, Edit, Point, TextSummary};
|
||||
use multi_buffer::{Anchor, MultiBufferSnapshot, ToOffset};
|
||||
use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset};
|
||||
use std::{
|
||||
cmp,
|
||||
ops::{Add, AddAssign, Range, Sub, SubAssign},
|
||||
@@ -9,10 +9,7 @@ use std::{
|
||||
use sum_tree::{Bias, Cursor, SumTree};
|
||||
use text::{Patch, Rope};
|
||||
|
||||
use super::{
|
||||
diff_map::{DiffEdit, DiffMapChunks, DiffMapRows, DiffMapSnapshot, DiffOffset, DiffPoint},
|
||||
Highlights,
|
||||
};
|
||||
use super::{custom_highlights::CustomHighlightsChunks, Highlights};
|
||||
|
||||
/// Decides where the [`Inlay`]s should be displayed.
|
||||
///
|
||||
@@ -24,7 +21,7 @@ pub struct InlayMap {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InlaySnapshot {
|
||||
pub diff_map_snapshot: DiffMapSnapshot,
|
||||
pub buffer: MultiBufferSnapshot,
|
||||
transforms: SumTree<Transform>,
|
||||
pub version: usize,
|
||||
}
|
||||
@@ -175,37 +172,37 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffOffset {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
DiffOffset(0)
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.input.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffPoint {
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
self.0 += &summary.input.lines;
|
||||
*self += &summary.input.len;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||
*self += &summary.input.lines;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InlayBufferRows<'a> {
|
||||
transforms: Cursor<'a, Transform, (InlayPoint, DiffPoint)>,
|
||||
diff_rows: DiffMapRows<'a>,
|
||||
transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
|
||||
buffer_rows: MultiBufferRows<'a>,
|
||||
inlay_row: u32,
|
||||
max_point: DiffPoint,
|
||||
max_buffer_row: MultiBufferRow,
|
||||
}
|
||||
|
||||
pub struct InlayChunks<'a> {
|
||||
transforms: Cursor<'a, Transform, (InlayOffset, DiffOffset)>,
|
||||
diff_map_chunks: DiffMapChunks<'a>,
|
||||
transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
|
||||
buffer_chunks: CustomHighlightsChunks<'a>,
|
||||
buffer_chunk: Option<Chunk<'a>>,
|
||||
inlay_chunks: Option<text::Chunks<'a>>,
|
||||
inlay_chunk: Option<&'a str>,
|
||||
@@ -217,21 +214,21 @@ pub struct InlayChunks<'a> {
|
||||
}
|
||||
|
||||
impl<'a> InlayChunks<'a> {
|
||||
pub fn offset(&self) -> InlayOffset {
|
||||
self.output_offset
|
||||
}
|
||||
|
||||
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
|
||||
self.transforms.seek(&new_range.start, Bias::Right, &());
|
||||
|
||||
let diff_range = self.snapshot.to_diff_offset(new_range.start)
|
||||
..self.snapshot.to_diff_offset(new_range.end);
|
||||
self.diff_map_chunks.seek(diff_range);
|
||||
let buffer_range = self.snapshot.to_buffer_offset(new_range.start)
|
||||
..self.snapshot.to_buffer_offset(new_range.end);
|
||||
self.buffer_chunks.seek(buffer_range);
|
||||
self.inlay_chunks = None;
|
||||
self.buffer_chunk = None;
|
||||
self.output_offset = new_range.start;
|
||||
self.max_output_offset = new_range.end;
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> InlayOffset {
|
||||
self.output_offset
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InlayChunks<'a> {
|
||||
@@ -246,9 +243,9 @@ impl<'a> Iterator for InlayChunks<'a> {
|
||||
Transform::Isomorphic(_) => {
|
||||
let chunk = self
|
||||
.buffer_chunk
|
||||
.get_or_insert_with(|| self.diff_map_chunks.next().unwrap());
|
||||
.get_or_insert_with(|| self.buffer_chunks.next().unwrap());
|
||||
if chunk.text.is_empty() {
|
||||
*chunk = self.diff_map_chunks.next().unwrap();
|
||||
*chunk = self.buffer_chunks.next().unwrap();
|
||||
}
|
||||
|
||||
let (prefix, suffix) = chunk.text.split_at(
|
||||
@@ -347,33 +344,33 @@ impl<'a> InlayBufferRows<'a> {
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
self.transforms.seek(&inlay_point, Bias::Left, &());
|
||||
|
||||
let mut diff_point = self.transforms.start().1;
|
||||
let row = if row == 0 {
|
||||
let mut buffer_point = self.transforms.start().1;
|
||||
let buffer_row = MultiBufferRow(if row == 0 {
|
||||
0
|
||||
} else {
|
||||
match self.transforms.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
diff_point.0 += inlay_point.0 - self.transforms.start().0 .0;
|
||||
diff_point.row()
|
||||
buffer_point += inlay_point.0 - self.transforms.start().0 .0;
|
||||
buffer_point.row
|
||||
}
|
||||
_ => cmp::min(diff_point.row() + 1, self.max_point.row()),
|
||||
_ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0),
|
||||
}
|
||||
};
|
||||
});
|
||||
self.inlay_row = inlay_point.row();
|
||||
self.diff_rows.seek(row);
|
||||
self.buffer_rows.seek(buffer_row);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for InlayBufferRows<'a> {
|
||||
type Item = RowInfo;
|
||||
type Item = Option<u32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let row_info = if self.inlay_row == 0 {
|
||||
self.diff_rows.next().unwrap()
|
||||
let buffer_row = if self.inlay_row == 0 {
|
||||
self.buffer_rows.next().unwrap()
|
||||
} else {
|
||||
match self.transforms.item()? {
|
||||
Transform::Inlay(_) => Default::default(),
|
||||
Transform::Isomorphic(_) => self.diff_rows.next().unwrap(),
|
||||
Transform::Inlay(_) => None,
|
||||
Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -381,7 +378,7 @@ impl<'a> Iterator for InlayBufferRows<'a> {
|
||||
self.transforms
|
||||
.seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
|
||||
|
||||
Some(row_info)
|
||||
Some(buffer_row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,14 +393,11 @@ impl InlayPoint {
|
||||
}
|
||||
|
||||
impl InlayMap {
|
||||
pub fn new(diff_map_snapshot: DiffMapSnapshot) -> (Self, InlaySnapshot) {
|
||||
pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
|
||||
let version = 0;
|
||||
let snapshot = InlaySnapshot {
|
||||
transforms: SumTree::from_iter(
|
||||
Some(Transform::Isomorphic(diff_map_snapshot.text_summary())),
|
||||
&(),
|
||||
),
|
||||
diff_map_snapshot,
|
||||
buffer: buffer.clone(),
|
||||
transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
|
||||
version,
|
||||
};
|
||||
|
||||
@@ -418,121 +412,133 @@ impl InlayMap {
|
||||
|
||||
pub fn sync(
|
||||
&mut self,
|
||||
diff_map_snapshot: DiffMapSnapshot,
|
||||
mut diff_edits: Vec<DiffEdit>,
|
||||
buffer_snapshot: MultiBufferSnapshot,
|
||||
mut buffer_edits: Vec<text::Edit<usize>>,
|
||||
) -> (InlaySnapshot, Vec<InlayEdit>) {
|
||||
let snapshot = &mut self.snapshot;
|
||||
|
||||
if diff_edits.is_empty()
|
||||
&& snapshot.buffer().trailing_excerpt_update_count()
|
||||
!= diff_map_snapshot.buffer().trailing_excerpt_update_count()
|
||||
if buffer_edits.is_empty()
|
||||
&& snapshot.buffer.trailing_excerpt_update_count()
|
||||
!= buffer_snapshot.trailing_excerpt_update_count()
|
||||
{
|
||||
diff_edits.push(Edit {
|
||||
old: snapshot.diff_map_snapshot.len()..snapshot.diff_map_snapshot.len(),
|
||||
new: diff_map_snapshot.len()..diff_map_snapshot.len(),
|
||||
buffer_edits.push(Edit {
|
||||
old: snapshot.buffer.len()..snapshot.buffer.len(),
|
||||
new: buffer_snapshot.len()..buffer_snapshot.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut inlay_edits = Patch::default();
|
||||
let mut new_transforms = SumTree::default();
|
||||
let mut cursor = snapshot.transforms.cursor::<(DiffOffset, InlayOffset)>(&());
|
||||
let mut diff_edits_iter = diff_edits.iter().peekable();
|
||||
while let Some(diff_edit) = diff_edits_iter.next() {
|
||||
new_transforms.append(cursor.slice(&diff_edit.old.start, Bias::Left, &()), &());
|
||||
if let Some(Transform::Isomorphic(transform)) = cursor.item() {
|
||||
if cursor.end(&()).0 == diff_edit.old.start {
|
||||
push_isomorphic(&mut new_transforms, transform.clone());
|
||||
if buffer_edits.is_empty() {
|
||||
if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
|
||||
|| snapshot.buffer.non_text_state_update_count()
|
||||
!= buffer_snapshot.non_text_state_update_count()
|
||||
|| snapshot.buffer.trailing_excerpt_update_count()
|
||||
!= buffer_snapshot.trailing_excerpt_update_count()
|
||||
{
|
||||
snapshot.version += 1;
|
||||
}
|
||||
|
||||
snapshot.buffer = buffer_snapshot;
|
||||
(snapshot.clone(), Vec::new())
|
||||
} else {
|
||||
let mut inlay_edits = Patch::default();
|
||||
let mut new_transforms = SumTree::default();
|
||||
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&());
|
||||
let mut buffer_edits_iter = buffer_edits.iter().peekable();
|
||||
while let Some(buffer_edit) = buffer_edits_iter.next() {
|
||||
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
|
||||
if let Some(Transform::Isomorphic(transform)) = cursor.item() {
|
||||
if cursor.end(&()).0 == buffer_edit.old.start {
|
||||
push_isomorphic(&mut new_transforms, transform.clone());
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all the inlays and transforms contained by the edit.
|
||||
let old_start =
|
||||
cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
|
||||
cursor.seek(&buffer_edit.old.end, Bias::Right, &());
|
||||
let old_end =
|
||||
cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
|
||||
|
||||
// Push the unchanged prefix.
|
||||
let prefix_start = new_transforms.summary().input.len;
|
||||
let prefix_end = buffer_edit.new.start;
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
|
||||
);
|
||||
let new_start = InlayOffset(new_transforms.summary().output.len);
|
||||
|
||||
let start_ix = match self.inlays.binary_search_by(|probe| {
|
||||
probe
|
||||
.position
|
||||
.to_offset(&buffer_snapshot)
|
||||
.cmp(&buffer_edit.new.start)
|
||||
.then(std::cmp::Ordering::Greater)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
|
||||
for inlay in &self.inlays[start_ix..] {
|
||||
let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
|
||||
if buffer_offset > buffer_edit.new.end {
|
||||
break;
|
||||
}
|
||||
|
||||
let prefix_start = new_transforms.summary().input.len;
|
||||
let prefix_end = buffer_offset;
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
|
||||
);
|
||||
|
||||
if inlay.position.is_valid(&buffer_snapshot) {
|
||||
new_transforms.push(Transform::Inlay(inlay.clone()), &());
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the rest of the edit.
|
||||
let transform_start = new_transforms.summary().input.len;
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
|
||||
);
|
||||
let new_end = InlayOffset(new_transforms.summary().output.len);
|
||||
inlay_edits.push(Edit {
|
||||
old: old_start..old_end,
|
||||
new: new_start..new_end,
|
||||
});
|
||||
|
||||
// If the next edit doesn't intersect the current isomorphic transform, then
|
||||
// we can push its remainder.
|
||||
if buffer_edits_iter
|
||||
.peek()
|
||||
.map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
|
||||
{
|
||||
let transform_start = new_transforms.summary().input.len;
|
||||
let transform_end =
|
||||
buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
buffer_snapshot.text_summary_for_range(transform_start..transform_end),
|
||||
);
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all the inlays and transforms contained by the edit.
|
||||
let old_start =
|
||||
cursor.start().1 + InlayOffset((diff_edit.old.start - cursor.start().0).0);
|
||||
cursor.seek(&diff_edit.old.end, Bias::Right, &());
|
||||
let old_end = cursor.start().1 + InlayOffset((diff_edit.old.end - cursor.start().0).0);
|
||||
|
||||
// Push the unchanged prefix.
|
||||
let prefix_start = DiffOffset(new_transforms.summary().input.len);
|
||||
let prefix_end = diff_edit.new.start;
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end),
|
||||
);
|
||||
let new_start = InlayOffset(new_transforms.summary().output.len);
|
||||
|
||||
let edit_buffer_range = diff_map_snapshot.to_multibuffer_offset(diff_edit.new.start)
|
||||
..diff_map_snapshot.to_multibuffer_offset(diff_edit.new.end);
|
||||
|
||||
let start_ix = match self.inlays.binary_search_by(|probe| {
|
||||
probe
|
||||
.position
|
||||
.to_offset(diff_map_snapshot.buffer())
|
||||
.cmp(&edit_buffer_range.start)
|
||||
.then(std::cmp::Ordering::Greater)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
|
||||
for inlay in &self.inlays[start_ix..] {
|
||||
let buffer_offset = inlay.position.to_offset(&diff_map_snapshot.buffer);
|
||||
let diff_offset = diff_map_snapshot.to_diff_offset(buffer_offset);
|
||||
if buffer_offset > edit_buffer_range.end {
|
||||
break;
|
||||
}
|
||||
|
||||
let prefix_start = DiffOffset(new_transforms.summary().input.len);
|
||||
let prefix_end = diff_offset;
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end),
|
||||
);
|
||||
|
||||
if inlay.position.is_valid(&diff_map_snapshot.buffer) {
|
||||
new_transforms.push(Transform::Inlay(inlay.clone()), &());
|
||||
}
|
||||
new_transforms.append(cursor.suffix(&()), &());
|
||||
if new_transforms.is_empty() {
|
||||
new_transforms.push(Transform::Isomorphic(Default::default()), &());
|
||||
}
|
||||
|
||||
// Apply the rest of the edit.
|
||||
let transform_start = DiffOffset(new_transforms.summary().input.len);
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
diff_map_snapshot.text_summary_for_range(transform_start..diff_edit.new.end),
|
||||
);
|
||||
let new_end = InlayOffset(new_transforms.summary().output.len);
|
||||
inlay_edits.push(Edit {
|
||||
old: old_start..old_end,
|
||||
new: new_start..new_end,
|
||||
});
|
||||
drop(cursor);
|
||||
snapshot.transforms = new_transforms;
|
||||
snapshot.version += 1;
|
||||
snapshot.buffer = buffer_snapshot;
|
||||
snapshot.check_invariants();
|
||||
|
||||
// If the next edit doesn't intersect the current isomorphic transform, then
|
||||
// we can push its remainder.
|
||||
if diff_edits_iter
|
||||
.peek()
|
||||
.map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
|
||||
{
|
||||
let transform_start = DiffOffset(new_transforms.summary().input.len);
|
||||
let transform_end = diff_edit.new.end + (cursor.end(&()).0 - diff_edit.old.end);
|
||||
push_isomorphic(
|
||||
&mut new_transforms,
|
||||
diff_map_snapshot.text_summary_for_range(transform_start..transform_end),
|
||||
);
|
||||
cursor.next(&());
|
||||
}
|
||||
(snapshot.clone(), inlay_edits.into_inner())
|
||||
}
|
||||
|
||||
new_transforms.append(cursor.suffix(&()), &());
|
||||
if new_transforms.is_empty() {
|
||||
new_transforms.push(Transform::Isomorphic(Default::default()), &());
|
||||
}
|
||||
|
||||
drop(cursor);
|
||||
snapshot.transforms = new_transforms;
|
||||
snapshot.version += 1;
|
||||
snapshot.diff_map_snapshot = diff_map_snapshot;
|
||||
snapshot.check_invariants();
|
||||
|
||||
(snapshot.clone(), inlay_edits.into_inner())
|
||||
}
|
||||
|
||||
pub fn splice(
|
||||
@@ -546,9 +552,7 @@ impl InlayMap {
|
||||
self.inlays.retain(|inlay| {
|
||||
let retain = !to_remove.contains(&inlay.id);
|
||||
if !retain {
|
||||
let offset = snapshot
|
||||
.diff_map_snapshot
|
||||
.to_diff_offset(inlay.position.to_offset(&snapshot.diff_map_snapshot.buffer));
|
||||
let offset = inlay.position.to_offset(&snapshot.buffer);
|
||||
edits.insert(offset);
|
||||
}
|
||||
retain
|
||||
@@ -560,16 +564,11 @@ impl InlayMap {
|
||||
continue;
|
||||
}
|
||||
|
||||
let offset = snapshot.diff_map_snapshot.to_diff_offset(
|
||||
inlay_to_insert
|
||||
.position
|
||||
.to_offset(&snapshot.diff_map_snapshot.buffer),
|
||||
);
|
||||
let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
|
||||
match self.inlays.binary_search_by(|probe| {
|
||||
probe.position.cmp(
|
||||
&inlay_to_insert.position,
|
||||
&snapshot.diff_map_snapshot.buffer,
|
||||
)
|
||||
probe
|
||||
.position
|
||||
.cmp(&inlay_to_insert.position, &snapshot.buffer)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => {
|
||||
self.inlays.insert(ix, inlay_to_insert);
|
||||
@@ -586,7 +585,7 @@ impl InlayMap {
|
||||
new: offset..offset,
|
||||
})
|
||||
.collect();
|
||||
let buffer_snapshot = snapshot.diff_map_snapshot.clone();
|
||||
let buffer_snapshot = snapshot.buffer.clone();
|
||||
let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
|
||||
(snapshot, edits)
|
||||
}
|
||||
@@ -609,8 +608,7 @@ impl InlayMap {
|
||||
let snapshot = &mut self.snapshot;
|
||||
for i in 0..rng.gen_range(1..=5) {
|
||||
if self.inlays.is_empty() || rng.gen() {
|
||||
let position = snapshot.buffer().random_byte_range(0, rng).start;
|
||||
let anchor = snapshot.buffer().anchor_at(position, Bias::Left);
|
||||
let position = snapshot.buffer.random_byte_range(0, rng).start;
|
||||
let bias = if rng.gen() { Bias::Left } else { Bias::Right };
|
||||
let len = if rng.gen_bool(0.01) {
|
||||
0
|
||||
@@ -628,7 +626,7 @@ impl InlayMap {
|
||||
InlayId::InlineCompletion(post_inc(next_inlay_id))
|
||||
};
|
||||
log::info!(
|
||||
"creating inlay {:?} at buffer offset {:?} with bias {:?} and text {:?}",
|
||||
"creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
|
||||
inlay_id,
|
||||
position,
|
||||
bias,
|
||||
@@ -637,7 +635,7 @@ impl InlayMap {
|
||||
|
||||
to_insert.push(Inlay {
|
||||
id: inlay_id,
|
||||
position: anchor,
|
||||
position: snapshot.buffer.anchor_at(position, bias),
|
||||
text: text.into(),
|
||||
});
|
||||
} else {
|
||||
@@ -661,16 +659,16 @@ impl InlaySnapshot {
|
||||
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<(InlayOffset, (InlayPoint, DiffOffset))>(&());
|
||||
.cursor::<(InlayOffset, (InlayPoint, usize))>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
let overshoot = offset.0 - cursor.start().0 .0;
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
let buffer_offset_start = cursor.start().1 .1;
|
||||
let buffer_offset_end = buffer_offset_start + DiffOffset(overshoot);
|
||||
let buffer_start = self.diff_map_snapshot.offset_to_point(buffer_offset_start);
|
||||
let buffer_end = self.diff_map_snapshot.offset_to_point(buffer_offset_end);
|
||||
cursor.start().1 .0 + InlayPoint(buffer_end.0 - buffer_start.0)
|
||||
let buffer_offset_end = buffer_offset_start + overshoot;
|
||||
let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
|
||||
let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
|
||||
InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
|
||||
}
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
let overshoot = inlay.text.offset_to_point(overshoot);
|
||||
@@ -691,68 +689,51 @@ impl InlaySnapshot {
|
||||
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
|
||||
let mut cursor = self
|
||||
.transforms
|
||||
.cursor::<(InlayPoint, (InlayOffset, DiffPoint))>(&());
|
||||
.cursor::<(InlayPoint, (InlayOffset, Point))>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
let overshoot = point.0 - cursor.start().0 .0;
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
let buffer_point_start = cursor.start().1 .1;
|
||||
let buffer_point_end = buffer_point_start + DiffPoint(overshoot);
|
||||
let buffer_offset_start =
|
||||
self.diff_map_snapshot.point_to_offset(buffer_point_start);
|
||||
let buffer_offset_end = self.diff_map_snapshot.point_to_offset(buffer_point_end);
|
||||
cursor.start().1 .0 + InlayOffset((buffer_offset_end - buffer_offset_start).0)
|
||||
let buffer_point_end = buffer_point_start + overshoot;
|
||||
let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
|
||||
let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
|
||||
InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
|
||||
}
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
let overshoot = inlay.text.point_to_offset(overshoot);
|
||||
cursor.start().1 .0 + InlayOffset(overshoot)
|
||||
InlayOffset(cursor.start().1 .0 .0 + overshoot)
|
||||
}
|
||||
None => self.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_diff_point(&self, point: InlayPoint) -> DiffPoint {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&());
|
||||
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
let overshoot = point.0 - cursor.start().0 .0;
|
||||
cursor.start().1 + DiffPoint(overshoot)
|
||||
cursor.start().1 + overshoot
|
||||
}
|
||||
Some(Transform::Inlay(_)) => cursor.start().1,
|
||||
None => self.diff_map_snapshot.max_point(),
|
||||
None => self.buffer.max_point(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_diff_offset(&self, offset: InlayOffset) -> DiffOffset {
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&());
|
||||
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
let overshoot = offset - cursor.start().0;
|
||||
cursor.start().1 + DiffOffset(overshoot.0)
|
||||
cursor.start().1 + overshoot.0
|
||||
}
|
||||
Some(Transform::Inlay(_)) => cursor.start().1,
|
||||
None => self.diff_map_snapshot.len(),
|
||||
None => self.buffer.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &MultiBufferSnapshot {
|
||||
&self.diff_map_snapshot.buffer
|
||||
}
|
||||
|
||||
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
|
||||
self.diff_map_snapshot
|
||||
.to_multibuffer_point(self.to_diff_point(point))
|
||||
}
|
||||
|
||||
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
|
||||
self.diff_map_snapshot
|
||||
.to_multibuffer_offset(self.to_diff_offset(offset))
|
||||
}
|
||||
|
||||
pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset {
|
||||
let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&());
|
||||
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
|
||||
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
|
||||
cursor.seek(&offset, Bias::Left, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -767,7 +748,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
return cursor.end(&()).1;
|
||||
} else {
|
||||
let overshoot = offset.0 - cursor.start().0 .0;
|
||||
let overshoot = offset - cursor.start().0;
|
||||
return InlayOffset(cursor.start().1 .0 + overshoot);
|
||||
}
|
||||
}
|
||||
@@ -784,17 +765,8 @@ impl InlaySnapshot {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_inlay_point(&self, multibuffer_point: Point) -> InlayPoint {
|
||||
self.to_inlay_point(self.diff_map_snapshot.to_diff_point(multibuffer_point))
|
||||
}
|
||||
|
||||
pub fn make_inlay_offset(&self, multibuffer_point: usize) -> InlayOffset {
|
||||
self.to_inlay_offset(self.diff_map_snapshot.to_diff_offset(multibuffer_point))
|
||||
}
|
||||
|
||||
pub fn to_inlay_point(&self, point: DiffPoint) -> InlayPoint {
|
||||
let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&());
|
||||
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
|
||||
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -810,7 +782,7 @@ impl InlaySnapshot {
|
||||
return cursor.end(&()).1;
|
||||
} else {
|
||||
let overshoot = point - cursor.start().0;
|
||||
return InlayPoint(cursor.start().1 .0 + overshoot.0);
|
||||
return InlayPoint(cursor.start().1 .0 + overshoot);
|
||||
}
|
||||
}
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
@@ -828,7 +800,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
|
||||
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
loop {
|
||||
match cursor.item() {
|
||||
@@ -864,11 +836,10 @@ impl InlaySnapshot {
|
||||
}
|
||||
} else {
|
||||
let overshoot = point.0 - cursor.start().0 .0;
|
||||
let diff_point = cursor.start().1 + DiffPoint(overshoot);
|
||||
let clipped_diff_point =
|
||||
self.diff_map_snapshot.clip_point(diff_point, bias);
|
||||
let clipped_overshoot = clipped_diff_point - cursor.start().1;
|
||||
let clipped_point = cursor.start().0 + InlayPoint(clipped_overshoot.0);
|
||||
let buffer_point = cursor.start().1 + overshoot;
|
||||
let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
|
||||
let clipped_overshoot = clipped_buffer_point - cursor.start().1;
|
||||
let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
|
||||
if clipped_point == point {
|
||||
return clipped_point;
|
||||
} else {
|
||||
@@ -926,19 +897,17 @@ impl InlaySnapshot {
|
||||
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
|
||||
let mut summary = TextSummary::default();
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
|
||||
let overshoot = range.start.0 - cursor.start().0 .0;
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
let buffer_start = cursor.start().1;
|
||||
let suffix_start = buffer_start + DiffOffset(overshoot);
|
||||
let suffix_end = buffer_start
|
||||
+ DiffOffset(cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
|
||||
summary = self
|
||||
.diff_map_snapshot
|
||||
.text_summary_for_range(suffix_start..suffix_end);
|
||||
let suffix_start = buffer_start + overshoot;
|
||||
let suffix_end =
|
||||
buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
|
||||
summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
|
||||
cursor.next(&());
|
||||
}
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
@@ -959,10 +928,10 @@ impl InlaySnapshot {
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
let prefix_start = cursor.start().1;
|
||||
let prefix_end = prefix_start + DiffOffset(overshoot);
|
||||
let prefix_end = prefix_start + overshoot;
|
||||
summary += self
|
||||
.diff_map_snapshot
|
||||
.text_summary_for_range(prefix_start..prefix_end);
|
||||
.buffer
|
||||
.text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
|
||||
}
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
let prefix_end = overshoot;
|
||||
@@ -975,30 +944,30 @@ impl InlaySnapshot {
|
||||
summary
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&());
|
||||
pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
cursor.seek(&inlay_point, Bias::Left, &());
|
||||
|
||||
let max_point = self.diff_map_snapshot.max_point();
|
||||
let mut diff_point = cursor.start().1;
|
||||
let diff_row = if row == 0 {
|
||||
0
|
||||
let max_buffer_row = self.buffer.max_row();
|
||||
let mut buffer_point = cursor.start().1;
|
||||
let buffer_row = if row == 0 {
|
||||
MultiBufferRow(0)
|
||||
} else {
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(_)) => {
|
||||
diff_point += DiffPoint(inlay_point.0 - cursor.start().0 .0);
|
||||
diff_point.row()
|
||||
buffer_point += inlay_point.0 - cursor.start().0 .0;
|
||||
MultiBufferRow(buffer_point.row)
|
||||
}
|
||||
_ => cmp::min(diff_point.row() + 1, max_point.row()),
|
||||
_ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
|
||||
}
|
||||
};
|
||||
|
||||
InlayBufferRows {
|
||||
transforms: cursor,
|
||||
inlay_row: inlay_point.row(),
|
||||
diff_rows: self.diff_map_snapshot.row_infos(diff_row),
|
||||
max_point,
|
||||
buffer_rows: self.buffer.buffer_rows(buffer_row),
|
||||
max_buffer_row,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,17 +987,20 @@ impl InlaySnapshot {
|
||||
language_aware: bool,
|
||||
highlights: Highlights<'a>,
|
||||
) -> InlayChunks<'a> {
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&());
|
||||
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
|
||||
let diff_range = self.to_diff_offset(range.start)..self.to_diff_offset(range.end);
|
||||
let diff_map_chunks =
|
||||
self.diff_map_snapshot
|
||||
.chunks(diff_range, language_aware, highlights.text_highlights);
|
||||
let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
|
||||
let buffer_chunks = CustomHighlightsChunks::new(
|
||||
buffer_range,
|
||||
language_aware,
|
||||
highlights.text_highlights,
|
||||
&self.buffer,
|
||||
);
|
||||
|
||||
InlayChunks {
|
||||
transforms: cursor,
|
||||
diff_map_chunks,
|
||||
buffer_chunks,
|
||||
inlay_chunks: None,
|
||||
inlay_chunk: None,
|
||||
buffer_chunk: None,
|
||||
@@ -1050,10 +1022,7 @@ impl InlaySnapshot {
|
||||
fn check_invariants(&self) {
|
||||
#[cfg(any(debug_assertions, feature = "test-support"))]
|
||||
{
|
||||
assert_eq!(
|
||||
self.transforms.summary().input,
|
||||
self.diff_map_snapshot.text_summary()
|
||||
);
|
||||
assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
|
||||
let mut transforms = self.transforms.iter().peekable();
|
||||
while let Some(transform) = transforms.next() {
|
||||
let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
|
||||
@@ -1094,7 +1063,7 @@ fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{DiffMap, InlayHighlights, TextHighlights},
|
||||
display_map::{InlayHighlights, TextHighlights},
|
||||
hover_links::InlayHighlight,
|
||||
InlayId, MultiBuffer,
|
||||
};
|
||||
@@ -1194,8 +1163,7 @@ mod tests {
|
||||
fn test_basic_inlays(cx: &mut AppContext) {
|
||||
let buffer = MultiBuffer::build_simple("abcdefghi", cx);
|
||||
let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let (diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_map_snapshot.clone());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
|
||||
assert_eq!(inlay_snapshot.text(), "abcdefghi");
|
||||
let mut next_inlay_id = 0;
|
||||
|
||||
@@ -1209,27 +1177,27 @@ mod tests {
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
|
||||
assert_eq!(
|
||||
inlay_snapshot.make_inlay_point(Point::new(0, 0)),
|
||||
inlay_snapshot.to_inlay_point(Point::new(0, 0)),
|
||||
InlayPoint::new(0, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
inlay_snapshot.make_inlay_point(Point::new(0, 1)),
|
||||
inlay_snapshot.to_inlay_point(Point::new(0, 1)),
|
||||
InlayPoint::new(0, 1)
|
||||
);
|
||||
assert_eq!(
|
||||
inlay_snapshot.make_inlay_point(Point::new(0, 2)),
|
||||
inlay_snapshot.to_inlay_point(Point::new(0, 2)),
|
||||
InlayPoint::new(0, 2)
|
||||
);
|
||||
assert_eq!(
|
||||
inlay_snapshot.make_inlay_point(Point::new(0, 3)),
|
||||
inlay_snapshot.to_inlay_point(Point::new(0, 3)),
|
||||
InlayPoint::new(0, 3)
|
||||
);
|
||||
assert_eq!(
|
||||
inlay_snapshot.make_inlay_point(Point::new(0, 4)),
|
||||
inlay_snapshot.to_inlay_point(Point::new(0, 4)),
|
||||
InlayPoint::new(0, 9)
|
||||
);
|
||||
assert_eq!(
|
||||
inlay_snapshot.make_inlay_point(Point::new(0, 5)),
|
||||
inlay_snapshot.to_inlay_point(Point::new(0, 5)),
|
||||
InlayPoint::new(0, 10)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1261,26 +1229,18 @@ mod tests {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
|
||||
});
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, _) = inlay_map.sync(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
|
||||
|
||||
// An edit surrounding the inlay should invalidate it.
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, _) = inlay_map.sync(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
|
||||
|
||||
let (inlay_snapshot, _) = inlay_map.splice(
|
||||
@@ -1302,16 +1262,10 @@ mod tests {
|
||||
|
||||
// Edits ending where the inlay starts should not move it if it has a left bias.
|
||||
buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
|
||||
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, _) = inlay_map.sync(
|
||||
buffer.read(cx).snapshot(cx),
|
||||
buffer_edits.consume().into_inner(),
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
|
||||
|
||||
assert_eq!(
|
||||
@@ -1496,8 +1450,7 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_inlay_buffer_rows(cx: &mut AppContext) {
|
||||
let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
|
||||
let (_diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_map_snapshot);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
|
||||
assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
|
||||
let mut next_inlay_id = 0;
|
||||
|
||||
@@ -1523,10 +1476,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
|
||||
assert_eq!(
|
||||
inlay_snapshot
|
||||
.row_infos(0)
|
||||
.map(|r| r.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
|
||||
vec![Some(0), None, Some(1), None, None, Some(2)]
|
||||
);
|
||||
}
|
||||
@@ -1551,8 +1501,7 @@ mod tests {
|
||||
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let mut next_inlay_id = 0;
|
||||
log::info!("buffer text: {:?}", buffer_snapshot.text());
|
||||
let (diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(diff_map_snapshot.clone());
|
||||
let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
for _ in 0..operations {
|
||||
let mut inlay_edits = Patch::default();
|
||||
|
||||
@@ -1575,12 +1524,8 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let (new_diff_snapshot, new_diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer.read(cx).snapshot(cx), buffer_edits, cx)
|
||||
});
|
||||
|
||||
let (new_inlay_snapshot, new_inlay_edits) =
|
||||
inlay_map.sync(new_diff_snapshot, new_diff_edits);
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
inlay_snapshot = new_inlay_snapshot;
|
||||
inlay_edits = inlay_edits.compose(new_inlay_edits);
|
||||
|
||||
@@ -1602,7 +1547,7 @@ mod tests {
|
||||
}
|
||||
assert_eq!(inlay_snapshot.text(), expected_text.to_string());
|
||||
|
||||
let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
|
||||
let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
expected_buffer_rows.len() as u32,
|
||||
expected_text.max_point().row + 1
|
||||
@@ -1610,7 +1555,7 @@ mod tests {
|
||||
for row_start in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
inlay_snapshot
|
||||
.row_infos(row_start as u32)
|
||||
.buffer_rows(row_start as u32)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[row_start..],
|
||||
"incorrect buffer rows starting at {}",
|
||||
@@ -1728,14 +1673,14 @@ mod tests {
|
||||
assert_eq!(expected_text.len(), inlay_snapshot.len().0);
|
||||
|
||||
let mut buffer_point = Point::default();
|
||||
let mut inlay_point = inlay_snapshot.make_inlay_point(buffer_point);
|
||||
let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
|
||||
let mut buffer_chars = buffer_snapshot.chars_at(0);
|
||||
loop {
|
||||
// Ensure conversion from buffer coordinates to inlay coordinates
|
||||
// is consistent.
|
||||
let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
|
||||
assert_eq!(
|
||||
inlay_snapshot.to_point(inlay_snapshot.make_inlay_offset(buffer_offset)),
|
||||
inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
|
||||
inlay_point
|
||||
);
|
||||
|
||||
@@ -1762,7 +1707,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// Ensure that moving forward in the buffer always moves the inlay point forward as well.
|
||||
let new_inlay_point = inlay_snapshot.make_inlay_point(buffer_point);
|
||||
let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
|
||||
assert!(new_inlay_point > inlay_point);
|
||||
inlay_point = new_inlay_point;
|
||||
} else {
|
||||
@@ -1822,7 +1767,7 @@ mod tests {
|
||||
// Ensure the clipped points are at valid buffer locations.
|
||||
assert_eq!(
|
||||
inlay_snapshot
|
||||
.to_inlay_point(inlay_snapshot.to_diff_point(clipped_left_point)),
|
||||
.to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
|
||||
clipped_left_point,
|
||||
"to_buffer_point({:?}) = {:?}",
|
||||
clipped_left_point,
|
||||
@@ -1830,7 +1775,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
inlay_snapshot
|
||||
.to_inlay_point(inlay_snapshot.to_diff_point(clipped_right_point)),
|
||||
.to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
|
||||
clipped_right_point,
|
||||
"to_buffer_point({:?}) = {:?}",
|
||||
clipped_right_point,
|
||||
|
||||
@@ -164,7 +164,7 @@ pub struct TabSnapshot {
|
||||
|
||||
impl TabSnapshot {
|
||||
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
|
||||
self.fold_snapshot.inlay_snapshot.buffer()
|
||||
&self.fold_snapshot.inlay_snapshot.buffer
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
@@ -272,8 +272,8 @@ impl TabSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> {
|
||||
self.fold_snapshot.row_infos(row)
|
||||
pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
|
||||
self.fold_snapshot.buffer_rows(row)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -317,7 +317,8 @@ impl TabSnapshot {
|
||||
}
|
||||
|
||||
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
let fold_point = self.fold_snapshot.make_fold_point(point, bias);
|
||||
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
self.to_tab_point(fold_point)
|
||||
}
|
||||
|
||||
@@ -601,7 +602,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap},
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
@@ -609,8 +610,8 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_expand_tabs(cx: &mut gpui::AppContext) {
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer, cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -626,8 +627,8 @@ mod tests {
|
||||
let output = "A BC DEF G HI J K L M";
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer, cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -673,8 +674,8 @@ mod tests {
|
||||
let input = "abcdefg⋯hij";
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -687,8 +688,8 @@ mod tests {
|
||||
let input = "\t \thello";
|
||||
|
||||
let buffer = MultiBuffer::build_simple(input, cx);
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
|
||||
@@ -748,11 +749,8 @@ mod tests {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
|
||||
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
|
||||
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
|
||||
|
||||
log::info!("DiffMap text: {:?}", diff_snapshot.text());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
|
||||
fold_map.randomly_mutate(&mut rng);
|
||||
let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::RowInfo;
|
||||
|
||||
use super::{
|
||||
fold_map::FoldRows,
|
||||
fold_map::FoldBufferRows,
|
||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
@@ -62,16 +60,16 @@ pub struct WrapChunks<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WrapRows<'a> {
|
||||
input_buffer_rows: FoldRows<'a>,
|
||||
input_buffer_row: RowInfo,
|
||||
pub struct WrapBufferRows<'a> {
|
||||
input_buffer_rows: FoldBufferRows<'a>,
|
||||
input_buffer_row: Option<u32>,
|
||||
output_row: u32,
|
||||
soft_wrapped: bool,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
impl<'a> WrapRows<'a> {
|
||||
impl<'a> WrapBufferRows<'a> {
|
||||
pub(crate) fn seek(&mut self, start_row: u32) {
|
||||
self.transforms
|
||||
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
@@ -719,7 +717,7 @@ impl WrapSnapshot {
|
||||
self.transforms.summary().output.longest_row
|
||||
}
|
||||
|
||||
pub fn row_infos(&self, start_row: u32) -> WrapRows {
|
||||
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
let mut input_row = transforms.start().1.row();
|
||||
@@ -727,9 +725,9 @@ impl WrapSnapshot {
|
||||
input_row += start_row - transforms.start().0.row();
|
||||
}
|
||||
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
|
||||
let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
|
||||
let input_buffer_row = input_buffer_rows.next().unwrap();
|
||||
WrapRows {
|
||||
WrapBufferRows {
|
||||
transforms,
|
||||
input_buffer_row,
|
||||
input_buffer_rows,
|
||||
@@ -849,7 +847,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
let text = language::Rope::from(self.text().as_str());
|
||||
let mut input_buffer_rows = self.tab_snapshot.rows(0);
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut prev_tab_row = 0;
|
||||
for display_row in 0..=self.max_point().row() {
|
||||
@@ -857,7 +855,7 @@ impl WrapSnapshot {
|
||||
if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
expected_buffer_rows.push(None);
|
||||
} else {
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
}
|
||||
|
||||
prev_tab_row = tab_point.row();
|
||||
@@ -866,8 +864,7 @@ impl WrapSnapshot {
|
||||
|
||||
for start_display_row in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
self.row_infos(start_display_row as u32)
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
self.buffer_rows(start_display_row as u32)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_display_row..],
|
||||
"invalid buffer_rows({}..)",
|
||||
@@ -961,8 +958,8 @@ impl<'a> Iterator for WrapChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WrapRows<'a> {
|
||||
type Item = RowInfo;
|
||||
impl<'a> Iterator for WrapBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.output_row > self.max_output_row {
|
||||
@@ -982,11 +979,7 @@ impl<'a> Iterator for WrapRows<'a> {
|
||||
self.soft_wrapped = true;
|
||||
}
|
||||
|
||||
Some(if soft_wrapped {
|
||||
RowInfo::default()
|
||||
} else {
|
||||
buffer_row
|
||||
})
|
||||
Some(if soft_wrapped { None } else { buffer_row })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,7 +1161,7 @@ fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use gpui::{font, px, test::observe};
|
||||
@@ -1216,11 +1209,9 @@ mod tests {
|
||||
});
|
||||
let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
|
||||
log::info!("DiffMap text: {:?}", diff_snapshot.text());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot);
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
@@ -1302,10 +1293,8 @@ mod tests {
|
||||
}
|
||||
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
|
||||
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
|
||||
});
|
||||
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
|
||||
@@ -89,7 +89,7 @@ use gpui::{
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
pub(crate) use hunk_diff::HoveredHunk;
|
||||
use hunk_diff::{diff_hunk_to_display, DiffMap};
|
||||
use hunk_diff::{diff_hunk_to_display, DiffMap, DiffMapSnapshot};
|
||||
use indent_guides::ActiveIndentGuidesState;
|
||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion::Direction;
|
||||
@@ -121,8 +121,8 @@ use lsp::{
|
||||
|
||||
use movement::TextLayoutDetails;
|
||||
pub use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
|
||||
ToOffset, ToPoint,
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
|
||||
ToPoint,
|
||||
};
|
||||
use multi_buffer::{
|
||||
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
|
||||
@@ -725,6 +725,7 @@ pub struct EditorSnapshot {
|
||||
git_blame_gutter_max_author_length: Option<usize>,
|
||||
pub display_snapshot: DisplaySnapshot,
|
||||
pub placeholder_text: Option<Arc<str>>,
|
||||
diff_map: DiffMapSnapshot,
|
||||
is_focused: bool,
|
||||
scroll_anchor: ScrollAnchor,
|
||||
ongoing_scroll: OngoingScroll,
|
||||
@@ -1204,12 +1205,7 @@ impl Editor {
|
||||
|
||||
let mut code_action_providers = Vec::new();
|
||||
if let Some(project) = project.clone() {
|
||||
get_unstaged_changes_for_buffers(
|
||||
&project,
|
||||
buffer.read(cx).all_buffers(),
|
||||
display_map.clone(),
|
||||
cx,
|
||||
);
|
||||
get_unstaged_changes_for_buffers(&project, buffer.read(cx).all_buffers(), cx);
|
||||
code_action_providers.push(Rc::new(project) as Rc<_>);
|
||||
}
|
||||
|
||||
@@ -1572,6 +1568,7 @@ impl Editor {
|
||||
scroll_anchor: self.scroll_manager.anchor(),
|
||||
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
|
||||
placeholder_text: self.placeholder_text.clone(),
|
||||
diff_map: self.diff_map.snapshot(),
|
||||
is_focused: self.focus_handle.is_focused(cx),
|
||||
current_line_highlight: self
|
||||
.current_line_highlight
|
||||
@@ -2476,7 +2473,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
if self.hide_context_menu(cx).is_some() {
|
||||
if self.has_active_inline_completion() {
|
||||
if self.show_inline_completions_in_menu(cx) && self.has_active_inline_completion() {
|
||||
self.update_visible_inline_completion(cx);
|
||||
}
|
||||
return true;
|
||||
@@ -2859,6 +2856,7 @@ impl Editor {
|
||||
);
|
||||
}
|
||||
|
||||
let had_active_inline_completion = this.has_active_inline_completion();
|
||||
this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
|
||||
s.select(new_selections)
|
||||
});
|
||||
@@ -2879,7 +2877,9 @@ impl Editor {
|
||||
this.show_signature_help(&ShowSignatureHelp, cx);
|
||||
}
|
||||
|
||||
this.trigger_completion_on_input(&text, true, cx);
|
||||
let trigger_in_words =
|
||||
this.show_inline_completions_in_menu(cx) || !had_active_inline_completion;
|
||||
this.trigger_completion_on_input(&text, trigger_in_words, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(this, cx);
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
});
|
||||
@@ -3685,10 +3685,6 @@ impl Editor {
|
||||
|
||||
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
|
||||
|
||||
let aside_was_displayed = match self.context_menu.borrow().deref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => menu.aside_was_displayed.get(),
|
||||
_ => false,
|
||||
};
|
||||
let trigger_kind = match &options.trigger {
|
||||
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
|
||||
CompletionTriggerKind::TRIGGER_CHARACTER
|
||||
@@ -3723,7 +3719,6 @@ impl Editor {
|
||||
position,
|
||||
buffer.clone(),
|
||||
completions.into(),
|
||||
aside_was_displayed,
|
||||
);
|
||||
|
||||
menu.filter(query.as_deref(), cx.background_executor().clone())
|
||||
@@ -3749,9 +3744,12 @@ impl Editor {
|
||||
let mut menu = menu.unwrap();
|
||||
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
|
||||
|
||||
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
|
||||
editor.hide_active_inline_completion(cx);
|
||||
menu.show_inline_completion_hint(hint);
|
||||
if editor.show_inline_completions_in_menu(cx) {
|
||||
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
|
||||
menu.show_inline_completion_hint(hint);
|
||||
}
|
||||
} else {
|
||||
editor.discard_inline_completion(false, cx);
|
||||
}
|
||||
|
||||
*editor.context_menu.borrow_mut() =
|
||||
@@ -3760,9 +3758,14 @@ impl Editor {
|
||||
cx.notify();
|
||||
} else if editor.completion_tasks.len() <= 1 {
|
||||
// If there are no more completion tasks and the last menu was
|
||||
// empty, we should hide it. If it was already hidden, we should
|
||||
// also show the copilot completion when available.
|
||||
editor.hide_context_menu(cx);
|
||||
// empty, we should hide it.
|
||||
let was_hidden = editor.hide_context_menu(cx).is_none();
|
||||
// If it was already hidden and we don't show inline
|
||||
// completions in the menu, we should also show the
|
||||
// inline-completion when available.
|
||||
if was_hidden && editor.show_inline_completions_in_menu(cx) {
|
||||
editor.update_visible_inline_completion(cx);
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -3816,7 +3819,9 @@ impl Editor {
|
||||
return Some(Task::ready(Ok(())));
|
||||
}
|
||||
CompletionEntry::Match(mat) => {
|
||||
self.discard_inline_completion(true, cx);
|
||||
if self.show_inline_completions_in_menu(cx) {
|
||||
self.discard_inline_completion(true, cx);
|
||||
}
|
||||
mat
|
||||
}
|
||||
};
|
||||
@@ -4554,7 +4559,9 @@ impl Editor {
|
||||
_: &AcceptInlineCompletion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.hide_context_menu(cx);
|
||||
if self.show_inline_completions_in_menu(cx) {
|
||||
self.hide_context_menu(cx);
|
||||
}
|
||||
|
||||
let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
|
||||
return;
|
||||
@@ -4703,17 +4710,6 @@ impl Editor {
|
||||
Some(active_inline_completion.completion)
|
||||
}
|
||||
|
||||
fn hide_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_inline_completion) = self.active_inline_completion.as_ref() {
|
||||
self.splice_inlays(
|
||||
active_inline_completion.inlay_ids.clone(),
|
||||
Default::default(),
|
||||
cx,
|
||||
);
|
||||
self.clear_highlights::<InlineCompletionHighlight>(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||
let selection = self.selections.newest_anchor();
|
||||
let cursor = selection.head();
|
||||
@@ -4721,7 +4717,11 @@ impl Editor {
|
||||
let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
|
||||
let excerpt_id = cursor.excerpt_id;
|
||||
|
||||
if !offset_selection.is_empty()
|
||||
let completions_menu_has_precedence = !self.show_inline_completions_in_menu(cx)
|
||||
&& (self.context_menu.borrow().is_some()
|
||||
|| (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
|
||||
if completions_menu_has_precedence
|
||||
|| !offset_selection.is_empty()
|
||||
|| self
|
||||
.active_inline_completion
|
||||
.as_ref()
|
||||
@@ -4745,16 +4745,10 @@ impl Editor {
|
||||
let edits = completion
|
||||
.edits
|
||||
.into_iter()
|
||||
.map(|(range, new_text)| {
|
||||
(
|
||||
multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, range.start)
|
||||
.unwrap()
|
||||
..multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, range.end)
|
||||
.unwrap(),
|
||||
new_text,
|
||||
)
|
||||
.flat_map(|(range, new_text)| {
|
||||
let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
|
||||
let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
|
||||
Some((start..end, new_text))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if edits.is_empty() {
|
||||
@@ -4785,34 +4779,32 @@ impl Editor {
|
||||
invalidation_row_range = edit_start_row..cursor_row;
|
||||
completion = InlineCompletion::Move(first_edit_start);
|
||||
} else {
|
||||
if !self.has_active_completions_menu() {
|
||||
if edits
|
||||
.iter()
|
||||
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
|
||||
{
|
||||
let mut inlays = Vec::new();
|
||||
for (range, new_text) in &edits {
|
||||
let inlay = Inlay::inline_completion(
|
||||
post_inc(&mut self.next_inlay_id),
|
||||
range.start,
|
||||
new_text.as_str(),
|
||||
);
|
||||
inlay_ids.push(inlay.id);
|
||||
inlays.push(inlay);
|
||||
}
|
||||
|
||||
self.splice_inlays(vec![], inlays, cx);
|
||||
} else {
|
||||
let background_color = cx.theme().status().deleted_background;
|
||||
self.highlight_text::<InlineCompletionHighlight>(
|
||||
edits.iter().map(|(range, _)| range.clone()).collect(),
|
||||
HighlightStyle {
|
||||
background_color: Some(background_color),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
if edits
|
||||
.iter()
|
||||
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
|
||||
{
|
||||
let mut inlays = Vec::new();
|
||||
for (range, new_text) in &edits {
|
||||
let inlay = Inlay::inline_completion(
|
||||
post_inc(&mut self.next_inlay_id),
|
||||
range.start,
|
||||
new_text.as_str(),
|
||||
);
|
||||
inlay_ids.push(inlay.id);
|
||||
inlays.push(inlay);
|
||||
}
|
||||
|
||||
self.splice_inlays(vec![], inlays, cx);
|
||||
} else {
|
||||
let background_color = cx.theme().status().deleted_background;
|
||||
self.highlight_text::<InlineCompletionHighlight>(
|
||||
edits.iter().map(|(range, _)| range.clone()).collect(),
|
||||
HighlightStyle {
|
||||
background_color: Some(background_color),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
invalidation_row_range = edit_start_row..edit_end_row;
|
||||
@@ -4832,7 +4824,7 @@ impl Editor {
|
||||
invalidation_range,
|
||||
});
|
||||
|
||||
if self.has_active_completions_menu() {
|
||||
if self.show_inline_completions_in_menu(cx) && self.has_active_completions_menu() {
|
||||
if let Some(hint) = self.inline_completion_menu_hint(cx) {
|
||||
match self.context_menu.borrow_mut().as_mut() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
@@ -4858,7 +4850,7 @@ impl Editor {
|
||||
|
||||
let text = match &self.active_inline_completion.as_ref()?.completion {
|
||||
InlineCompletion::Edit(edits) => {
|
||||
inline_completion_edit_text(&editor_snapshot, edits, cx)
|
||||
inline_completion_edit_text(&editor_snapshot, edits, true, cx)
|
||||
}
|
||||
InlineCompletion::Move(target) => {
|
||||
let target_point =
|
||||
@@ -4883,6 +4875,13 @@ impl Editor {
|
||||
Some(self.inline_completion_provider.as_ref()?.provider.clone())
|
||||
}
|
||||
|
||||
fn show_inline_completions_in_menu(&self, cx: &AppContext) -> bool {
|
||||
EditorSettings::get_global(cx).show_inline_completions_in_menu
|
||||
&& self
|
||||
.inline_completion_provider()
|
||||
.map_or(false, |provider| provider.show_completions_in_menu())
|
||||
}
|
||||
|
||||
fn render_code_actions_indicator(
|
||||
&self,
|
||||
_style: &EditorStyle,
|
||||
@@ -5085,7 +5084,7 @@ impl Editor {
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn context_menu_visible(&self) -> bool {
|
||||
self.context_menu
|
||||
.borrow()
|
||||
@@ -5121,12 +5120,27 @@ impl Editor {
|
||||
) -> Option<AnyElement> {
|
||||
self.context_menu.borrow().as_ref().and_then(|menu| {
|
||||
if menu.visible() {
|
||||
Some(menu.render(
|
||||
Some(menu.render(style, max_height_in_lines, cx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn render_context_menu_aside(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
max_height: Pixels,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<AnyElement> {
|
||||
self.context_menu.borrow().as_ref().and_then(|menu| {
|
||||
if menu.visible() {
|
||||
menu.render_aside(
|
||||
style,
|
||||
max_height_in_lines,
|
||||
max_height,
|
||||
self.workspace.as_ref().map(|(w, _)| w.clone()),
|
||||
cx,
|
||||
))
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -5136,7 +5150,11 @@ impl Editor {
|
||||
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<CodeContextMenu> {
|
||||
cx.notify();
|
||||
self.completion_tasks.clear();
|
||||
self.context_menu.borrow_mut().take()
|
||||
let context_menu = self.context_menu.borrow_mut().take();
|
||||
if context_menu.is_some() && !self.show_inline_completions_in_menu(cx) {
|
||||
self.update_visible_inline_completion(cx);
|
||||
}
|
||||
context_menu
|
||||
}
|
||||
|
||||
fn show_snippet_choices(
|
||||
@@ -5425,7 +5443,8 @@ impl Editor {
|
||||
if end_point == start_point {
|
||||
let offset = text::ToOffset::to_offset(&range.start, &snapshot)
|
||||
.saturating_sub(1);
|
||||
start_point = TP::to_point(&offset, &snapshot);
|
||||
start_point =
|
||||
snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
|
||||
};
|
||||
|
||||
(start_point..end_point, empty_str.clone())
|
||||
@@ -5921,9 +5940,10 @@ impl Editor {
|
||||
pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext<Self>) {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in snapshot
|
||||
.hunks_for_ranges(Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter())
|
||||
{
|
||||
for hunk in hunks_for_ranges(
|
||||
Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter(),
|
||||
&snapshot,
|
||||
) {
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
if !revert_changes.is_empty() {
|
||||
@@ -5941,12 +5961,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
|
||||
let selections = self.selections.all(cx).into_iter().map(|s| s.range());
|
||||
let mut revert_changes = HashMap::default();
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in &snapshot.hunks_for_ranges(selections) {
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
let revert_changes = self.gather_revert_changes(&self.selections.all(cx), cx);
|
||||
if !revert_changes.is_empty() {
|
||||
self.transact(cx, |editor, cx| {
|
||||
editor.revert(revert_changes, cx);
|
||||
@@ -5983,18 +5998,28 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn gather_revert_changes(
|
||||
&mut self,
|
||||
selections: &[Selection<Point>],
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in hunks_for_selections(&snapshot, selections) {
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
revert_changes
|
||||
}
|
||||
|
||||
pub fn prepare_revert_change(
|
||||
&mut self,
|
||||
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
||||
hunk: &MultiBufferDiffHunk,
|
||||
cx: &mut WindowContext,
|
||||
cx: &AppContext,
|
||||
) -> Option<()> {
|
||||
let change_set = self
|
||||
.display_map
|
||||
.read(cx)
|
||||
.diff_base_for(hunk.buffer_id, cx)?;
|
||||
let buffer = self.buffer.read(cx).buffer(hunk.buffer_id)?;
|
||||
let buffer = buffer.read(cx);
|
||||
let change_set = &self.diff_map.diff_bases.get(&hunk.buffer_id)?.change_set;
|
||||
let original_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
@@ -9210,8 +9235,9 @@ impl Editor {
|
||||
snapshot,
|
||||
position,
|
||||
ix > 0,
|
||||
snapshot.diff_hunks_in_range(
|
||||
snapshot.diff_map.diff_hunks_in_range(
|
||||
position + Point::new(1, 0)..snapshot.buffer_snapshot.max_point(),
|
||||
&snapshot.buffer_snapshot,
|
||||
),
|
||||
cx,
|
||||
) {
|
||||
@@ -9241,7 +9267,9 @@ impl Editor {
|
||||
snapshot,
|
||||
position,
|
||||
ix > 0,
|
||||
snapshot.diff_hunks_in_range_rev(Point::zero()..position),
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range_rev(Point::zero()..position, &snapshot.buffer_snapshot),
|
||||
cx,
|
||||
) {
|
||||
return Some(hunk);
|
||||
@@ -10371,7 +10399,7 @@ impl Editor {
|
||||
self.end_transaction_at(Instant::now(), cx)
|
||||
}
|
||||
|
||||
fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
|
||||
pub fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
|
||||
self.end_selection(cx);
|
||||
if let Some(tx_id) = self
|
||||
.buffer
|
||||
@@ -10385,7 +10413,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn end_transaction_at(
|
||||
pub fn end_transaction_at(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -10924,29 +10952,6 @@ impl Editor {
|
||||
self.display_map.read(cx).fold_placeholder.clone()
|
||||
}
|
||||
|
||||
pub fn set_expand_all_diff_hunks(&mut self, cx: &mut AppContext) {
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.set_all_hunks_expanded(cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn expand_all_diff_hunks(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
||||
let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
if display_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
|
||||
display_map.collapse_diff_hunks(ranges, cx)
|
||||
} else {
|
||||
display_map.expand_diff_hunks(ranges, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
|
||||
if hovered != self.gutter_hovered {
|
||||
self.gutter_hovered = hovered;
|
||||
@@ -12250,12 +12255,7 @@ impl Editor {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if !self.diff_map.diff_bases.contains_key(&buffer_id) {
|
||||
if let Some(project) = &self.project {
|
||||
get_unstaged_changes_for_buffers(
|
||||
project,
|
||||
[buffer.clone()],
|
||||
self.display_map.clone(),
|
||||
cx,
|
||||
);
|
||||
get_unstaged_changes_for_buffers(project, [buffer.clone()], cx);
|
||||
}
|
||||
}
|
||||
cx.emit(EditorEvent::ExcerptsAdded {
|
||||
@@ -12959,8 +12959,7 @@ impl Editor {
|
||||
fn get_unstaged_changes_for_buffers(
|
||||
project: &Model<Project>,
|
||||
buffers: impl IntoIterator<Item = Model<Buffer>>,
|
||||
display_map: Model<DisplayMap>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let mut tasks = Vec::new();
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -12968,17 +12967,16 @@ fn get_unstaged_changes_for_buffers(
|
||||
tasks.push(project.open_unstaged_changes(buffer.clone(), cx))
|
||||
}
|
||||
});
|
||||
cx.spawn(|mut cx| async move {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let change_sets = futures::future::join_all(tasks).await;
|
||||
display_map
|
||||
.update(&mut cx, |display_map, cx| {
|
||||
for change_set in change_sets {
|
||||
if let Some(change_set) = change_set.log_err() {
|
||||
display_map.add_change_set(change_set, cx);
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for change_set in change_sets {
|
||||
if let Some(change_set) = change_set.log_err() {
|
||||
this.diff_map.add_change_set(change_set, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -13266,6 +13264,56 @@ fn test_wrap_with_prefix() {
|
||||
);
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
snapshot: &EditorSnapshot,
|
||||
selections: &[Selection<Point>],
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
hunks_for_ranges(
|
||||
selections.iter().map(|selection| selection.range()),
|
||||
snapshot,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hunks_for_ranges(
|
||||
ranges: impl Iterator<Item = Range<Point>>,
|
||||
snapshot: &EditorSnapshot,
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let mut hunks = Vec::new();
|
||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||
HashMap::default();
|
||||
for query_range in ranges {
|
||||
let query_rows =
|
||||
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
|
||||
for hunk in snapshot.diff_map.diff_hunks_in_range(
|
||||
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
|
||||
&snapshot.buffer_snapshot,
|
||||
) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
|
||||
let related_to_selection = if allow_adjacent {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
|| hunk.row_range.start == query_rows.end
|
||||
|| hunk.row_range.end == query_rows.start
|
||||
} else {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
};
|
||||
if related_to_selection {
|
||||
if !processed_buffer_rows
|
||||
.entry(hunk.buffer_id)
|
||||
.or_default()
|
||||
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
hunks.push(hunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hunks
|
||||
}
|
||||
|
||||
pub trait CollaborationHub {
|
||||
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
|
||||
fn user_participant_indices<'a>(
|
||||
@@ -13814,45 +13862,6 @@ impl EditorSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hunks_for_ranges(
|
||||
&self,
|
||||
ranges: impl Iterator<Item = Range<Point>>,
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let mut hunks = Vec::new();
|
||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||
HashMap::default();
|
||||
for query_range in ranges {
|
||||
let query_rows =
|
||||
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
|
||||
for hunk in self.diff_snapshot().diff_hunks_in_range(
|
||||
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
|
||||
) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
|
||||
let related_to_selection = if allow_adjacent {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
|| hunk.row_range.start == query_rows.end
|
||||
|| hunk.row_range.end == query_rows.start
|
||||
} else {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
};
|
||||
if related_to_selection {
|
||||
if !processed_buffer_rows
|
||||
.entry(hunk.buffer_id)
|
||||
.or_default()
|
||||
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
hunks.push(hunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hunks
|
||||
}
|
||||
|
||||
pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
|
||||
self.display_snapshot.buffer_snapshot.language_at(position)
|
||||
}
|
||||
@@ -14607,6 +14616,7 @@ pub fn diagnostic_block_renderer(
|
||||
fn inline_completion_edit_text(
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
edits: &Vec<(Range<Anchor>, String)>,
|
||||
include_deletions: bool,
|
||||
cx: &WindowContext,
|
||||
) -> InlineCompletionText {
|
||||
let edit_start = edits
|
||||
@@ -14630,12 +14640,24 @@ fn inline_completion_edit_text(
|
||||
offset = old_offset_range.end;
|
||||
|
||||
let start = text.len();
|
||||
text.push_str(new_text);
|
||||
let color = if include_deletions && new_text.is_empty() {
|
||||
text.extend(
|
||||
editor_snapshot
|
||||
.buffer_snapshot
|
||||
.chunks(old_offset_range.start..offset, false)
|
||||
.map(|chunk| chunk.text),
|
||||
);
|
||||
cx.theme().status().deleted_background
|
||||
} else {
|
||||
text.push_str(new_text);
|
||||
cx.theme().status().created_background
|
||||
};
|
||||
let end = text.len();
|
||||
|
||||
highlights.push((
|
||||
start..end,
|
||||
HighlightStyle {
|
||||
background_color: Some(cx.theme().status().created_background),
|
||||
background_color: Some(color),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
|
||||
@@ -35,6 +35,7 @@ pub struct EditorSettings {
|
||||
pub auto_signature_help: bool,
|
||||
pub show_signature_help_after_edits: bool,
|
||||
pub jupyter: Jupyter,
|
||||
pub show_inline_completions_in_menu: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -300,6 +301,12 @@ pub struct EditorSettingsContent {
|
||||
/// Default: false
|
||||
pub show_signature_help_after_edits: Option<bool>,
|
||||
|
||||
/// Whether to show the inline completions next to the completions provided by a language server.
|
||||
/// Only has an effect if inline completion provider supports it.
|
||||
///
|
||||
/// Default: true
|
||||
pub show_inline_completions_in_menu: Option<bool>,
|
||||
|
||||
/// Jupyter REPL settings.
|
||||
pub jupyter: Option<JupyterContent>,
|
||||
}
|
||||
|
||||
@@ -3359,7 +3359,8 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
|
||||
let snapshot = editor.snapshot(cx);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
"Should not have any diffs for files with custom newlines"
|
||||
@@ -11654,9 +11655,7 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -12074,34 +12073,12 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
for _ in 0..2 {
|
||||
for _ in 0..3 {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
}
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
+ ˇuse some::modified;
|
||||
|
||||
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ println!("hello there");
|
||||
|
||||
+ println!("around the");
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
@@ -12187,7 +12164,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
@@ -12232,7 +12209,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
@@ -12315,7 +12292,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -12508,9 +12485,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -12620,16 +12595,14 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
|
||||
let buffer = buffer.read(cx).text_snapshot();
|
||||
let change_set = cx
|
||||
.new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
})
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut cx = EditorTestContext::for_editor(editor, cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx));
|
||||
cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
@@ -12693,7 +12666,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -12712,7 +12685,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12735,7 +12708,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12759,7 +12732,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12784,7 +12757,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12810,7 +12783,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12831,7 +12804,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
@@ -12884,7 +12857,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -12903,7 +12876,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12926,7 +12899,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12949,7 +12922,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
@@ -12973,7 +12946,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
@@ -13026,7 +12999,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -14394,7 +14367,7 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
|
||||
fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// Test case 1: Simple insertion
|
||||
// Simple insertion
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
|
||||
@@ -14410,7 +14383,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
let edits = vec![(edit_range, " beautiful".to_string())];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
inline_completion_edit_text(&snapshot, &edits, false, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
@@ -14426,7 +14399,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test case 2: Replacement
|
||||
// Replacement
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("This is a test.", cx);
|
||||
@@ -14444,7 +14417,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
)];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
inline_completion_edit_text(&snapshot, &edits, false, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
@@ -14460,7 +14433,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test case 3: Multiple edits
|
||||
// Multiple edits
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
|
||||
@@ -14485,7 +14458,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
inline_completion_edit_text(&snapshot, &edits, false, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
@@ -14506,7 +14479,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Test case 4: Multiple lines with edits
|
||||
// Multiple lines with edits
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer =
|
||||
@@ -14537,7 +14510,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, cx)
|
||||
inline_completion_edit_text(&snapshot, &edits, false, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
@@ -14559,6 +14532,75 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// Deletion
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, true, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
|
||||
let edits = vec![(edit_range, "".to_string())];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, true, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
|
||||
assert_eq!(text, "Hello, world!");
|
||||
assert_eq!(highlights.len(), 1);
|
||||
assert_eq!(highlights[0].0, 5..11);
|
||||
assert_eq!(
|
||||
highlights[0].1.background_color,
|
||||
Some(cx.theme().status().deleted_background)
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Insertion
|
||||
{
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, true, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
|
||||
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
|
||||
let edits = vec![(edit_range, " digital".to_string())];
|
||||
|
||||
let InlineCompletionText::Edit { text, highlights } =
|
||||
inline_completion_edit_text(&snapshot, &edits, true, cx)
|
||||
else {
|
||||
panic!("Failed to generate inline completion text");
|
||||
};
|
||||
|
||||
assert_eq!(text, "Hello, digital world!");
|
||||
assert_eq!(highlights.len(), 1);
|
||||
assert_eq!(highlights[0].0, 6..14);
|
||||
assert_eq!(
|
||||
highlights[0].1.background_color,
|
||||
Some(cx.theme().status().created_background)
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||
point..point
|
||||
@@ -14726,7 +14768,8 @@ fn assert_hunk_revert(
|
||||
let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let reverted_hunk_statuses = snapshot
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.map(|hunk| hunk_status(&hunk))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
||||
code_context_menus::CodeActionsMenu,
|
||||
code_context_menus::{CodeActionsMenu, MAX_COMPLETIONS_ASIDE_WIDTH},
|
||||
display_map::{
|
||||
Block, BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint,
|
||||
},
|
||||
@@ -21,8 +21,8 @@ use crate::{
|
||||
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
|
||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowInfo, RowRangeExt, SelectPhase,
|
||||
Selection, SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
|
||||
SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
@@ -381,7 +381,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::toggle_git_blame);
|
||||
register_action(view, cx, Editor::toggle_git_blame_inline);
|
||||
register_action(view, cx, Editor::toggle_hunk_diff);
|
||||
register_action(view, cx, Editor::expand_all_diff_hunks);
|
||||
register_action(view, cx, Editor::expand_all_hunk_diffs);
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
if let Some(task) = editor.format(action, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
@@ -1196,7 +1196,7 @@ impl EditorElement {
|
||||
let editor = self.editor.read(cx);
|
||||
let is_singleton = editor.is_singleton(cx);
|
||||
// Git
|
||||
(is_singleton && scrollbar_settings.git_diff && snapshot.has_diff_hunks())
|
||||
(is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty())
|
||||
||
|
||||
// Buffer Search Results
|
||||
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
|
||||
@@ -1461,10 +1461,10 @@ impl EditorElement {
|
||||
.unwrap_err();
|
||||
let mut expanded_hunks = expanded_hunks[expanded_hunks_start_ix..].iter().peekable();
|
||||
|
||||
let mut display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)> = snapshot
|
||||
.display_snapshot
|
||||
.diff_snapshot()
|
||||
.diff_hunks_in_range(buffer_start..buffer_end)
|
||||
let mut display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)> = editor
|
||||
.diff_map
|
||||
.snapshot
|
||||
.diff_hunks_in_range(buffer_start..buffer_end, &buffer_snapshot)
|
||||
.filter_map(|hunk| {
|
||||
let display_hunk = diff_hunk_to_display(&hunk, snapshot);
|
||||
|
||||
@@ -1532,7 +1532,7 @@ impl EditorElement {
|
||||
fn layout_inline_blame(
|
||||
&self,
|
||||
display_row: DisplayRow,
|
||||
row_info: &RowInfo,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
line_layout: &LineWithInvisibles,
|
||||
crease_trailer: Option<&CreaseTrailerLayout>,
|
||||
em_width: Pixels,
|
||||
@@ -1555,10 +1555,13 @@ impl EditorElement {
|
||||
.as_ref()
|
||||
.map(|(w, _)| w.clone());
|
||||
|
||||
let display_point = DisplayPoint::new(display_row, 0);
|
||||
let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
|
||||
|
||||
let blame = self.editor.read(cx).blame.clone()?;
|
||||
let blame_entry = blame
|
||||
.update(cx, |blame, cx| {
|
||||
blame.blame_for_rows(&[*row_info], cx).next()
|
||||
blame.blame_for_rows([Some(buffer_row)], cx).next()
|
||||
})
|
||||
.flatten()?;
|
||||
|
||||
@@ -1598,7 +1601,7 @@ impl EditorElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_blame_entries(
|
||||
&self,
|
||||
buffer_rows: &[RowInfo],
|
||||
buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
|
||||
em_width: Pixels,
|
||||
scroll_position: gpui::Point<f32>,
|
||||
line_height: Pixels,
|
||||
@@ -1935,7 +1938,7 @@ impl EditorElement {
|
||||
let end = rows.end.max(relative_to);
|
||||
|
||||
let buffer_rows = snapshot
|
||||
.row_infos(start)
|
||||
.buffer_rows(start)
|
||||
.take(1 + end.minus(start) as usize)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1943,7 +1946,7 @@ impl EditorElement {
|
||||
let mut delta = 1;
|
||||
let mut i = head_idx + 1;
|
||||
while i < buffer_rows.len() as u32 {
|
||||
if buffer_rows[i as usize].buffer_row.is_some() {
|
||||
if buffer_rows[i as usize].is_some() {
|
||||
if rows.contains(&DisplayRow(i + start.0)) {
|
||||
relative_rows.insert(DisplayRow(i + start.0), delta);
|
||||
}
|
||||
@@ -1953,13 +1956,13 @@ impl EditorElement {
|
||||
}
|
||||
delta = 1;
|
||||
i = head_idx.min(buffer_rows.len() as u32 - 1);
|
||||
while i > 0 && buffer_rows[i as usize].buffer_row.is_none() {
|
||||
while i > 0 && buffer_rows[i as usize].is_none() {
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
while i > 0 {
|
||||
i -= 1;
|
||||
if buffer_rows[i as usize].buffer_row.is_some() {
|
||||
if buffer_rows[i as usize].is_some() {
|
||||
if rows.contains(&DisplayRow(i + start.0)) {
|
||||
relative_rows.insert(DisplayRow(i + start.0), delta);
|
||||
}
|
||||
@@ -1973,7 +1976,7 @@ impl EditorElement {
|
||||
fn layout_line_numbers(
|
||||
&self,
|
||||
rows: Range<DisplayRow>,
|
||||
buffer_rows: &[RowInfo],
|
||||
buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
|
||||
active_rows: &BTreeMap<DisplayRow, bool>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
snapshot: &EditorSnapshot,
|
||||
@@ -2015,7 +2018,8 @@ impl EditorElement {
|
||||
buffer_rows
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, row_info)| {
|
||||
.map(|(ix, multibuffer_row)| {
|
||||
let multibuffer_row = multibuffer_row?;
|
||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||
let color = if active_rows.contains_key(&display_row) {
|
||||
cx.theme().colors().editor_active_line_number
|
||||
@@ -2023,7 +2027,7 @@ impl EditorElement {
|
||||
cx.theme().colors().editor_line_number
|
||||
};
|
||||
line_number.clear();
|
||||
let default_number = row_info.buffer_row? + 1;
|
||||
let default_number = multibuffer_row.0 + 1;
|
||||
let number = relative_rows
|
||||
.get(&DisplayRow(ix as u32 + rows.start.0))
|
||||
.unwrap_or(&default_number);
|
||||
@@ -2048,7 +2052,7 @@ impl EditorElement {
|
||||
fn layout_crease_toggles(
|
||||
&self,
|
||||
rows: Range<DisplayRow>,
|
||||
row_infos: &[RowInfo],
|
||||
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
active_rows: &BTreeMap<DisplayRow, bool>,
|
||||
snapshot: &EditorSnapshot,
|
||||
cx: &mut WindowContext,
|
||||
@@ -2057,21 +2061,22 @@ impl EditorElement {
|
||||
&& snapshot.mode == EditorMode::Full
|
||||
&& self.editor.read(cx).is_singleton(cx);
|
||||
if include_fold_statuses {
|
||||
row_infos
|
||||
buffer_rows
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, info)| {
|
||||
let row = info.buffer_row?;
|
||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||
let active = active_rows.contains_key(&display_row);
|
||||
|
||||
// todo(max): Retrieve the multibuffer row correctly
|
||||
snapshot.render_crease_toggle(
|
||||
MultiBufferRow(row),
|
||||
active,
|
||||
self.editor.clone(),
|
||||
cx,
|
||||
)
|
||||
.map(|(ix, row)| {
|
||||
if let Some(multibuffer_row) = row {
|
||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||
let active = active_rows.contains_key(&display_row);
|
||||
snapshot.render_crease_toggle(
|
||||
multibuffer_row,
|
||||
active,
|
||||
self.editor.clone(),
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
@@ -2081,16 +2086,15 @@ impl EditorElement {
|
||||
|
||||
fn layout_crease_trailers(
|
||||
&self,
|
||||
buffer_rows: impl IntoIterator<Item = RowInfo>,
|
||||
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
snapshot: &EditorSnapshot,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<Option<AnyElement>> {
|
||||
buffer_rows
|
||||
.into_iter()
|
||||
.map(|row_info| {
|
||||
// FIXME: These are not really MultiBufferRow?!
|
||||
if let Some(row) = row_info.buffer_row {
|
||||
snapshot.render_crease_trailer(MultiBufferRow(row), cx)
|
||||
.map(|row| {
|
||||
if let Some(multibuffer_row) = row {
|
||||
snapshot.render_crease_trailer(multibuffer_row, cx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -2897,38 +2901,44 @@ impl EditorElement {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let target_offset = match context_menu_origin {
|
||||
crate::ContextMenuOrigin::EditorPoint(display_point) => {
|
||||
let cursor_row_layout =
|
||||
&line_layouts[display_point.row().minus(start_row) as usize];
|
||||
gpui::Point {
|
||||
x: cursor_row_layout.x_for_index(display_point.column() as usize)
|
||||
- scroll_pixel_position.x,
|
||||
y: display_point.row().next_row().as_f32() * line_height
|
||||
- scroll_pixel_position.y,
|
||||
let target_position = content_origin
|
||||
+ match context_menu_origin {
|
||||
crate::ContextMenuOrigin::EditorPoint(display_point) => {
|
||||
let cursor_row_layout =
|
||||
&line_layouts[display_point.row().minus(start_row) as usize];
|
||||
gpui::Point {
|
||||
x: cmp::max(
|
||||
px(0.),
|
||||
cursor_row_layout.x_for_index(display_point.column() as usize)
|
||||
- scroll_pixel_position.x,
|
||||
),
|
||||
y: cmp::max(
|
||||
px(0.),
|
||||
display_point.row().next_row().as_f32() * line_height
|
||||
- scroll_pixel_position.y,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::ContextMenuOrigin::GutterIndicator(row) => {
|
||||
// Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
|
||||
// text field.
|
||||
gpui::Point {
|
||||
x: -gutter_overshoot,
|
||||
y: row.next_row().as_f32() * line_height - scroll_pixel_position.y,
|
||||
crate::ContextMenuOrigin::GutterIndicator(row) => {
|
||||
// Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
|
||||
// text field.
|
||||
gpui::Point {
|
||||
x: -gutter_overshoot,
|
||||
y: row.next_row().as_f32() * line_height - scroll_pixel_position.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// If the context menu's max height won't fit below, then flip it above the line and display
|
||||
// it in reverse order. If the available space above is less than below.
|
||||
let unconstrained_max_height = line_height * 12. + POPOVER_Y_PADDING;
|
||||
let min_height = line_height * 3. + POPOVER_Y_PADDING;
|
||||
let target_position = content_origin + target_offset;
|
||||
let y_overflows_below = target_position.y + unconstrained_max_height > text_hitbox.bottom();
|
||||
let bottom_y_when_flipped = target_position.y - line_height;
|
||||
let available_above = bottom_y_when_flipped - text_hitbox.top();
|
||||
let available_below = text_hitbox.bottom() - target_position.y;
|
||||
let y_overflows_below = unconstrained_max_height > available_below;
|
||||
let mut y_is_flipped = y_overflows_below && available_above > available_below;
|
||||
let mut max_height = cmp::min(
|
||||
let mut height = cmp::min(
|
||||
unconstrained_max_height,
|
||||
if y_is_flipped {
|
||||
available_above
|
||||
@@ -2938,33 +2948,33 @@ impl EditorElement {
|
||||
);
|
||||
|
||||
// If less than 3 lines fit within the text bounds, instead fit within the window.
|
||||
if max_height < 3. * line_height {
|
||||
if height < min_height {
|
||||
let available_above = bottom_y_when_flipped;
|
||||
let available_below = cx.viewport_size().height - target_position.y;
|
||||
if available_below > 3. * line_height {
|
||||
y_is_flipped = false;
|
||||
max_height = min_height;
|
||||
height = min_height;
|
||||
} else if available_above > 3. * line_height {
|
||||
y_is_flipped = true;
|
||||
max_height = min_height;
|
||||
height = min_height;
|
||||
} else if available_above > available_below {
|
||||
y_is_flipped = true;
|
||||
max_height = available_above;
|
||||
height = available_above;
|
||||
} else {
|
||||
y_is_flipped = false;
|
||||
max_height = available_below;
|
||||
height = available_below;
|
||||
}
|
||||
}
|
||||
|
||||
let max_height_in_lines = ((max_height - POPOVER_Y_PADDING) / line_height).floor() as u32;
|
||||
let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
|
||||
|
||||
let Some(mut menu) = self.editor.update(cx, |editor, cx| {
|
||||
let Some(mut menu_element) = self.editor.update(cx, |editor, cx| {
|
||||
editor.render_context_menu(&self.style, max_height_in_lines, cx)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let menu_size = menu.layout_as_root(AvailableSpace::min_size(), cx);
|
||||
let menu_size = menu_element.layout_as_root(AvailableSpace::min_size(), cx);
|
||||
let menu_position = gpui::Point {
|
||||
x: if target_position.x + menu_size.width > cx.viewport_size().width {
|
||||
// Snap the right edge of the list to the right edge of the window if its horizontal bounds
|
||||
@@ -2979,8 +2989,114 @@ impl EditorElement {
|
||||
target_position.y
|
||||
},
|
||||
};
|
||||
cx.defer_draw(menu_element, menu_position, 1);
|
||||
|
||||
cx.defer_draw(menu, menu_position, 1);
|
||||
let aside_element = self.editor.update(cx, |editor, cx| {
|
||||
editor.render_context_menu_aside(&self.style, unconstrained_max_height, cx)
|
||||
});
|
||||
if let Some(aside_element) = aside_element {
|
||||
let menu_bounds = Bounds::new(menu_position, menu_size);
|
||||
let max_menu_size = size(menu_size.width, unconstrained_max_height);
|
||||
let max_menu_bounds = if y_is_flipped {
|
||||
Bounds::new(
|
||||
point(
|
||||
menu_position.x,
|
||||
bottom_y_when_flipped - max_menu_size.height,
|
||||
),
|
||||
max_menu_size,
|
||||
)
|
||||
} else {
|
||||
Bounds::new(target_position, max_menu_size)
|
||||
};
|
||||
|
||||
// Pad the target by 4 pixels to create a gap.
|
||||
let mut extend_amount = Edges::all(px(4.));
|
||||
// Extend to include the cursored line to avoid overlapping it.
|
||||
if y_is_flipped {
|
||||
extend_amount.bottom = line_height;
|
||||
} else {
|
||||
extend_amount.top = line_height;
|
||||
}
|
||||
self.layout_context_menu_aside(
|
||||
text_hitbox,
|
||||
y_is_flipped,
|
||||
menu_position,
|
||||
menu_bounds.extend(extend_amount),
|
||||
max_menu_bounds.extend(extend_amount),
|
||||
unconstrained_max_height,
|
||||
aside_element,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_context_menu_aside(
|
||||
&self,
|
||||
text_hitbox: &Hitbox,
|
||||
y_is_flipped: bool,
|
||||
menu_position: gpui::Point<Pixels>,
|
||||
target_bounds: Bounds<Pixels>,
|
||||
max_target_bounds: Bounds<Pixels>,
|
||||
max_height: Pixels,
|
||||
aside: AnyElement,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let mut aside = aside;
|
||||
let actual_size = aside.layout_as_root(AvailableSpace::min_size(), cx);
|
||||
|
||||
// Snap to right side of window if it would overflow.
|
||||
let aside_x = cmp::min(
|
||||
menu_position.x,
|
||||
cx.viewport_size().width - actual_size.width,
|
||||
);
|
||||
if aside_x < px(0.) {
|
||||
// Not enough space, skip drawing.
|
||||
return;
|
||||
}
|
||||
|
||||
let top_position = point(aside_x, target_bounds.top() - actual_size.height);
|
||||
let bottom_position = point(aside_x, target_bounds.bottom());
|
||||
let right_position = point(target_bounds.right(), menu_position.y);
|
||||
|
||||
let fit_horizontally_within = |available: Edges<Pixels>, wanted: Size<Pixels>| {
|
||||
// Prefer to fit to the right, then on the same side of the line as the menu, then on
|
||||
// the other side of the line.
|
||||
if wanted.width < available.right {
|
||||
Some(right_position)
|
||||
} else if !y_is_flipped && wanted.height < available.bottom {
|
||||
Some(bottom_position)
|
||||
} else if !y_is_flipped && wanted.height < available.top {
|
||||
Some(top_position)
|
||||
} else if y_is_flipped && wanted.height < available.top {
|
||||
Some(top_position)
|
||||
} else if y_is_flipped && wanted.height < available.bottom {
|
||||
Some(bottom_position)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Prefer choosing a direction using max sizes rather than actual size for stability.
|
||||
let mut available = max_target_bounds.space_within(&text_hitbox.bounds);
|
||||
let mut wanted = size(MAX_COMPLETIONS_ASIDE_WIDTH, max_height);
|
||||
let aside_position = fit_horizontally_within(available, wanted)
|
||||
.or_else(|| {
|
||||
// Fallback: fit max size in window.
|
||||
available = max_target_bounds
|
||||
.space_within(&Bounds::new(Default::default(), cx.viewport_size()));
|
||||
fit_horizontally_within(available, wanted)
|
||||
})
|
||||
.or_else(|| {
|
||||
// Fallback: fit actual size in window.
|
||||
wanted = actual_size;
|
||||
fit_horizontally_within(available, wanted)
|
||||
});
|
||||
|
||||
// Skip drawing if it doesn't fit anywhere.
|
||||
if let Some(aside_position) = aside_position {
|
||||
cx.defer_draw(aside, aside_position, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -3105,7 +3221,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
let crate::InlineCompletionText::Edit { text, highlights } =
|
||||
crate::inline_completion_edit_text(editor_snapshot, edits, cx)
|
||||
crate::inline_completion_edit_text(editor_snapshot, edits, false, cx)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
@@ -4372,29 +4488,32 @@ impl EditorElement {
|
||||
let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
|
||||
let mut marker_quads = Vec::new();
|
||||
if scrollbar_settings.git_diff {
|
||||
let marker_row_ranges = snapshot.diff_hunks().map(|hunk| {
|
||||
let start_display_row =
|
||||
MultiBufferPoint::new(hunk.row_range.start.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
let mut end_display_row =
|
||||
MultiBufferPoint::new(hunk.row_range.end.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
if end_display_row != start_display_row {
|
||||
end_display_row.0 -= 1;
|
||||
}
|
||||
let color = match hunk_status(&hunk) {
|
||||
DiffHunkStatus::Added => theme.status().created,
|
||||
DiffHunkStatus::Modified => theme.status().modified,
|
||||
DiffHunkStatus::Removed => theme.status().deleted,
|
||||
};
|
||||
ColoredRange {
|
||||
start: start_display_row,
|
||||
end: end_display_row,
|
||||
color,
|
||||
}
|
||||
});
|
||||
let marker_row_ranges = snapshot
|
||||
.diff_map
|
||||
.diff_hunks(&snapshot.buffer_snapshot)
|
||||
.map(|hunk| {
|
||||
let start_display_row =
|
||||
MultiBufferPoint::new(hunk.row_range.start.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
let mut end_display_row =
|
||||
MultiBufferPoint::new(hunk.row_range.end.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
if end_display_row != start_display_row {
|
||||
end_display_row.0 -= 1;
|
||||
}
|
||||
let color = match hunk_status(&hunk) {
|
||||
DiffHunkStatus::Added => theme.status().created,
|
||||
DiffHunkStatus::Modified => theme.status().modified,
|
||||
DiffHunkStatus::Removed => theme.status().deleted,
|
||||
};
|
||||
ColoredRange {
|
||||
start: start_display_row,
|
||||
end: end_display_row,
|
||||
color,
|
||||
}
|
||||
});
|
||||
|
||||
marker_quads.extend(
|
||||
scrollbar_layout
|
||||
@@ -5813,15 +5932,12 @@ impl Element for EditorElement {
|
||||
);
|
||||
let end_row = DisplayRow(end_row);
|
||||
|
||||
let row_infos = snapshot
|
||||
.row_infos(start_row)
|
||||
let buffer_rows = snapshot
|
||||
.buffer_rows(start_row)
|
||||
.take((start_row..end_row).len())
|
||||
.collect::<Vec<RowInfo>>();
|
||||
let is_row_soft_wrapped = |row: usize| {
|
||||
row_infos
|
||||
.get(row)
|
||||
.map_or(true, |info| info.buffer_row.is_none())
|
||||
};
|
||||
.collect::<Vec<_>>();
|
||||
let is_row_soft_wrapped =
|
||||
|row| buffer_rows.get(row).copied().flatten().is_none();
|
||||
|
||||
let start_anchor = if start_row == Default::default() {
|
||||
Anchor::min()
|
||||
@@ -5838,21 +5954,9 @@ impl Element for EditorElement {
|
||||
)
|
||||
};
|
||||
|
||||
let mut highlighted_rows = self
|
||||
let highlighted_rows = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.highlighted_display_rows(cx));
|
||||
|
||||
for (ix, row_info) in row_infos.iter().enumerate() {
|
||||
let color = match row_info.diff_status {
|
||||
Some(DiffHunkStatus::Added) => style.status.created_background,
|
||||
Some(DiffHunkStatus::Removed) => style.status.deleted_background,
|
||||
_ => continue,
|
||||
};
|
||||
highlighted_rows
|
||||
.entry(start_row + DisplayRow(ix as u32))
|
||||
.or_insert(color);
|
||||
}
|
||||
|
||||
let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
|
||||
start_anchor..end_anchor,
|
||||
&snapshot.display_snapshot,
|
||||
@@ -5892,7 +5996,7 @@ impl Element for EditorElement {
|
||||
|
||||
let line_numbers = self.layout_line_numbers(
|
||||
start_row..end_row,
|
||||
&row_infos,
|
||||
buffer_rows.iter().copied(),
|
||||
&active_rows,
|
||||
newest_selection_head,
|
||||
&snapshot,
|
||||
@@ -5902,14 +6006,14 @@ impl Element for EditorElement {
|
||||
let mut crease_toggles = cx.with_element_namespace("crease_toggles", |cx| {
|
||||
self.layout_crease_toggles(
|
||||
start_row..end_row,
|
||||
&row_infos,
|
||||
buffer_rows.iter().copied(),
|
||||
&active_rows,
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
|
||||
self.layout_crease_trailers(row_infos.iter().copied(), &snapshot, cx)
|
||||
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
||||
});
|
||||
|
||||
let display_hunks = self.layout_gutter_git_hunks(
|
||||
@@ -6050,12 +6154,11 @@ impl Element for EditorElement {
|
||||
let display_row = newest_selection_head.row();
|
||||
if (start_row..end_row).contains(&display_row) {
|
||||
let line_ix = display_row.minus(start_row) as usize;
|
||||
let row_info = &row_infos[line_ix];
|
||||
let line_layout = &line_layouts[line_ix];
|
||||
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
|
||||
inline_blame = self.layout_inline_blame(
|
||||
display_row,
|
||||
row_info,
|
||||
&snapshot.display_snapshot,
|
||||
line_layout,
|
||||
crease_trailer_layout,
|
||||
em_width,
|
||||
@@ -6068,7 +6171,7 @@ impl Element for EditorElement {
|
||||
}
|
||||
|
||||
let blamed_display_rows = self.layout_blame_entries(
|
||||
&row_infos,
|
||||
buffer_rows.into_iter(),
|
||||
em_width,
|
||||
scroll_position,
|
||||
line_height,
|
||||
@@ -6157,6 +6260,22 @@ impl Element for EditorElement {
|
||||
|
||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||
|
||||
let expanded_add_hunks_by_rows = self.editor.update(cx, |editor, _| {
|
||||
editor
|
||||
.diff_map
|
||||
.hunks(false)
|
||||
.filter(|hunk| hunk.status == DiffHunkStatus::Added)
|
||||
.map(|expanded_hunk| {
|
||||
let start_row = expanded_hunk
|
||||
.hunk_range
|
||||
.start
|
||||
.to_display_point(&snapshot)
|
||||
.row();
|
||||
(start_row, expanded_hunk.clone())
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
});
|
||||
|
||||
let rows_with_hunk_bounds = display_hunks
|
||||
.iter()
|
||||
.filter_map(|(hunk, hitbox)| Some((hunk, hitbox.as_ref()?.bounds)))
|
||||
@@ -6199,32 +6318,38 @@ impl Element for EditorElement {
|
||||
if show_code_actions {
|
||||
let newest_selection_point =
|
||||
newest_selection_head.to_point(&snapshot.display_snapshot);
|
||||
if !snapshot
|
||||
.is_line_folded(MultiBufferRow(newest_selection_point.row))
|
||||
let newest_selection_display_row =
|
||||
newest_selection_point.to_display_point(&snapshot).row();
|
||||
if !expanded_add_hunks_by_rows
|
||||
.contains_key(&newest_selection_display_row)
|
||||
{
|
||||
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
|
||||
MultiBufferRow(newest_selection_point.row),
|
||||
);
|
||||
if let Some((buffer, range)) = buffer {
|
||||
let buffer_id = buffer.remote_id();
|
||||
let row = range.start.row;
|
||||
let has_test_indicator = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.tasks
|
||||
.contains_key(&(buffer_id, row));
|
||||
if !snapshot
|
||||
.is_line_folded(MultiBufferRow(newest_selection_point.row))
|
||||
{
|
||||
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
|
||||
MultiBufferRow(newest_selection_point.row),
|
||||
);
|
||||
if let Some((buffer, range)) = buffer {
|
||||
let buffer_id = buffer.remote_id();
|
||||
let row = range.start.row;
|
||||
let has_test_indicator = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.tasks
|
||||
.contains_key(&(buffer_id, row));
|
||||
|
||||
if !has_test_indicator {
|
||||
code_actions_indicator = self
|
||||
.layout_code_actions_indicator(
|
||||
line_height,
|
||||
newest_selection_head,
|
||||
scroll_pixel_position,
|
||||
&gutter_dimensions,
|
||||
&gutter_hitbox,
|
||||
&rows_with_hunk_bounds,
|
||||
cx,
|
||||
);
|
||||
if !has_test_indicator {
|
||||
code_actions_indicator = self
|
||||
.layout_code_actions_indicator(
|
||||
line_height,
|
||||
newest_selection_head,
|
||||
scroll_pixel_position,
|
||||
&gutter_dimensions,
|
||||
&gutter_hitbox,
|
||||
&rows_with_hunk_bounds,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6898,7 +7023,16 @@ impl CursorLayout {
|
||||
let name_origin = if cursor_name.is_top_row {
|
||||
point(bounds.right() - px(1.), bounds.top())
|
||||
} else {
|
||||
point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
|
||||
match self.shape {
|
||||
CursorShape::Bar => point(
|
||||
bounds.right() - px(2.),
|
||||
bounds.top() - text_size / 2. - px(1.),
|
||||
),
|
||||
_ => point(
|
||||
bounds.right() - px(1.),
|
||||
bounds.top() - text_size / 2. - px(1.),
|
||||
),
|
||||
}
|
||||
};
|
||||
let mut name_element = div()
|
||||
.bg(self.color)
|
||||
@@ -7196,12 +7330,7 @@ mod tests {
|
||||
.update_window(*window, |_, cx| {
|
||||
element.layout_line_numbers(
|
||||
DisplayRow(0)..DisplayRow(6),
|
||||
&(0..6)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
(0..6).map(MultiBufferRow).map(Some),
|
||||
&Default::default(),
|
||||
Some(DisplayPoint::new(DisplayRow(0), 0)),
|
||||
&snapshot,
|
||||
|
||||
@@ -9,13 +9,12 @@ use git::{
|
||||
use gpui::{Model, ModelContext, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use project::{Project, ProjectItem};
|
||||
use smallvec::SmallVec;
|
||||
use sum_tree::SumTree;
|
||||
use url::Url;
|
||||
|
||||
use crate::RowInfo;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GitBlameEntry {
|
||||
pub rows: u32,
|
||||
@@ -195,15 +194,15 @@ impl GitBlame {
|
||||
|
||||
pub fn blame_for_rows<'a>(
|
||||
&'a mut self,
|
||||
rows: &'a [RowInfo],
|
||||
rows: impl 'a + IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||
self.sync(cx);
|
||||
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
rows.into_iter().map(move |info| {
|
||||
let row = info.buffer_row?;
|
||||
cursor.seek_forward(&row, Bias::Right, &());
|
||||
rows.into_iter().map(move |row| {
|
||||
let row = row?;
|
||||
cursor.seek_forward(&row.0, Bias::Right, &());
|
||||
cursor.item()?.blame.clone()
|
||||
})
|
||||
}
|
||||
@@ -564,38 +563,15 @@ mod tests {
|
||||
use unindent::Unindent as _;
|
||||
use util::RandomCharIter;
|
||||
|
||||
// macro_rules! assert_blame_rows {
|
||||
// ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
|
||||
// assert_eq!(
|
||||
// $blame
|
||||
// .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
|
||||
// .collect::<Vec<_>>(),
|
||||
// $expected
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
|
||||
#[track_caller]
|
||||
fn assert_blame_rows(
|
||||
blame: &mut GitBlame,
|
||||
rows: Range<u32>,
|
||||
expected: Vec<Option<BlameEntry>>,
|
||||
cx: &mut ModelContext<GitBlame>,
|
||||
) {
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&rows
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
);
|
||||
macro_rules! assert_blame_rows {
|
||||
($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
|
||||
assert_eq!(
|
||||
$blame
|
||||
.blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
|
||||
.collect::<Vec<_>>(),
|
||||
$expected
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
@@ -658,15 +634,7 @@ mod tests {
|
||||
blame.update(cx, |blame, cx| {
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&(0..1)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![None]
|
||||
);
|
||||
@@ -730,15 +698,7 @@ mod tests {
|
||||
// All lines
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&(0..8)
|
||||
.map(|buffer_row| RowInfo {
|
||||
buffer_row: Some(buffer_row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..1)),
|
||||
@@ -754,15 +714,7 @@ mod tests {
|
||||
// Subset of lines
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&(1..4)
|
||||
.map(|buffer_row| RowInfo {
|
||||
buffer_row: Some(buffer_row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
@@ -773,17 +725,7 @@ mod tests {
|
||||
// Subset of lines, with some not displayed
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&[
|
||||
RowInfo {
|
||||
buffer_row: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
],
|
||||
cx
|
||||
)
|
||||
.blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
|
||||
);
|
||||
@@ -835,16 +777,16 @@ mod tests {
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
// Sanity check before edits: make sure that we get the same blame entry for all
|
||||
// lines.
|
||||
assert_blame_rows(
|
||||
assert_blame_rows!(
|
||||
blame,
|
||||
0..4,
|
||||
(0..4),
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
],
|
||||
cx,
|
||||
cx
|
||||
);
|
||||
});
|
||||
|
||||
@@ -853,11 +795,11 @@ mod tests {
|
||||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "X")], None, cx);
|
||||
});
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows(
|
||||
assert_blame_rows!(
|
||||
blame,
|
||||
0..2,
|
||||
(0..2),
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4))],
|
||||
cx,
|
||||
cx
|
||||
);
|
||||
});
|
||||
// Modify a single line, in the middle of the line
|
||||
@@ -865,21 +807,21 @@ mod tests {
|
||||
buffer.edit([(Point::new(1, 2)..Point::new(1, 2), "X")], None, cx);
|
||||
});
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows(
|
||||
assert_blame_rows!(
|
||||
blame,
|
||||
1..4,
|
||||
(1..4),
|
||||
vec![
|
||||
None,
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4))
|
||||
],
|
||||
cx,
|
||||
cx
|
||||
);
|
||||
});
|
||||
|
||||
// Before we insert a newline at the end, sanity check:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
assert_blame_rows!(blame, (3..4), vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
});
|
||||
// Insert a newline at the end
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
@@ -887,17 +829,17 @@ mod tests {
|
||||
});
|
||||
// Only the new line is marked as edited:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows(
|
||||
assert_blame_rows!(
|
||||
blame,
|
||||
3..5,
|
||||
(3..5),
|
||||
vec![Some(blame_entry("1b1b1b", 0..4)), None],
|
||||
cx,
|
||||
cx
|
||||
);
|
||||
});
|
||||
|
||||
// Before we insert a newline at the start, sanity check:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
assert_blame_rows!(blame, (2..3), vec![Some(blame_entry("1b1b1b", 0..4)),], cx);
|
||||
});
|
||||
|
||||
// Usage example
|
||||
@@ -907,11 +849,11 @@ mod tests {
|
||||
});
|
||||
// Only the new line is marked as edited:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows(
|
||||
assert_blame_rows!(
|
||||
blame,
|
||||
2..4,
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4))],
|
||||
cx,
|
||||
(2..4),
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4)),],
|
||||
cx
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl ProjectDiffEditor {
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut diff_display_editor =
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx);
|
||||
diff_display_editor.set_expand_all_diff_hunks(cx);
|
||||
diff_display_editor.set_expand_all_diff_hunks();
|
||||
diff_display_editor
|
||||
});
|
||||
|
||||
@@ -311,11 +311,9 @@ impl ProjectDiffEditor {
|
||||
.update(&mut cx, |project_diff_editor, cx| {
|
||||
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
|
||||
project_diff_editor.editor.update(cx, |editor, cx| {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
for change_set in change_sets {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
for change_set in change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
@@ -1195,9 +1193,9 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
file_a_editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set.clone(), cx)
|
||||
});
|
||||
file_a_editor
|
||||
.diff_map
|
||||
.add_change_set(change_set.clone(), cx);
|
||||
project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
buffer_store.set_change_set(
|
||||
|
||||
@@ -6,10 +6,11 @@ use gpui::{
|
||||
use language::{Buffer, BufferId, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
|
||||
MultiBufferSnapshot, ToPoint,
|
||||
MultiBufferSnapshot, ToOffset, ToPoint,
|
||||
};
|
||||
use project::buffer_store::BufferChangeSet;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use sum_tree::TreeMap;
|
||||
use text::OffsetRangeExt;
|
||||
use ui::{
|
||||
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
|
||||
@@ -19,10 +20,10 @@ use util::RangeExt;
|
||||
use workspace::Item;
|
||||
|
||||
use crate::{
|
||||
editor_settings::CurrentLineHighlight, hunk_status, ApplyAllDiffHunks, ApplyDiffHunk,
|
||||
BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
|
||||
DisplaySnapshot, Editor, EditorElement, GoToHunk, GoToPrevHunk, RevertFile,
|
||||
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks,
|
||||
ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight,
|
||||
DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
|
||||
RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -36,6 +37,7 @@ pub(super) struct HoveredHunk {
|
||||
pub(super) struct DiffMap {
|
||||
pub(crate) hunks: Vec<ExpandedHunk>,
|
||||
pub(crate) diff_bases: HashMap<BufferId, DiffBaseState>,
|
||||
pub(crate) snapshot: DiffMapSnapshot,
|
||||
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
|
||||
expand_all: bool,
|
||||
}
|
||||
@@ -49,6 +51,9 @@ pub(super) struct ExpandedHunk {
|
||||
pub folded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct DiffMapSnapshot(TreeMap<BufferId, git::diff::BufferDiff>);
|
||||
|
||||
pub(crate) struct DiffBaseState {
|
||||
pub(crate) change_set: Model<BufferChangeSet>,
|
||||
pub(crate) last_version: Option<usize>,
|
||||
@@ -69,7 +74,133 @@ pub enum DisplayDiffHunk {
|
||||
},
|
||||
}
|
||||
|
||||
impl DiffMap {
|
||||
pub fn snapshot(&self) -> DiffMapSnapshot {
|
||||
self.snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn add_change_set(
|
||||
&mut self,
|
||||
change_set: Model<BufferChangeSet>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let buffer_id = change_set.read(cx).buffer_id;
|
||||
self.snapshot
|
||||
.0
|
||||
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
|
||||
self.diff_bases.insert(
|
||||
buffer_id,
|
||||
DiffBaseState {
|
||||
last_version: None,
|
||||
_subscription: cx.observe(&change_set, move |editor, change_set, cx| {
|
||||
editor
|
||||
.diff_map
|
||||
.snapshot
|
||||
.0
|
||||
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
|
||||
Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx);
|
||||
}),
|
||||
change_set,
|
||||
},
|
||||
);
|
||||
Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
|
||||
}
|
||||
|
||||
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
|
||||
self.hunks
|
||||
.iter()
|
||||
.filter(move |hunk| include_folded || !hunk.folded)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffMapSnapshot {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.values().all(|diff| diff.is_empty())
|
||||
}
|
||||
|
||||
pub fn diff_hunks<'a>(
|
||||
&'a self,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot)
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
|
||||
buffer_snapshot
|
||||
.excerpts_for_range(range.clone())
|
||||
.filter_map(move |excerpt| {
|
||||
let buffer = excerpt.buffer();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let diff = self.0.get(&buffer_id)?;
|
||||
let buffer_range = excerpt.map_range_to_buffer(range.clone());
|
||||
let buffer_range =
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
diff.hunks_intersecting_range(buffer_range, excerpt.buffer())
|
||||
.map(move |hunk| {
|
||||
let start =
|
||||
excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0));
|
||||
let end =
|
||||
excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0));
|
||||
MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
|
||||
buffer_id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
|
||||
buffer_snapshot
|
||||
.excerpts_for_range_rev(range.clone())
|
||||
.filter_map(move |excerpt| {
|
||||
let buffer = excerpt.buffer();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let diff = self.0.get(&buffer_id)?;
|
||||
let buffer_range = excerpt.map_range_to_buffer(range.clone());
|
||||
let buffer_range =
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer())
|
||||
.map(move |hunk| {
|
||||
let start_row = excerpt
|
||||
.map_point_from_buffer(Point::new(hunk.row_range.start, 0))
|
||||
.row;
|
||||
let end_row = excerpt
|
||||
.map_point_from_buffer(Point::new(hunk.row_range.end, 0))
|
||||
.row;
|
||||
MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row),
|
||||
buffer_id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn set_expand_all_diff_hunks(&mut self) {
|
||||
self.diff_map.expand_all = true;
|
||||
}
|
||||
|
||||
pub(super) fn toggle_hovered_hunk(
|
||||
&mut self,
|
||||
hovered_hunk: &HoveredHunk,
|
||||
@@ -82,6 +213,47 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let selections = self.selections.all(cx);
|
||||
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
|
||||
}
|
||||
|
||||
pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let display_rows_with_expanded_hunks = self
|
||||
.diff_map
|
||||
.hunks(false)
|
||||
.map(|hunk| &hunk.hunk_range)
|
||||
.map(|anchor_range| {
|
||||
(
|
||||
anchor_range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row(),
|
||||
anchor_range
|
||||
.end
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row(),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let hunks = self
|
||||
.diff_map
|
||||
.snapshot
|
||||
.diff_hunks(&snapshot.display_snapshot.buffer_snapshot)
|
||||
.filter(|hunk| {
|
||||
let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
..Point::new(hunk.row_range.end.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot);
|
||||
let row_range_end =
|
||||
display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
|
||||
row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())
|
||||
});
|
||||
self.toggle_hunks_expanded(hunks.collect(), cx);
|
||||
}
|
||||
|
||||
fn toggle_hunks_expanded(
|
||||
&mut self,
|
||||
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
|
||||
@@ -325,8 +497,7 @@ impl Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let ranges = self.selections.all(cx).into_iter().map(|s| s.range());
|
||||
let hunks = snapshot.hunks_for_ranges(ranges);
|
||||
let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
|
||||
let mut ranges_by_buffer = HashMap::default();
|
||||
self.transact(cx, |editor, cx| {
|
||||
for hunk in hunks {
|
||||
@@ -350,8 +521,10 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn has_multiple_hunks(&self, cx: &mut WindowContext) -> bool {
|
||||
self.display_map.read(cx).has_multiple_hunks(cx)
|
||||
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot);
|
||||
hunks.nth(1).is_some()
|
||||
}
|
||||
|
||||
fn hunk_header_block(
|
||||
@@ -692,15 +865,23 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
|
||||
self.display_map.update(cx, |diff_map, cx| {
|
||||
let ranges = vec![Anchor::min()..Anchor::max()];
|
||||
if diff_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
|
||||
diff_map.collapse_diff_hunks(ranges, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
if self.diff_map.expand_all {
|
||||
return false;
|
||||
}
|
||||
self.diff_map.hunk_update_tasks.clear();
|
||||
self.clear_row_highlights::<DiffRowHighlight>();
|
||||
let to_remove = self
|
||||
.diff_map
|
||||
.hunks
|
||||
.drain(..)
|
||||
.flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
|
||||
.collect::<HashSet<_>>();
|
||||
if to_remove.is_empty() {
|
||||
false
|
||||
} else {
|
||||
self.remove_blocks(to_remove, None, cx);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sync_expanded_diff_hunks(
|
||||
@@ -728,7 +909,8 @@ impl Editor {
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let mut recalculated_hunks = snapshot
|
||||
.diff_hunks()
|
||||
.diff_map
|
||||
.diff_hunks(&snapshot.buffer_snapshot)
|
||||
.filter(|hunk| hunk.buffer_id == buffer_id)
|
||||
.fuse()
|
||||
.peekable();
|
||||
@@ -1236,9 +1418,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -1290,7 +1470,8 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_hunks_in_range(Point::zero()..Point::new(12, 0))
|
||||
.diff_map
|
||||
.diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot)
|
||||
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected,
|
||||
@@ -1298,7 +1479,11 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_hunks_in_range_rev(Point::zero()..Point::new(12, 0))
|
||||
.diff_map
|
||||
.diff_hunks_in_range_rev(
|
||||
Point::zero()..Point::new(12, 0),
|
||||
&snapshot.buffer_snapshot
|
||||
)
|
||||
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
|
||||
@@ -321,6 +321,10 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
|
||||
"Fake Completion Provider"
|
||||
}
|
||||
|
||||
fn show_completions_in_menu() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
_buffer: &gpui::Model<language::Buffer>,
|
||||
|
||||
@@ -61,7 +61,7 @@ impl ProposedChangesEditor {
|
||||
let mut this = Self {
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
editor.set_expand_all_diff_hunks();
|
||||
editor.set_completion_provider(None);
|
||||
editor.clear_code_action_providers();
|
||||
editor.set_semantics_provider(
|
||||
@@ -223,11 +223,9 @@ impl ProposedChangesEditor {
|
||||
self.buffer_entries = buffer_entries;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| selections.refresh());
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
for change_set in new_change_sets {
|
||||
display_map.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
for change_set in new_change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -391,7 +391,7 @@ impl SelectionsCollection {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn change_with<R>(
|
||||
pub fn change_with<R>(
|
||||
&mut self,
|
||||
cx: &mut AppContext,
|
||||
change: impl FnOnce(&mut MutableSelectionsCollection) -> R,
|
||||
@@ -764,7 +764,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
|
||||
pub fn replace_cursors_with(
|
||||
&mut self,
|
||||
mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec<DisplayPoint>,
|
||||
find_replacement_cursors: impl FnOnce(&DisplaySnapshot) -> Vec<DisplayPoint>,
|
||||
) {
|
||||
let display_map = self.display_map();
|
||||
let new_selections = find_replacement_cursors(&display_map)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
RowExt,
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint,
|
||||
Editor, MultiBuffer, RowExt,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use futures::Future;
|
||||
@@ -11,12 +11,11 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use multi_buffer::ExcerptRange;
|
||||
use multi_buffer::{ExcerptRange, ToPoint};
|
||||
use parking_lot::RwLock;
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
path::Path,
|
||||
sync::{
|
||||
@@ -24,7 +23,6 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use text::Bias;
|
||||
use util::{
|
||||
assert_set_eq,
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
@@ -334,51 +332,83 @@ impl EditorTestContext {
|
||||
///
|
||||
/// Diff hunks are indicated by lines starting with `+` and `-`.
|
||||
#[track_caller]
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff_text: String) {
|
||||
let (snapshot, selections) = self
|
||||
.editor
|
||||
.update(&mut self.cx, |editor, cx| editor.selections.all_display(cx));
|
||||
|
||||
let diff_snapshot = snapshot.diff_snapshot();
|
||||
let diff_offsets = selections
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let start = snapshot.display_point_to_diff_offset(s.start, Bias::Left).0;
|
||||
let end = snapshot.display_point_to_diff_offset(s.end, Bias::Left).0;
|
||||
cmp::min(start, end)..cmp::max(start, end)
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff: String) {
|
||||
let has_diff_markers = expected_diff
|
||||
.lines()
|
||||
.any(|line| line.starts_with("+") || line.starts_with("-"));
|
||||
let expected_diff_text = expected_diff
|
||||
.split('\n')
|
||||
.map(|line| {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
String::new()
|
||||
} else if has_diff_markers {
|
||||
line.to_string()
|
||||
} else {
|
||||
format!(" {line}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.join("\n");
|
||||
|
||||
let actual_marked_text = generate_marked_text(&diff_snapshot.text(), &diff_offsets, true);
|
||||
let actual_selections = self.editor_selections();
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
|
||||
// Read the actual diff from the editor's row highlights and block
|
||||
// decorations.
|
||||
let line_infos = diff_snapshot.row_infos(0).collect::<Vec<_>>();
|
||||
let has_diff = line_infos.iter().any(|info| info.diff_status.is_some());
|
||||
|
||||
let actual_diff = actual_marked_text
|
||||
.split('\n')
|
||||
.zip(line_infos)
|
||||
.map(|(line, info)| {
|
||||
let mut marker = match info.diff_status {
|
||||
Some(DiffHunkStatus::Added) => "+ ",
|
||||
Some(DiffHunkStatus::Removed) => "- ",
|
||||
Some(DiffHunkStatus::Modified) => unreachable!(),
|
||||
None => {
|
||||
if has_diff {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let insertions = editor
|
||||
.highlighted_rows::<DiffRowHighlight>()
|
||||
.map(|(range, _)| {
|
||||
let start = range.start.to_point(&snapshot.buffer_snapshot);
|
||||
let end = range.end.to_point(&snapshot.buffer_snapshot);
|
||||
start.row..end.row
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let deletions = editor
|
||||
.diff_map
|
||||
.hunks
|
||||
.iter()
|
||||
.filter_map(|hunk| {
|
||||
if hunk.blocks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if line.is_empty() {
|
||||
marker = marker.trim();
|
||||
}
|
||||
format!("{marker}{line}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let row = hunk
|
||||
.hunk_range
|
||||
.start
|
||||
.to_point(&snapshot.buffer_snapshot)
|
||||
.row;
|
||||
let (_, buffer, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(hunk.hunk_range.start, cx)
|
||||
.expect("no excerpt for expanded buffer's hunk start");
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let change_set = &editor
|
||||
.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer_id)
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.change_set;
|
||||
let deleted_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
.as_ref()
|
||||
.expect("no base text for expanded hunk")
|
||||
.read(cx)
|
||||
.as_rope()
|
||||
.slice(hunk.diff_base_byte_range.clone())
|
||||
.to_string();
|
||||
if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status {
|
||||
Some((row, deleted_text))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format_diff(actual_marked_text, deletions, insertions)
|
||||
});
|
||||
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
|
||||
}
|
||||
@@ -473,6 +503,46 @@ impl EditorTestContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_diff(
|
||||
text: String,
|
||||
actual_deletions: Vec<(u32, String)>,
|
||||
actual_insertions: Vec<Range<u32>>,
|
||||
) -> String {
|
||||
let mut diff = String::new();
|
||||
for (row, line) in text.split('\n').enumerate() {
|
||||
let row = row as u32;
|
||||
if row > 0 {
|
||||
diff.push('\n');
|
||||
}
|
||||
if let Some(text) = actual_deletions
|
||||
.iter()
|
||||
.find_map(|(deletion_row, deleted_text)| {
|
||||
if *deletion_row == row {
|
||||
Some(deleted_text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
for line in text.lines() {
|
||||
diff.push('-');
|
||||
if !line.is_empty() {
|
||||
diff.push(' ');
|
||||
diff.push_str(line);
|
||||
}
|
||||
diff.push('\n');
|
||||
}
|
||||
}
|
||||
let marker = if actual_insertions.iter().any(|range| range.contains(&row)) {
|
||||
"+ "
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
diff.push_str(format!("{marker}{line}").trim_end());
|
||||
}
|
||||
diff
|
||||
}
|
||||
|
||||
impl Deref for EditorTestContext {
|
||||
type Target = gpui::VisualTestContext;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ semantic_version.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
telemetry.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -48,7 +48,8 @@ impl RenderOnce for ExtensionCard {
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.occlude()
|
||||
.block_mouse_down()
|
||||
.cursor_default()
|
||||
.size_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use client::telemetry::Telemetry;
|
||||
use gpui::{AnyElement, Div, StyleRefinement};
|
||||
use smallvec::SmallVec;
|
||||
use ui::{prelude::*, ButtonLike};
|
||||
@@ -8,17 +5,15 @@ use ui::{prelude::*, ButtonLike};
|
||||
#[derive(IntoElement)]
|
||||
pub struct FeatureUpsell {
|
||||
base: Div,
|
||||
telemetry: Arc<Telemetry>,
|
||||
text: SharedString,
|
||||
docs_url: Option<SharedString>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl FeatureUpsell {
|
||||
pub fn new(telemetry: Arc<Telemetry>, text: impl Into<SharedString>) -> Self {
|
||||
pub fn new(text: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
base: h_flex(),
|
||||
telemetry,
|
||||
text: text.into(),
|
||||
docs_url: None,
|
||||
children: SmallVec::new(),
|
||||
@@ -67,12 +62,13 @@ impl RenderOnce for FeatureUpsell {
|
||||
.child(Icon::new(IconName::ArrowUpRight)),
|
||||
)
|
||||
.on_click({
|
||||
let telemetry = self.telemetry.clone();
|
||||
let docs_url = docs_url.clone();
|
||||
move |_event, cx| {
|
||||
telemetry.report_app_event(format!(
|
||||
"feature upsell: viewed docs ({docs_url})"
|
||||
));
|
||||
telemetry::event!(
|
||||
"Documentation Viewed",
|
||||
source = "Feature Upsell",
|
||||
url = docs_url,
|
||||
);
|
||||
cx.open_url(&docs_url)
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use client::telemetry::Telemetry;
|
||||
use client::ExtensionMetadata;
|
||||
use collections::{BTreeMap, BTreeSet};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
@@ -182,7 +181,6 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
|
||||
pub struct ExtensionsPage {
|
||||
workspace: WeakView<Workspace>,
|
||||
list: UniformListScrollHandle,
|
||||
telemetry: Arc<Telemetry>,
|
||||
is_fetching_extensions: bool,
|
||||
filter: ExtensionFilter,
|
||||
remote_extension_entries: Vec<ExtensionMetadata>,
|
||||
@@ -221,7 +219,6 @@ impl ExtensionsPage {
|
||||
let mut this = Self {
|
||||
workspace: workspace.weak_handle(),
|
||||
list: UniformListScrollHandle::new(),
|
||||
telemetry: workspace.client().telemetry().clone(),
|
||||
is_fetching_extensions: false,
|
||||
filter: ExtensionFilter::All,
|
||||
dev_extension_entries: Vec::new(),
|
||||
@@ -704,18 +701,15 @@ impl ExtensionsPage {
|
||||
|
||||
match status.clone() {
|
||||
ExtensionStatus::NotInstalled => (
|
||||
Button::new(SharedString::from(extension.id.clone()), "Install").on_click(
|
||||
cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("extensions: install extension".to_string());
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.install_latest_extension(extension_id.clone(), cx)
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
Button::new(SharedString::from(extension.id.clone()), "Install").on_click({
|
||||
let extension_id = extension.id.clone();
|
||||
move |_, cx| {
|
||||
telemetry::event!("Extension Installed");
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.install_latest_extension(extension_id.clone(), cx)
|
||||
});
|
||||
}
|
||||
}),
|
||||
None,
|
||||
),
|
||||
ExtensionStatus::Installing => (
|
||||
@@ -729,18 +723,15 @@ impl ExtensionsPage {
|
||||
),
|
||||
),
|
||||
ExtensionStatus::Installed(installed_version) => (
|
||||
Button::new(SharedString::from(extension.id.clone()), "Uninstall").on_click(
|
||||
cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("extensions: uninstall extension".to_string());
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(extension_id.clone(), cx)
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
Button::new(SharedString::from(extension.id.clone()), "Uninstall").on_click({
|
||||
let extension_id = extension.id.clone();
|
||||
move |_, cx| {
|
||||
telemetry::event!("Extension Uninstalled", extension_id);
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(extension_id.clone(), cx)
|
||||
});
|
||||
}
|
||||
}),
|
||||
if installed_version == extension.manifest.version {
|
||||
None
|
||||
} else {
|
||||
@@ -760,13 +751,11 @@ impl ExtensionsPage {
|
||||
})
|
||||
})
|
||||
.disabled(!is_compatible)
|
||||
.on_click(cx.listener({
|
||||
.on_click({
|
||||
let extension_id = extension.id.clone();
|
||||
let version = extension.manifest.version.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"extensions: install extension".to_string(),
|
||||
);
|
||||
move |_, cx| {
|
||||
telemetry::event!("Extension Installed", extension_id, version);
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store
|
||||
.upgrade_extension(
|
||||
@@ -777,7 +766,7 @@ impl ExtensionsPage {
|
||||
.detach_and_log_err(cx)
|
||||
});
|
||||
}
|
||||
})),
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
@@ -972,19 +961,16 @@ impl ExtensionsPage {
|
||||
let upsells_count = self.upsells.len();
|
||||
|
||||
v_flex().children(self.upsells.iter().enumerate().map(|(ix, feature)| {
|
||||
let telemetry = self.telemetry.clone();
|
||||
let upsell = match feature {
|
||||
Feature::Git => FeatureUpsell::new(
|
||||
telemetry,
|
||||
"Zed comes with basic Git support. More Git features are coming in the future.",
|
||||
)
|
||||
.docs_url("https://zed.dev/docs/git"),
|
||||
Feature::OpenIn => FeatureUpsell::new(
|
||||
telemetry,
|
||||
"Zed supports linking to a source line on GitHub and others.",
|
||||
)
|
||||
.docs_url("https://zed.dev/docs/git#git-integrations"),
|
||||
Feature::Vim => FeatureUpsell::new(telemetry, "Vim support is built-in to Zed!")
|
||||
Feature::Vim => FeatureUpsell::new("Vim support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/vim")
|
||||
.child(CheckboxWithLabel::new(
|
||||
"enable-vim",
|
||||
@@ -995,8 +981,7 @@ impl ExtensionsPage {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("feature upsell: toggle vim".to_string());
|
||||
telemetry::event!("Vim Mode Toggled", source = "Feature Upsell");
|
||||
this.update_settings::<VimModeSetting>(
|
||||
selection,
|
||||
cx,
|
||||
@@ -1004,36 +989,22 @@ impl ExtensionsPage {
|
||||
);
|
||||
}),
|
||||
)),
|
||||
Feature::LanguageBash => {
|
||||
FeatureUpsell::new(telemetry, "Shell support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/bash")
|
||||
}
|
||||
Feature::LanguageC => {
|
||||
FeatureUpsell::new(telemetry, "C support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/c")
|
||||
}
|
||||
Feature::LanguageCpp => {
|
||||
FeatureUpsell::new(telemetry, "C++ support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/cpp")
|
||||
}
|
||||
Feature::LanguageGo => {
|
||||
FeatureUpsell::new(telemetry, "Go support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/go")
|
||||
}
|
||||
Feature::LanguagePython => {
|
||||
FeatureUpsell::new(telemetry, "Python support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/python")
|
||||
}
|
||||
Feature::LanguageReact => {
|
||||
FeatureUpsell::new(telemetry, "React support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/typescript")
|
||||
}
|
||||
Feature::LanguageRust => {
|
||||
FeatureUpsell::new(telemetry, "Rust support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/rust")
|
||||
}
|
||||
Feature::LanguageBash => FeatureUpsell::new("Shell support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/bash"),
|
||||
Feature::LanguageC => FeatureUpsell::new("C support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/c"),
|
||||
Feature::LanguageCpp => FeatureUpsell::new("C++ support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/cpp"),
|
||||
Feature::LanguageGo => FeatureUpsell::new("Go support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/go"),
|
||||
Feature::LanguagePython => FeatureUpsell::new("Python support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/python"),
|
||||
Feature::LanguageReact => FeatureUpsell::new("React support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/typescript"),
|
||||
Feature::LanguageRust => FeatureUpsell::new("Rust support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/rust"),
|
||||
Feature::LanguageTypescript => {
|
||||
FeatureUpsell::new(telemetry, "Typescript support is built-in to Zed!")
|
||||
FeatureUpsell::new("Typescript support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/languages/typescript")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,13 +21,26 @@ const fn zed_repo_url() -> &'static str {
|
||||
"https://github.com/zed-industries/zed"
|
||||
}
|
||||
|
||||
const fn request_feature_url() -> &'static str {
|
||||
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cenhancement&projects=&template=0_feature_request.yml"
|
||||
fn request_feature_url(specs: &SystemSpecs) -> String {
|
||||
format!(
|
||||
concat!(
|
||||
"https://github.com/zed-industries/zed/issues/new",
|
||||
"?labels=admin+read%2Ctriage%2Cenhancement",
|
||||
"&template=0_feature_request.yml",
|
||||
"&environment={}"
|
||||
),
|
||||
urlencoding::encode(&specs.to_string())
|
||||
)
|
||||
}
|
||||
|
||||
fn file_bug_report_url(specs: &SystemSpecs) -> String {
|
||||
format!(
|
||||
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cbug&projects=&template=1_bug_report.yml&environment={}",
|
||||
concat!(
|
||||
"https://github.com/zed-industries/zed/issues/new",
|
||||
"?labels=admin+read%2Ctriage%2Cbug",
|
||||
"&template=1_bug_report.yml",
|
||||
"&environment={}"
|
||||
),
|
||||
urlencoding::encode(&specs.to_string())
|
||||
)
|
||||
}
|
||||
@@ -57,7 +70,15 @@ pub fn init(cx: &mut AppContext) {
|
||||
.detach();
|
||||
})
|
||||
.register_action(|_, _: &RequestFeature, cx| {
|
||||
cx.open_url(request_feature_url());
|
||||
let specs = SystemSpecs::new(cx);
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let specs = specs.await;
|
||||
cx.update(|cx| {
|
||||
cx.open_url(&request_feature_url(&specs));
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.register_action(move |_, _: &FileBugReport, cx| {
|
||||
let specs = SystemSpecs::new(cx);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use std::{ops::RangeInclusive, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
ops::RangeInclusive,
|
||||
sync::{Arc, LazyLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use bitflags::bitflags;
|
||||
@@ -34,7 +38,8 @@ const DEV_MODE: bool = true;
|
||||
const DEV_MODE: bool = false;
|
||||
|
||||
const DATABASE_KEY_NAME: &str = "email_address";
|
||||
const EMAIL_REGEX: &str = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
|
||||
static EMAIL_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap());
|
||||
const FEEDBACK_CHAR_LIMIT: RangeInclusive<i32> = 10..=5000;
|
||||
const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
||||
"Feedback failed to submit, see error log for details.";
|
||||
@@ -320,7 +325,7 @@ impl FeedbackModal {
|
||||
let mut invalid_state_flags = InvalidStateFlags::empty();
|
||||
|
||||
let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) {
|
||||
Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address),
|
||||
Some(email_address) => EMAIL_REGEX.is_match(&email_address),
|
||||
None => true,
|
||||
};
|
||||
|
||||
|
||||
@@ -47,9 +47,12 @@ windows.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
ashpd.workspace = true
|
||||
which.workspace = true
|
||||
shlex.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[features]
|
||||
test-support = ["gpui/test-support", "git/test-support"]
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ use git::GitHostingProviderRegistry;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use ashpd::desktop::trash;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use smol::process::Command;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use std::fs::File;
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsFd;
|
||||
@@ -514,24 +516,7 @@ impl Fs for RealFs {
|
||||
|
||||
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
|
||||
smol::unblock(move || {
|
||||
let mut tmp_file = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
// Use the directory of the destination as temp dir to avoid
|
||||
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
|
||||
// See https://github.com/zed-industries/zed/pull/8437 for more details.
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))
|
||||
} else if cfg!(target_os = "windows") {
|
||||
// If temp dir is set to a different drive than the destination,
|
||||
// we receive error:
|
||||
//
|
||||
// failed to persist temporary file:
|
||||
// The system cannot move the file to a different disk drive. (os error 17)
|
||||
//
|
||||
// So we use the directory of the destination as a temp dir to avoid it.
|
||||
// https://github.com/zed-industries/zed/issues/16571
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))
|
||||
} else {
|
||||
NamedTempFile::new()
|
||||
}?;
|
||||
let mut tmp_file = create_temp_file(&path)?;
|
||||
tmp_file.write_all(data.as_bytes())?;
|
||||
tmp_file.persist(path)?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
@@ -546,13 +531,43 @@ impl Fs for RealFs {
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
let file = smol::fs::File::create(path).await?;
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
|
||||
for chunk in chunks(text, line_ending) {
|
||||
writer.write_all(chunk.as_bytes()).await?;
|
||||
match smol::fs::File::create(path).await {
|
||||
Ok(file) => {
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
|
||||
for chunk in chunks(text, line_ending) {
|
||||
writer.write_all(chunk.as_bytes()).await?;
|
||||
}
|
||||
writer.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
let target_path = path.to_path_buf();
|
||||
let temp_file = smol::unblock(move || create_temp_file(&target_path)).await?;
|
||||
|
||||
let temp_path = temp_file.into_temp_path();
|
||||
let temp_path_for_write = temp_path.to_path_buf();
|
||||
|
||||
let async_file = smol::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&temp_path)
|
||||
.await?;
|
||||
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, async_file);
|
||||
|
||||
for chunk in chunks(text, line_ending) {
|
||||
writer.write_all(chunk.as_bytes()).await?;
|
||||
}
|
||||
writer.flush().await?;
|
||||
|
||||
write_to_file_as_root(temp_path_for_write, path.to_path_buf()).await
|
||||
} else {
|
||||
// Todo: Implement for Mac and Windows
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
writer.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
|
||||
@@ -1999,6 +2014,84 @@ fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
|
||||
})
|
||||
}
|
||||
|
||||
fn create_temp_file(path: &Path) -> Result<NamedTempFile> {
|
||||
let temp_file = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
// Use the directory of the destination as temp dir to avoid
|
||||
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
|
||||
// See https://github.com/zed-industries/zed/pull/8437 for more details.
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))?
|
||||
} else if cfg!(target_os = "windows") {
|
||||
// If temp dir is set to a different drive than the destination,
|
||||
// we receive error:
|
||||
//
|
||||
// failed to persist temporary file:
|
||||
// The system cannot move the file to a different disk drive. (os error 17)
|
||||
//
|
||||
// So we use the directory of the destination as a temp dir to avoid it.
|
||||
// https://github.com/zed-industries/zed/issues/16571
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))?
|
||||
} else {
|
||||
NamedTempFile::new()?
|
||||
};
|
||||
|
||||
Ok(temp_file)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn write_to_file_as_root(_temp_file_path: PathBuf, _target_file_path: PathBuf) -> Result<()> {
|
||||
unimplemented!("write_to_file_as_root is not implemented")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn write_to_file_as_root(_temp_file_path: PathBuf, _target_file_path: PathBuf) -> Result<()> {
|
||||
unimplemented!("write_to_file_as_root is not implemented")
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
async fn write_to_file_as_root(temp_file_path: PathBuf, target_file_path: PathBuf) -> Result<()> {
|
||||
use shlex::try_quote;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use which::which;
|
||||
|
||||
let pkexec_path = smol::unblock(|| which("pkexec"))
|
||||
.await
|
||||
.map_err(|_| anyhow::anyhow!("pkexec not found in PATH"))?;
|
||||
|
||||
let script_file = smol::unblock(move || {
|
||||
let script_file = tempfile::Builder::new()
|
||||
.prefix("write-to-file-as-root-")
|
||||
.tempfile_in(paths::temp_dir())?;
|
||||
|
||||
writeln!(
|
||||
script_file.as_file(),
|
||||
"#!/usr/bin/env sh\nset -eu\ncat \"{}\" > \"{}\"",
|
||||
try_quote(&temp_file_path.to_string_lossy())?,
|
||||
try_quote(&target_file_path.to_string_lossy())?
|
||||
)?;
|
||||
|
||||
let mut perms = script_file.as_file().metadata()?.permissions();
|
||||
perms.set_mode(0o700); // rwx------
|
||||
script_file.as_file().set_permissions(perms)?;
|
||||
|
||||
Result::<_>::Ok(script_file)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let script_path = script_file.into_temp_path();
|
||||
|
||||
let output = Command::new(&pkexec_path)
|
||||
.arg("--disable-internal-agent")
|
||||
.arg(&script_path)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to write to file as root"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
let mut components = path.components().peekable();
|
||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
@@ -15,9 +15,9 @@ use git::{
|
||||
};
|
||||
|
||||
fn pull_request_number_regex() -> &'static Regex {
|
||||
static PULL_REQUEST_NUMBER_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
PULL_REQUEST_NUMBER_REGEX.get_or_init(|| Regex::new(r"\(#(\d+)\)$").unwrap())
|
||||
static PULL_REQUEST_NUMBER_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\(#(\d+)\)$").unwrap());
|
||||
&PULL_REQUEST_NUMBER_REGEX
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -136,8 +136,8 @@ font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7"
|
||||
foreign-types = "0.5"
|
||||
log.workspace = true
|
||||
media.workspace = true
|
||||
metal = "0.29"
|
||||
objc = "0.2"
|
||||
metal.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]
|
||||
pathfinder_geometry = "0.5"
|
||||
|
||||
@@ -815,14 +815,8 @@ where
|
||||
Bounds { origin, size }
|
||||
}
|
||||
|
||||
/// Constructs a `Bounds` from a corner point and size.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Corner, Point};
|
||||
/// todo!
|
||||
/// ```
|
||||
/// Constructs a `Bounds` from a corner point and size. The specified corner will be placed at
|
||||
/// the specified origin.
|
||||
pub fn from_corner_and_size(corner: Corner, origin: Point<T>, size: Size<T>) -> Bounds<T> {
|
||||
let origin = match corner {
|
||||
Corner::TopLeft => origin,
|
||||
@@ -1003,6 +997,18 @@ where
|
||||
size: self.size.clone() + size(double_amount.clone(), double_amount),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the bounds different amounts in each direction.
|
||||
pub fn extend(&self, amount: Edges<T>) -> Bounds<T> {
|
||||
Bounds {
|
||||
origin: self.origin.clone() - point(amount.left.clone(), amount.top.clone()),
|
||||
size: self.size.clone()
|
||||
+ size(
|
||||
amount.left.clone() + amount.right.clone(),
|
||||
amount.top.clone() + amount.bottom.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
@@ -1097,6 +1103,21 @@ impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output =
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bounds<T>
|
||||
where
|
||||
T: Clone + Debug + Add<T, Output = T> + Sub<T, Output = T> + Default,
|
||||
{
|
||||
/// Computes the space available within outer bounds.
|
||||
pub fn space_within(&self, outer: &Self) -> Edges<T> {
|
||||
Edges {
|
||||
top: self.top().clone() - outer.top().clone(),
|
||||
right: outer.right().clone() - self.right().clone(),
|
||||
bottom: outer.bottom().clone() - self.bottom().clone(),
|
||||
left: self.left().clone() - outer.left().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Rhs> Mul<Rhs> for Bounds<T>
|
||||
where
|
||||
T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
|
||||
|
||||
@@ -344,15 +344,15 @@ impl<T> Flatten<T> for Result<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the GPU GPUI is running on.
|
||||
#[derive(Default, Debug)]
|
||||
/// Information about the GPU GPUI is running on
|
||||
pub struct GPUSpecs {
|
||||
/// true if the GPU is really a fake (like llvmpipe) running on the CPU
|
||||
pub struct GpuSpecs {
|
||||
/// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU.
|
||||
pub is_software_emulated: bool,
|
||||
/// Name of the device as reported by vulkan
|
||||
/// The name of the device, as reported by Vulkan.
|
||||
pub device_name: String,
|
||||
/// Name of the driver as reported by vulkan
|
||||
/// The name of the driver, as reported by Vulkan.
|
||||
pub driver_name: String,
|
||||
/// Further driver info as reported by vulkan
|
||||
/// Further information about the driver, as reported by Vulkan.
|
||||
pub driver_info: String,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ mod windows;
|
||||
|
||||
use crate::{
|
||||
point, Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
|
||||
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GPUSpecs, GlyphId,
|
||||
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs,
|
||||
ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImage,
|
||||
RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, SvgRenderer,
|
||||
SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
|
||||
@@ -432,7 +432,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
WindowControls::default()
|
||||
}
|
||||
fn set_client_inset(&self, _inset: Pixels) {}
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs>;
|
||||
fn gpu_specs(&self) -> Option<GpuSpecs>;
|
||||
|
||||
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>);
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod apple_compat;
|
||||
mod blade_atlas;
|
||||
mod blade_context;
|
||||
mod blade_renderer;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) use apple_compat::*;
|
||||
pub(crate) use blade_atlas::*;
|
||||
pub(crate) use blade_context::*;
|
||||
pub(crate) use blade_renderer::*;
|
||||
|
||||
60
crates/gpui/src/platform/blade/apple_compat.rs
Normal file
60
crates/gpui/src/platform/blade/apple_compat.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use super::{BladeContext, BladeRenderer, BladeSurfaceConfig};
|
||||
use blade_graphics as gpu;
|
||||
use std::{ffi::c_void, ptr::NonNull};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
inner: BladeContext,
|
||||
}
|
||||
impl Default for Context {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: BladeContext::new().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Renderer = BladeRenderer;
|
||||
|
||||
pub unsafe fn new_renderer(
|
||||
context: Context,
|
||||
_native_window: *mut c_void,
|
||||
native_view: *mut c_void,
|
||||
bounds: crate::Size<f32>,
|
||||
transparent: bool,
|
||||
) -> Renderer {
|
||||
use raw_window_handle as rwh;
|
||||
struct RawWindow {
|
||||
view: *mut c_void,
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
let view = NonNull::new(self.view).unwrap();
|
||||
let handle = rwh::AppKitWindowHandle::new(view);
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
let handle = rwh::AppKitDisplayHandle::new();
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
BladeRenderer::new(
|
||||
&context.inner,
|
||||
&RawWindow {
|
||||
view: native_view as *mut _,
|
||||
},
|
||||
BladeSurfaceConfig {
|
||||
size: gpu::Extent {
|
||||
width: bounds.width as u32,
|
||||
height: bounds.height as u32,
|
||||
depth: 1,
|
||||
},
|
||||
transparent,
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -268,7 +268,7 @@ impl BladeAtlasState {
|
||||
fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
|
||||
self.flush_initializations(encoder);
|
||||
|
||||
let mut transfers = encoder.transfer();
|
||||
let mut transfers = encoder.transfer("atlas");
|
||||
for upload in self.uploads.drain(..) {
|
||||
let texture = &self.storage[upload.id];
|
||||
transfers.copy_buffer_to_texture(
|
||||
|
||||
24
crates/gpui/src/platform/blade/blade_context.rs
Normal file
24
crates/gpui/src/platform/blade/blade_context.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use blade_graphics as gpu;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg_attr(target_os = "macos", derive(Clone))]
|
||||
pub struct BladeContext {
|
||||
pub(super) gpu: Arc<gpu::Context>,
|
||||
}
|
||||
|
||||
impl BladeContext {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let gpu = Arc::new(
|
||||
unsafe {
|
||||
gpu::Context::init(gpu::ContextDesc {
|
||||
presentation: true,
|
||||
validation: false,
|
||||
device_id: 0, //TODO: hook up to user settings
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
|
||||
);
|
||||
Ok(Self { gpu })
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
// Doing `if let` gives you nice scoping with passes/encoders
|
||||
#![allow(irrefutable_let_patterns)]
|
||||
|
||||
use super::{BladeAtlas, PATH_TEXTURE_FORMAT};
|
||||
use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
|
||||
use crate::{
|
||||
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GPUSpecs,
|
||||
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
|
||||
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
|
||||
ScaledPixels, Scene, Shadow, Size, Underline,
|
||||
};
|
||||
@@ -11,8 +11,6 @@ use bytemuck::{Pod, Zeroable};
|
||||
use collections::HashMap;
|
||||
#[cfg(target_os = "macos")]
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::{ffi::c_void, ptr::NonNull};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use blade_util::{BufferBelt, BufferBeltDescriptor};
|
||||
@@ -20,66 +18,6 @@ use std::{mem, sync::Arc};
|
||||
|
||||
const MAX_FRAME_TIME_MS: u32 = 10000;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Context {}
|
||||
#[cfg(target_os = "macos")]
|
||||
pub type Renderer = BladeRenderer;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub unsafe fn new_renderer(
|
||||
_context: self::Context,
|
||||
_native_window: *mut c_void,
|
||||
native_view: *mut c_void,
|
||||
bounds: crate::Size<f32>,
|
||||
transparent: bool,
|
||||
) -> Renderer {
|
||||
use raw_window_handle as rwh;
|
||||
struct RawWindow {
|
||||
view: *mut c_void,
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
let view = NonNull::new(self.view).unwrap();
|
||||
let handle = rwh::AppKitWindowHandle::new(view);
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
let handle = rwh::AppKitDisplayHandle::new();
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
let gpu = Arc::new(
|
||||
gpu::Context::init_windowed(
|
||||
&RawWindow {
|
||||
view: native_view as *mut _,
|
||||
},
|
||||
gpu::ContextDesc {
|
||||
validation: cfg!(debug_assertions),
|
||||
capture: false,
|
||||
overlay: false,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
BladeRenderer::new(
|
||||
gpu,
|
||||
BladeSurfaceConfig {
|
||||
size: gpu::Extent {
|
||||
width: bounds.width as u32,
|
||||
height: bounds.height as u32,
|
||||
depth: 1,
|
||||
},
|
||||
transparent,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
struct GlobalParams {
|
||||
@@ -354,10 +292,14 @@ pub struct BladeSurfaceConfig {
|
||||
pub transparent: bool,
|
||||
}
|
||||
|
||||
//Note: we could see some of these fields moved into `BladeContext`
|
||||
// so that they are shared between windows. E.g. `pipelines`.
|
||||
// But that is complicated by the fact that pipelines depend on
|
||||
// the format and alpha mode.
|
||||
pub struct BladeRenderer {
|
||||
gpu: Arc<gpu::Context>,
|
||||
surface: gpu::Surface,
|
||||
surface_config: gpu::SurfaceConfig,
|
||||
alpha_mode: gpu::AlphaMode,
|
||||
command_encoder: gpu::CommandEncoder,
|
||||
last_sync_point: Option<gpu::SyncPoint>,
|
||||
pipelines: BladePipelines,
|
||||
@@ -370,7 +312,11 @@ pub struct BladeRenderer {
|
||||
}
|
||||
|
||||
impl BladeRenderer {
|
||||
pub fn new(gpu: Arc<gpu::Context>, config: BladeSurfaceConfig) -> Self {
|
||||
pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
|
||||
context: &BladeContext,
|
||||
window: &I,
|
||||
config: BladeSurfaceConfig,
|
||||
) -> anyhow::Result<Self> {
|
||||
let surface_config = gpu::SurfaceConfig {
|
||||
size: config.size,
|
||||
usage: gpu::TextureUsage::TARGET,
|
||||
@@ -379,20 +325,23 @@ impl BladeRenderer {
|
||||
allow_exclusive_full_screen: false,
|
||||
transparent: config.transparent,
|
||||
};
|
||||
let surface_info = gpu.resize(surface_config);
|
||||
let surface = context
|
||||
.gpu
|
||||
.create_surface_configured(window, surface_config)
|
||||
.unwrap();
|
||||
|
||||
let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
|
||||
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
|
||||
name: "main",
|
||||
buffer_count: 2,
|
||||
});
|
||||
let pipelines = BladePipelines::new(&gpu, surface_info);
|
||||
let pipelines = BladePipelines::new(&context.gpu, surface.info());
|
||||
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
|
||||
memory: gpu::Memory::Shared,
|
||||
min_chunk_size: 0x1000,
|
||||
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
|
||||
});
|
||||
let atlas = Arc::new(BladeAtlas::new(&gpu));
|
||||
let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
|
||||
let atlas = Arc::new(BladeAtlas::new(&context.gpu));
|
||||
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
|
||||
name: "atlas",
|
||||
mag_filter: gpu::FilterMode::Linear,
|
||||
min_filter: gpu::FilterMode::Linear,
|
||||
@@ -402,13 +351,13 @@ impl BladeRenderer {
|
||||
#[cfg(target_os = "macos")]
|
||||
let core_video_texture_cache = unsafe {
|
||||
use foreign_types::ForeignType as _;
|
||||
CVMetalTextureCache::new(gpu.metal_device().as_ptr()).unwrap()
|
||||
CVMetalTextureCache::new(context.gpu.metal_device().as_ptr()).unwrap()
|
||||
};
|
||||
|
||||
Self {
|
||||
gpu,
|
||||
Ok(Self {
|
||||
gpu: Arc::clone(&context.gpu),
|
||||
surface,
|
||||
surface_config,
|
||||
alpha_mode: surface_info.alpha,
|
||||
command_encoder,
|
||||
last_sync_point: None,
|
||||
pipelines,
|
||||
@@ -418,7 +367,7 @@ impl BladeRenderer {
|
||||
atlas_sampler,
|
||||
#[cfg(target_os = "macos")]
|
||||
core_video_texture_cache,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn wait_for_gpu(&mut self) {
|
||||
@@ -452,7 +401,8 @@ impl BladeRenderer {
|
||||
if always_resize || gpu_size != self.surface_config.size {
|
||||
self.wait_for_gpu();
|
||||
self.surface_config.size = gpu_size;
|
||||
self.gpu.resize(self.surface_config);
|
||||
self.gpu
|
||||
.reconfigure_surface(&mut self.surface, self.surface_config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,10 +410,10 @@ impl BladeRenderer {
|
||||
if transparent != self.surface_config.transparent {
|
||||
self.wait_for_gpu();
|
||||
self.surface_config.transparent = transparent;
|
||||
let surface_info = self.gpu.resize(self.surface_config);
|
||||
self.gpu
|
||||
.reconfigure_surface(&mut self.surface, self.surface_config);
|
||||
self.pipelines.destroy(&self.gpu);
|
||||
self.pipelines = BladePipelines::new(&self.gpu, surface_info);
|
||||
self.alpha_mode = surface_info.alpha;
|
||||
self.pipelines = BladePipelines::new(&self.gpu, self.surface.info());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,10 +427,10 @@ impl BladeRenderer {
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
pub fn gpu_specs(&self) -> GPUSpecs {
|
||||
pub fn gpu_specs(&self) -> GpuSpecs {
|
||||
let info = self.gpu.device_information();
|
||||
|
||||
GPUSpecs {
|
||||
GpuSpecs {
|
||||
is_software_emulated: info.is_software_emulated,
|
||||
device_name: info.device_name.clone(),
|
||||
driver_name: info.driver_name.clone(),
|
||||
@@ -490,13 +440,13 @@ impl BladeRenderer {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn layer(&self) -> metal::MetalLayer {
|
||||
self.gpu.metal_layer().unwrap()
|
||||
self.surface.metal_layer()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
|
||||
use metal::foreign_types::ForeignType as _;
|
||||
self.gpu.metal_layer().unwrap().as_ptr()
|
||||
self.surface.metal_layer().as_ptr()
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
@@ -538,14 +488,17 @@ impl BladeRenderer {
|
||||
};
|
||||
|
||||
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
|
||||
let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
|
||||
colors: &[gpu::RenderTarget {
|
||||
view: tex_info.raw_view,
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}],
|
||||
depth_stencil: None,
|
||||
});
|
||||
let mut pass = self.command_encoder.render(
|
||||
"paths",
|
||||
gpu::RenderTargetSet {
|
||||
colors: &[gpu::RenderTarget {
|
||||
view: tex_info.raw_view,
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}],
|
||||
depth_stencil: None,
|
||||
},
|
||||
);
|
||||
|
||||
let mut encoder = pass.with(&self.pipelines.path_rasterization);
|
||||
encoder.bind(
|
||||
@@ -566,6 +519,7 @@ impl BladeRenderer {
|
||||
self.instance_belt.destroy(&self.gpu);
|
||||
self.gpu.destroy_command_encoder(&mut self.command_encoder);
|
||||
self.pipelines.destroy(&self.gpu);
|
||||
self.gpu.destroy_surface(&mut self.surface);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
@@ -575,7 +529,7 @@ impl BladeRenderer {
|
||||
|
||||
let frame = {
|
||||
profiling::scope!("acquire frame");
|
||||
self.gpu.acquire_frame()
|
||||
self.surface.acquire_frame()
|
||||
};
|
||||
self.command_encoder.init_texture(frame.texture());
|
||||
|
||||
@@ -584,21 +538,24 @@ impl BladeRenderer {
|
||||
self.surface_config.size.width as f32,
|
||||
self.surface_config.size.height as f32,
|
||||
],
|
||||
premultiplied_alpha: match self.alpha_mode {
|
||||
premultiplied_alpha: match self.surface.info().alpha {
|
||||
gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
|
||||
gpu::AlphaMode::PreMultiplied => 1,
|
||||
},
|
||||
pad: 0,
|
||||
};
|
||||
|
||||
if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
|
||||
colors: &[gpu::RenderTarget {
|
||||
view: frame.texture_view(),
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}],
|
||||
depth_stencil: None,
|
||||
}) {
|
||||
if let mut pass = self.command_encoder.render(
|
||||
"main",
|
||||
gpu::RenderTargetSet {
|
||||
colors: &[gpu::RenderTarget {
|
||||
view: frame.texture_view(),
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}],
|
||||
depth_stencil: None,
|
||||
},
|
||||
) {
|
||||
profiling::scope!("render pass");
|
||||
for batch in scene.batches() {
|
||||
match batch {
|
||||
|
||||
@@ -1,52 +1,45 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::any::{type_name, Any};
|
||||
use std::cell::{self, RefCell};
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
|
||||
use std::panic::{AssertUnwindSafe, Location};
|
||||
use std::rc::Weak;
|
||||
use std::{
|
||||
env,
|
||||
panic::AssertUnwindSafe,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::Read as _,
|
||||
os::fd::{AsFd, AsRawFd, FromRawFd},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use async_task::Runnable;
|
||||
use calloop::channel::Channel;
|
||||
use calloop::{EventLoop, LoopHandle, LoopSignal};
|
||||
use flume::{Receiver, Sender};
|
||||
use calloop::{channel::Channel, LoopSignal};
|
||||
use futures::{channel::oneshot, future::FutureExt};
|
||||
use parking_lot::Mutex;
|
||||
use util::ResultExt;
|
||||
|
||||
use util::ResultExt as _;
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
|
||||
use crate::platform::NoopTextSystem;
|
||||
use crate::{
|
||||
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu,
|
||||
PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem,
|
||||
PlatformWindow, Point, PromptLevel, Result, ScreenCaptureSource, SemanticVersion, SharedString,
|
||||
Size, Task, WindowAppearance, WindowOptions, WindowParams,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
|
||||
Pixels, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Point, Result,
|
||||
ScreenCaptureSource, Task, WindowAppearance, WindowParams,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) const SCROLL_LINES: f32 = 3.0;
|
||||
|
||||
// Values match the defaults on GTK.
|
||||
// Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
|
||||
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
|
||||
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
const FILE_PICKER_PORTAL_MISSING: &str =
|
||||
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
|
||||
|
||||
@@ -54,8 +47,9 @@ pub trait LinuxClient {
|
||||
fn compositor_name(&self) -> &'static str;
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
#[allow(unused)]
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
@@ -98,9 +92,9 @@ pub(crate) struct LinuxCommon {
|
||||
impl LinuxCommon {
|
||||
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
|
||||
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
let text_system = Arc::new(crate::CosmicTextSystem::new());
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
let text_system = Arc::new(crate::NoopTextSystem::new());
|
||||
|
||||
@@ -218,7 +212,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
}
|
||||
}
|
||||
|
||||
fn activate(&self, ignoring_other_apps: bool) {
|
||||
fn activate(&self, _ignoring_other_apps: bool) {
|
||||
log::info!("activate is not implemented on Linux, ignoring the call")
|
||||
}
|
||||
|
||||
@@ -281,7 +275,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
done_tx.send(Ok(None));
|
||||
let _ = (done_tx.send(Ok(None)), options);
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
self.foreground_executor()
|
||||
@@ -306,7 +300,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
|
||||
err => err.into(),
|
||||
};
|
||||
done_tx.send(Err(result));
|
||||
let _ = done_tx.send(Err(result));
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -322,7 +316,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
Err(ashpd::Error::Response(_)) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
};
|
||||
done_tx.send(result);
|
||||
let _ = done_tx.send(result);
|
||||
})
|
||||
.detach();
|
||||
done_rx
|
||||
@@ -332,7 +326,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
done_tx.send(Ok(None));
|
||||
let _ = (done_tx.send(Ok(None)), directory);
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
self.foreground_executor()
|
||||
@@ -356,7 +350,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
}
|
||||
err => err.into(),
|
||||
};
|
||||
done_tx.send(Err(result));
|
||||
let _ = done_tx.send(Err(result));
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -369,7 +363,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
Err(ashpd::Error::Response(_)) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
};
|
||||
done_tx.send(result);
|
||||
let _ = done_tx.send(result);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -426,7 +420,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
// get the path of the executable of the current process
|
||||
let exe_path = std::env::current_exe()?;
|
||||
let exe_path = env::current_exe()?;
|
||||
Ok(exe_path)
|
||||
}
|
||||
|
||||
@@ -440,9 +434,9 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
self.with_common(|common| Some(common.menus.clone()))
|
||||
}
|
||||
|
||||
fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap) {}
|
||||
fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {}
|
||||
|
||||
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||
fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
|
||||
Err(anyhow::Error::msg(
|
||||
"Platform<LinuxPlatform>::path_for_auxiliary_executable is not implemented yet",
|
||||
))
|
||||
@@ -614,6 +608,7 @@ pub(super) fn reveal_path_internal(
|
||||
.detach();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
|
||||
let diff = a - b;
|
||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||
@@ -622,7 +617,7 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
|
||||
let mut locales = Vec::default();
|
||||
if let Some(locale) = std::env::var_os("LC_CTYPE") {
|
||||
if let Some(locale) = env::var_os("LC_CTYPE") {
|
||||
locales.push(locale);
|
||||
}
|
||||
locales.push(OsString::from("C"));
|
||||
@@ -650,6 +645,7 @@ pub(super) unsafe fn read_fd(mut fd: filedescriptor::FileDescriptor) -> Result<V
|
||||
}
|
||||
|
||||
impl CursorStyle {
|
||||
#[allow(unused)]
|
||||
pub(super) fn to_icon_name(&self) -> String {
|
||||
// Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME)
|
||||
// and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
|
||||
@@ -682,10 +678,12 @@ impl CursorStyle {
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl Keystroke {
|
||||
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
||||
let mut modifiers = modifiers;
|
||||
|
||||
impl crate::Keystroke {
|
||||
pub(super) fn from_xkb(
|
||||
state: &State,
|
||||
mut modifiers: crate::Modifiers,
|
||||
keycode: Keycode,
|
||||
) -> Self {
|
||||
let key_utf32 = state.key_get_utf32(keycode);
|
||||
let key_utf8 = state.key_get_utf8(keycode);
|
||||
let key_sym = state.key_get_one_sym(keycode);
|
||||
@@ -759,7 +757,7 @@ impl Keystroke {
|
||||
let key_char =
|
||||
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
|
||||
|
||||
Keystroke {
|
||||
Self {
|
||||
modifiers,
|
||||
key,
|
||||
key_char,
|
||||
@@ -776,7 +774,6 @@ impl Keystroke {
|
||||
Keysym::dead_acute => Some("´".to_owned()),
|
||||
Keysym::dead_circumflex => Some("^".to_owned()),
|
||||
Keysym::dead_tilde => Some("~".to_owned()),
|
||||
Keysym::dead_perispomeni => Some("͂".to_owned()),
|
||||
Keysym::dead_macron => Some("¯".to_owned()),
|
||||
Keysym::dead_breve => Some("˘".to_owned()),
|
||||
Keysym::dead_abovedot => Some("˙".to_owned()),
|
||||
@@ -794,9 +791,7 @@ impl Keystroke {
|
||||
Keysym::dead_horn => Some("̛".to_owned()),
|
||||
Keysym::dead_stroke => Some("̶̶".to_owned()),
|
||||
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
|
||||
Keysym::dead_psili => Some("᾿".to_owned()),
|
||||
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
|
||||
Keysym::dead_dasia => Some("῾".to_owned()),
|
||||
Keysym::dead_doublegrave => Some("̏".to_owned()),
|
||||
Keysym::dead_belowring => Some("˳".to_owned()),
|
||||
Keysym::dead_belowmacron => Some("̱".to_owned()),
|
||||
@@ -830,7 +825,7 @@ impl Keystroke {
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl Modifiers {
|
||||
impl crate::Modifiers {
|
||||
pub(super) fn from_xkb(keymap_state: &State) -> Self {
|
||||
let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
|
||||
let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
|
||||
@@ -838,7 +833,7 @@ impl Modifiers {
|
||||
keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
|
||||
let platform =
|
||||
keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
|
||||
Modifiers {
|
||||
Self {
|
||||
shift,
|
||||
alt,
|
||||
control,
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::hash::Hash;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
hash::Hash,
|
||||
os::fd::{AsRawFd, BorrowedFd},
|
||||
path::PathBuf,
|
||||
rc::{Rc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use calloop::timer::{TimeoutAction, Timer};
|
||||
use calloop::{EventLoop, LoopHandle};
|
||||
use calloop::{
|
||||
timer::{TimeoutAction, Timer},
|
||||
EventLoop, LoopHandle,
|
||||
};
|
||||
use calloop_wayland_source::WaylandSource;
|
||||
use collections::HashMap;
|
||||
use filedescriptor::Pipe;
|
||||
@@ -64,30 +68,28 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
|
||||
|
||||
use super::display::WaylandDisplay;
|
||||
use super::window::{ImeInput, WaylandWindowStatePtr};
|
||||
use crate::platform::linux::wayland::clipboard::{
|
||||
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
|
||||
};
|
||||
use crate::platform::linux::wayland::cursor::Cursor;
|
||||
use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
|
||||
use crate::platform::linux::wayland::window::WaylandWindow;
|
||||
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
|
||||
use crate::platform::linux::LinuxClient;
|
||||
|
||||
use crate::platform::linux::{
|
||||
get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
|
||||
reveal_path_internal,
|
||||
wayland::{
|
||||
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE},
|
||||
cursor::Cursor,
|
||||
serial::{SerialKind, SerialTracker},
|
||||
window::WaylandWindow,
|
||||
},
|
||||
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
|
||||
LinuxClient,
|
||||
};
|
||||
use crate::platform::PlatformWindow;
|
||||
use crate::platform::{blade::BladeContext, PlatformWindow};
|
||||
use crate::{
|
||||
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
|
||||
DOUBLE_CLICK_INTERVAL, SCROLL_LINES,
|
||||
point, px, size, AnyWindowHandle, Bounds, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
|
||||
ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
|
||||
MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels,
|
||||
ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, DOUBLE_CLICK_INTERVAL,
|
||||
SCROLL_LINES,
|
||||
};
|
||||
use crate::{
|
||||
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta,
|
||||
ScrollWheelEvent, TouchPhase,
|
||||
};
|
||||
use crate::{LinuxCommon, WindowParams};
|
||||
|
||||
/// Used to convert evdev scancode to xkb scancode
|
||||
const MIN_KEYCODE: u32 = 8;
|
||||
@@ -186,6 +188,7 @@ pub struct Output {
|
||||
pub(crate) struct WaylandClientState {
|
||||
serial_tracker: SerialTracker,
|
||||
globals: Globals,
|
||||
gpu_context: BladeContext,
|
||||
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
|
||||
wl_pointer: Option<wl_pointer::WlPointer>,
|
||||
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
@@ -459,6 +462,8 @@ impl WaylandClient {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
|
||||
|
||||
let seat = seat.unwrap();
|
||||
let globals = Globals::new(
|
||||
globals,
|
||||
@@ -512,6 +517,7 @@ impl WaylandClient {
|
||||
let mut state = Rc::new(RefCell::new(WaylandClientState {
|
||||
serial_tracker: SerialTracker::new(),
|
||||
globals,
|
||||
gpu_context,
|
||||
wl_seat: seat,
|
||||
wl_pointer: None,
|
||||
wl_keyboard: None,
|
||||
@@ -627,6 +633,7 @@ impl LinuxClient for WaylandClient {
|
||||
let (window, surface_id) = WaylandWindow::new(
|
||||
handle,
|
||||
state.globals.clone(),
|
||||
&state.gpu_context,
|
||||
WaylandClientStatePtr(Rc::downgrade(&self.0)),
|
||||
params,
|
||||
state.common.appearance,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
ffi::c_void,
|
||||
ptr::NonNull,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use collections::HashMap;
|
||||
@@ -19,13 +21,14 @@ use wayland_protocols::xdg::shell::client::xdg_surface;
|
||||
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self};
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
|
||||
|
||||
use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
|
||||
use crate::platform::linux::wayland::display::WaylandDisplay;
|
||||
use crate::platform::linux::wayland::serial::SerialKind;
|
||||
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
|
||||
use crate::platform::{
|
||||
blade::{BladeContext, BladeRenderer, BladeSurfaceConfig},
|
||||
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
|
||||
PlatformAtlas, PlatformInputHandler, PlatformWindow,
|
||||
};
|
||||
use crate::scene::Scene;
|
||||
use crate::{
|
||||
px, size, AnyWindowHandle, Bounds, Decorations, GPUSpecs, Globals, Modifiers, Output, Pixels,
|
||||
px, size, AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
|
||||
PlatformDisplay, PlatformInput, Point, PromptLevel, RequestFrameOptions, ResizeEdge,
|
||||
ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
|
||||
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowParams,
|
||||
@@ -123,37 +126,28 @@ impl WaylandWindowState {
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
client: WaylandClientStatePtr,
|
||||
globals: Globals,
|
||||
gpu_context: &BladeContext,
|
||||
options: WindowParams,
|
||||
) -> anyhow::Result<Self> {
|
||||
let raw = RawWindow {
|
||||
window: surface.id().as_ptr().cast::<c_void>(),
|
||||
display: surface
|
||||
.backend()
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.display_ptr()
|
||||
.cast::<c_void>(),
|
||||
};
|
||||
let gpu = Arc::new(
|
||||
unsafe {
|
||||
gpu::Context::init_windowed(
|
||||
&raw,
|
||||
gpu::ContextDesc {
|
||||
validation: false,
|
||||
capture: false,
|
||||
overlay: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
|
||||
);
|
||||
let config = BladeSurfaceConfig {
|
||||
size: gpu::Extent {
|
||||
width: options.bounds.size.width.0 as u32,
|
||||
height: options.bounds.size.height.0 as u32,
|
||||
depth: 1,
|
||||
},
|
||||
transparent: true,
|
||||
let renderer = {
|
||||
let raw_window = RawWindow {
|
||||
window: surface.id().as_ptr().cast::<c_void>(),
|
||||
display: surface
|
||||
.backend()
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.display_ptr()
|
||||
.cast::<c_void>(),
|
||||
};
|
||||
let config = BladeSurfaceConfig {
|
||||
size: gpu::Extent {
|
||||
width: options.bounds.size.width.0 as u32,
|
||||
height: options.bounds.size.height.0 as u32,
|
||||
depth: 1,
|
||||
},
|
||||
transparent: true,
|
||||
};
|
||||
BladeRenderer::new(gpu_context, &raw_window, config)?
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -168,7 +162,7 @@ impl WaylandWindowState {
|
||||
globals,
|
||||
outputs: HashMap::default(),
|
||||
display: None,
|
||||
renderer: BladeRenderer::new(gpu, config),
|
||||
renderer,
|
||||
bounds: options.bounds,
|
||||
scale: 1.0,
|
||||
input_handler: None,
|
||||
@@ -266,6 +260,7 @@ impl WaylandWindow {
|
||||
pub fn new(
|
||||
handle: AnyWindowHandle,
|
||||
globals: Globals,
|
||||
gpu_context: &BladeContext,
|
||||
client: WaylandClientStatePtr,
|
||||
params: WindowParams,
|
||||
appearance: WindowAppearance,
|
||||
@@ -308,6 +303,7 @@ impl WaylandWindow {
|
||||
viewport,
|
||||
client,
|
||||
globals,
|
||||
gpu_context,
|
||||
params,
|
||||
)?)),
|
||||
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
||||
@@ -1019,7 +1015,7 @@ impl PlatformWindow for WaylandWindow {
|
||||
state.client.update_ime_position(bounds);
|
||||
}
|
||||
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
fn gpu_specs(&self) -> Option<GpuSpecs> {
|
||||
self.borrow().renderer.gpu_specs().into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use core::str;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{BTreeMap, HashSet},
|
||||
ops::Deref,
|
||||
path::PathBuf,
|
||||
rc::{Rc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use calloop::generic::{FdWrapper, Generic};
|
||||
use calloop::{EventLoop, LoopHandle, RegistrationToken};
|
||||
use calloop::{
|
||||
generic::{FdWrapper, Generic},
|
||||
EventLoop, LoopHandle, RegistrationToken,
|
||||
};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use collections::HashMap;
|
||||
@@ -15,44 +19,49 @@ use http_client::Url;
|
||||
use smallvec::SmallVec;
|
||||
use util::ResultExt;
|
||||
|
||||
use x11rb::connection::{Connection, RequestConnection};
|
||||
use x11rb::cursor;
|
||||
use x11rb::errors::ConnectionError;
|
||||
use x11rb::protocol::randr::ConnectionExt as _;
|
||||
use x11rb::protocol::xinput::ConnectionExt;
|
||||
use x11rb::protocol::xkb::ConnectionExt as _;
|
||||
use x11rb::protocol::xproto::{
|
||||
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _,
|
||||
EventMask, KeyPressEvent,
|
||||
use x11rb::{
|
||||
connection::{Connection, RequestConnection},
|
||||
cursor,
|
||||
errors::ConnectionError,
|
||||
protocol::randr::ConnectionExt as _,
|
||||
protocol::xinput::ConnectionExt,
|
||||
protocol::xkb::ConnectionExt as _,
|
||||
protocol::xproto::{
|
||||
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent,
|
||||
ConnectionExt as _, EventMask, KeyPressEvent,
|
||||
},
|
||||
protocol::{randr, render, xinput, xkb, xproto, Event},
|
||||
resource_manager::Database,
|
||||
wrapper::ConnectionExt as _,
|
||||
xcb_ffi::XCBConnection,
|
||||
};
|
||||
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
|
||||
use x11rb::resource_manager::Database;
|
||||
use x11rb::wrapper::ConnectionExt as _;
|
||||
use x11rb::xcb_ffi::XCBConnection;
|
||||
use xim::{x11rb::X11rbClient, Client};
|
||||
use xim::{AttributeName, InputStyle};
|
||||
use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle};
|
||||
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
|
||||
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
|
||||
|
||||
use crate::platform::linux::LinuxClient;
|
||||
use crate::platform::{LinuxCommon, PlatformWindow};
|
||||
use crate::{
|
||||
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
|
||||
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
|
||||
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
};
|
||||
|
||||
use super::{
|
||||
button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state,
|
||||
pressed_button_from_mask, ButtonOrScroll, ScrollDirection,
|
||||
};
|
||||
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
|
||||
use super::{XimCallbackEvent, XimHandler};
|
||||
use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};
|
||||
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
|
||||
use crate::platform::linux::{
|
||||
get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
|
||||
|
||||
use crate::platform::{
|
||||
blade::BladeContext,
|
||||
linux::{
|
||||
get_xkb_compose_state, is_within_click_distance, open_uri_internal,
|
||||
platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
|
||||
reveal_path_internal,
|
||||
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
|
||||
LinuxClient,
|
||||
},
|
||||
LinuxCommon, PlatformWindow,
|
||||
};
|
||||
use crate::{
|
||||
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
|
||||
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
|
||||
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
};
|
||||
|
||||
/// Value for DeviceId parameters which selects all devices.
|
||||
@@ -158,6 +167,8 @@ pub struct X11ClientState {
|
||||
pub(crate) last_location: Point<Pixels>,
|
||||
pub(crate) current_count: usize,
|
||||
|
||||
gpu_context: BladeContext,
|
||||
|
||||
pub(crate) scale_factor: f32,
|
||||
|
||||
xkb_context: xkbc::Context,
|
||||
@@ -360,6 +371,8 @@ impl X11Client {
|
||||
let compose_state = get_xkb_compose_state(&xkb_context);
|
||||
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
|
||||
|
||||
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
|
||||
|
||||
let scale_factor = resource_database
|
||||
.get_value("Xft.dpi", "Xft.dpi")
|
||||
.ok()
|
||||
@@ -428,6 +441,7 @@ impl X11Client {
|
||||
last_mouse_button: None,
|
||||
last_location: Point::new(px(0.0), px(0.0)),
|
||||
current_count: 0,
|
||||
gpu_context,
|
||||
scale_factor,
|
||||
|
||||
xkb_context,
|
||||
@@ -1299,6 +1313,7 @@ impl LinuxClient for X11Client {
|
||||
handle,
|
||||
X11ClientStatePtr(Rc::downgrade(&self.0)),
|
||||
state.common.foreground_executor.clone(),
|
||||
&state.gpu_context,
|
||||
params,
|
||||
&state.xcb_connection,
|
||||
state.client_side_decorations_supported,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
|
||||
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
|
||||
use crate::{
|
||||
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
||||
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
|
||||
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs,
|
||||
Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
||||
PlatformWindow, Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
|
||||
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
||||
@@ -247,7 +247,6 @@ pub struct X11WindowState {
|
||||
x_root_window: xproto::Window,
|
||||
pub(crate) counter_id: sync::Counter,
|
||||
pub(crate) last_sync_counter: Option<sync::Int64>,
|
||||
_raw: RawWindow,
|
||||
bounds: Bounds<Pixels>,
|
||||
scale_factor: f32,
|
||||
renderer: BladeRenderer,
|
||||
@@ -358,6 +357,7 @@ impl X11WindowState {
|
||||
handle: AnyWindowHandle,
|
||||
client: X11ClientStatePtr,
|
||||
executor: ForegroundExecutor,
|
||||
gpu_context: &BladeContext,
|
||||
params: WindowParams,
|
||||
xcb: &Rc<XCBConnection>,
|
||||
client_side_decorations_supported: bool,
|
||||
@@ -555,50 +555,39 @@ impl X11WindowState {
|
||||
|
||||
xcb.flush().with_context(|| "X11 Flush failed.")?;
|
||||
|
||||
let raw = RawWindow {
|
||||
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(xcb)
|
||||
as *mut _,
|
||||
screen_id: x_screen_index,
|
||||
window_id: x_window,
|
||||
visual_id: visual.id,
|
||||
let renderer = {
|
||||
let raw_window = RawWindow {
|
||||
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
|
||||
xcb,
|
||||
) as *mut _,
|
||||
screen_id: x_screen_index,
|
||||
window_id: x_window,
|
||||
visual_id: visual.id,
|
||||
};
|
||||
let config = BladeSurfaceConfig {
|
||||
// Note: this has to be done after the GPU init, or otherwise
|
||||
// the sizes are immediately invalidated.
|
||||
size: query_render_extent(xcb, x_window)?,
|
||||
// We set it to transparent by default, even if we have client-side
|
||||
// decorations, since those seem to work on X11 even without `true` here.
|
||||
// If the window appearance changes, then the renderer will get updated
|
||||
// too
|
||||
transparent: false,
|
||||
};
|
||||
BladeRenderer::new(gpu_context, &raw_window, config)?
|
||||
};
|
||||
let gpu = Arc::new(
|
||||
unsafe {
|
||||
gpu::Context::init_windowed(
|
||||
&raw,
|
||||
gpu::ContextDesc {
|
||||
validation: false,
|
||||
capture: false,
|
||||
overlay: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
.map_err(|e| anyhow!("{:?}", e))?,
|
||||
);
|
||||
|
||||
let config = BladeSurfaceConfig {
|
||||
// Note: this has to be done after the GPU init, or otherwise
|
||||
// the sizes are immediately invalidated.
|
||||
size: query_render_extent(xcb, x_window)?,
|
||||
// We set it to transparent by default, even if we have client-side
|
||||
// decorations, since those seem to work on X11 even without `true` here.
|
||||
// If the window appearance changes, then the renderer will get updated
|
||||
// too
|
||||
transparent: false,
|
||||
};
|
||||
check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
|
||||
|
||||
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
executor,
|
||||
display,
|
||||
_raw: raw,
|
||||
x_root_window: visual_set.root,
|
||||
bounds: bounds.to_pixels(scale_factor),
|
||||
scale_factor,
|
||||
renderer: BladeRenderer::new(gpu, config),
|
||||
renderer,
|
||||
atoms: *atoms,
|
||||
input_handler: None,
|
||||
active: false,
|
||||
@@ -716,6 +705,7 @@ impl X11Window {
|
||||
handle: AnyWindowHandle,
|
||||
client: X11ClientStatePtr,
|
||||
executor: ForegroundExecutor,
|
||||
gpu_context: &BladeContext,
|
||||
params: WindowParams,
|
||||
xcb: &Rc<XCBConnection>,
|
||||
client_side_decorations_supported: bool,
|
||||
@@ -730,6 +720,7 @@ impl X11Window {
|
||||
handle,
|
||||
client,
|
||||
executor,
|
||||
gpu_context,
|
||||
params,
|
||||
xcb,
|
||||
client_side_decorations_supported,
|
||||
@@ -1215,7 +1206,7 @@ impl PlatformWindow for X11Window {
|
||||
title.as_bytes(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
.log_err();
|
||||
|
||||
check_reply(
|
||||
|| "X11 ChangeProperty8 on _NET_WM_NAME failed.",
|
||||
@@ -1227,8 +1218,8 @@ impl PlatformWindow for X11Window {
|
||||
title.as_bytes(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
self.flush().unwrap();
|
||||
.log_err();
|
||||
self.flush().log_err();
|
||||
}
|
||||
|
||||
fn set_app_id(&mut self, app_id: &str) {
|
||||
@@ -1536,7 +1527,7 @@ impl PlatformWindow for X11Window {
|
||||
client.update_ime_position(bounds);
|
||||
}
|
||||
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
fn gpu_specs(&self) -> Option<GpuSpecs> {
|
||||
self.0.state.borrow().renderer.gpu_specs().into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1102,7 +1102,7 @@ impl PlatformWindow for MacWindow {
|
||||
self.0.lock().renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
fn gpu_specs(&self) -> Option<crate::GPUSpecs> {
|
||||
fn gpu_specs(&self) -> Option<crate::GpuSpecs> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GPUSpecs,
|
||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||
Point, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId, WindowAppearance,
|
||||
WindowBackgroundAppearance, WindowBounds, WindowParams,
|
||||
@@ -276,7 +276,7 @@ impl PlatformWindow for TestWindow {
|
||||
|
||||
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {}
|
||||
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
fn gpu_specs(&self) -> Option<GpuSpecs> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,12 @@ use windows::{
|
||||
UI::ViewManagement::UISettings,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
use crate::{platform::blade::BladeContext, *};
|
||||
|
||||
pub(crate) struct WindowsPlatform {
|
||||
state: RefCell<WindowsPlatformState>,
|
||||
raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
|
||||
gpu_context: BladeContext,
|
||||
// The below members will never change throughout the entire lifecycle of the app.
|
||||
icon: HICON,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
@@ -94,12 +95,14 @@ impl WindowsPlatform {
|
||||
let icon = load_icon().unwrap_or_default();
|
||||
let state = RefCell::new(WindowsPlatformState::new());
|
||||
let raw_window_handles = RwLock::new(SmallVec::new());
|
||||
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
|
||||
let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
|
||||
let validation_number = rand::random::<usize>();
|
||||
|
||||
Self {
|
||||
state,
|
||||
raw_window_handles,
|
||||
gpu_context,
|
||||
icon,
|
||||
main_receiver,
|
||||
dispatch_event,
|
||||
@@ -344,7 +347,12 @@ impl Platform for WindowsPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> Result<Box<dyn PlatformWindow>> {
|
||||
let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
|
||||
let window = WindowsWindow::new(
|
||||
handle,
|
||||
options,
|
||||
self.generate_creation_info(),
|
||||
&self.gpu_context,
|
||||
)?;
|
||||
let handle = window.get_raw_handle();
|
||||
self.raw_window_handles.write().push(handle);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::platform::blade::BladeRenderer;
|
||||
use crate::platform::blade::{BladeContext, BladeRenderer};
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
|
||||
@@ -78,6 +78,7 @@ impl WindowsWindowState {
|
||||
cs: &CREATESTRUCTW,
|
||||
current_cursor: HCURSOR,
|
||||
display: WindowsDisplay,
|
||||
gpu_context: &BladeContext,
|
||||
) -> Result<Self> {
|
||||
let scale_factor = {
|
||||
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
|
||||
@@ -94,7 +95,7 @@ impl WindowsWindowState {
|
||||
};
|
||||
let border_offset = WindowBorderOffset::default();
|
||||
let is_minimized = None;
|
||||
let renderer = windows_renderer::windows_renderer(hwnd, transparent)?;
|
||||
let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
|
||||
let callbacks = Callbacks::default();
|
||||
let input_handler = None;
|
||||
let system_key_handled = false;
|
||||
@@ -227,6 +228,7 @@ impl WindowsWindowStatePtr {
|
||||
cs,
|
||||
context.current_cursor,
|
||||
context.display,
|
||||
context.gpu_context,
|
||||
)?);
|
||||
|
||||
Ok(Rc::new_cyclic(|this| Self {
|
||||
@@ -340,7 +342,7 @@ pub(crate) struct Callbacks {
|
||||
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
struct WindowCreateContext {
|
||||
struct WindowCreateContext<'a> {
|
||||
inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
|
||||
handle: AnyWindowHandle,
|
||||
hide_title_bar: bool,
|
||||
@@ -352,6 +354,7 @@ struct WindowCreateContext {
|
||||
windows_version: WindowsVersion,
|
||||
validation_number: usize,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
gpu_context: &'a BladeContext,
|
||||
}
|
||||
|
||||
impl WindowsWindow {
|
||||
@@ -359,6 +362,7 @@ impl WindowsWindow {
|
||||
handle: AnyWindowHandle,
|
||||
params: WindowParams,
|
||||
creation_info: WindowCreationInfo,
|
||||
gpu_context: &BladeContext,
|
||||
) -> Result<Self> {
|
||||
let WindowCreationInfo {
|
||||
icon,
|
||||
@@ -410,6 +414,7 @@ impl WindowsWindow {
|
||||
windows_version,
|
||||
validation_number,
|
||||
main_receiver,
|
||||
gpu_context,
|
||||
};
|
||||
let lpparam = Some(&context as *const _ as *const _);
|
||||
let creation_result = unsafe {
|
||||
@@ -770,7 +775,7 @@ impl PlatformWindow for WindowsWindow {
|
||||
self.0.hwnd
|
||||
}
|
||||
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
fn gpu_specs(&self) -> Option<GpuSpecs> {
|
||||
Some(self.0.state.borrow().renderer.gpu_specs())
|
||||
}
|
||||
|
||||
@@ -1236,38 +1241,24 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
|
||||
}
|
||||
|
||||
mod windows_renderer {
|
||||
use std::{num::NonZeroIsize, sync::Arc};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
|
||||
use raw_window_handle as rwh;
|
||||
use std::num::NonZeroIsize;
|
||||
use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
|
||||
|
||||
use crate::{
|
||||
get_window_long,
|
||||
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
||||
};
|
||||
use crate::get_window_long;
|
||||
|
||||
pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> anyhow::Result<BladeRenderer> {
|
||||
pub(super) fn init(
|
||||
context: &BladeContext,
|
||||
hwnd: HWND,
|
||||
transparent: bool,
|
||||
) -> anyhow::Result<BladeRenderer> {
|
||||
let raw = RawWindow { hwnd };
|
||||
let gpu: Arc<gpu::Context> = Arc::new(
|
||||
unsafe {
|
||||
gpu::Context::init_windowed(
|
||||
&raw,
|
||||
gpu::ContextDesc {
|
||||
validation: false,
|
||||
capture: false,
|
||||
overlay: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
|
||||
);
|
||||
let config = BladeSurfaceConfig {
|
||||
size: gpu::Extent::default(),
|
||||
size: Default::default(),
|
||||
transparent,
|
||||
};
|
||||
|
||||
Ok(BladeRenderer::new(gpu, config))
|
||||
BladeRenderer::new(context, &raw, config)
|
||||
}
|
||||
|
||||
struct RawWindow {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
|
||||
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
|
||||
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
||||
FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler,
|
||||
FileDropEvent, Flatten, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler,
|
||||
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent,
|
||||
KeystrokeObserver, LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers,
|
||||
ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent,
|
||||
@@ -3808,7 +3808,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Read information about the GPU backing this window.
|
||||
/// Currently returns None on Mac and Windows.
|
||||
pub fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
pub fn gpu_specs(&self) -> Option<GpuSpecs> {
|
||||
self.window.platform_window.gpu_specs()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
use std::sync::OnceLock;
|
||||
use std::{cell::RefCell, sync::LazyLock};
|
||||
|
||||
use anyhow::Result;
|
||||
use markup5ever_rcdom::{Handle, NodeData};
|
||||
@@ -10,13 +9,14 @@ use regex::Regex;
|
||||
use crate::html_element::HtmlElement;
|
||||
|
||||
fn empty_line_regex() -> &'static Regex {
|
||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
REGEX.get_or_init(|| Regex::new(r"^\s*$").unwrap())
|
||||
static REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\s*$").expect("Failed to create empty_line_regex"));
|
||||
®EX
|
||||
}
|
||||
|
||||
fn more_than_three_newlines_regex() -> &'static Regex {
|
||||
static REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
REGEX.get_or_init(|| Regex::new(r"\n{3,}").unwrap())
|
||||
static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
|
||||
®EX
|
||||
}
|
||||
|
||||
pub enum StartTagOutcome {
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct InlineCompletion {
|
||||
pub trait InlineCompletionProvider: 'static + Sized {
|
||||
fn name() -> &'static str;
|
||||
fn display_name() -> &'static str;
|
||||
fn show_completions_in_menu() -> bool;
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@@ -59,6 +60,7 @@ pub trait InlineCompletionProviderHandle {
|
||||
cursor_position: language::Anchor,
|
||||
cx: &AppContext,
|
||||
) -> bool;
|
||||
fn show_completions_in_menu(&self) -> bool;
|
||||
fn refresh(
|
||||
&self,
|
||||
buffer: Model<Buffer>,
|
||||
@@ -95,6 +97,10 @@ where
|
||||
T::display_name()
|
||||
}
|
||||
|
||||
fn show_completions_in_menu(&self) -> bool {
|
||||
T::show_completions_in_menu()
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
@@ -19,7 +19,7 @@ anyhow.workspace = true
|
||||
core-foundation.workspace = true
|
||||
ctor.workspace = true
|
||||
foreign-types = "0.5"
|
||||
metal = "0.29"
|
||||
metal.workspace = true
|
||||
objc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -27,13 +27,11 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
rand.workspace = true
|
||||
settings.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1990
crates/multi_buffer/src/multi_buffer_tests.rs
Normal file
1990
crates/multi_buffer/src/multi_buffer_tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,317 +0,0 @@
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
iter::zip,
|
||||
marker::PhantomData,
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
};
|
||||
use text::{Point, PointUtf16};
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct TypedOffset<T> {
|
||||
pub value: usize,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct TypedPoint<T> {
|
||||
pub value: Point,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct TypedPointUtf16<T> {
|
||||
pub value: PointUtf16,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct TypedRow<T> {
|
||||
pub value: u32,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TypedOffset<T> {
|
||||
pub fn new(offset: usize) -> Self {
|
||||
Self {
|
||||
value: offset,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn saturating_sub(self, n: TypedOffset<T>) -> Self {
|
||||
Self {
|
||||
value: self.value.saturating_sub(n.value),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new(0)
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.value == 0
|
||||
}
|
||||
}
|
||||
impl<T> TypedPoint<T> {
|
||||
pub fn new(row: u32, column: u32) -> Self {
|
||||
Self {
|
||||
value: Point::new(row, column),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn wrap(point: Point) -> Self {
|
||||
Self {
|
||||
value: point,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn row(&self) -> u32 {
|
||||
self.value.row
|
||||
}
|
||||
pub fn column(&self) -> u32 {
|
||||
self.value.column
|
||||
}
|
||||
pub fn zero() -> Self {
|
||||
Self::wrap(Point::zero())
|
||||
}
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.value.is_zero()
|
||||
}
|
||||
}
|
||||
impl<T> TypedPointUtf16<T> {
|
||||
pub fn new(row: u32, column: u32) -> Self {
|
||||
TypedPointUtf16 {
|
||||
value: PointUtf16::new(row, column),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn wrap(point: PointUtf16) -> Self {
|
||||
Self {
|
||||
value: point,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> TypedRow<T> {
|
||||
pub fn new(row: u32) -> Self {
|
||||
Self {
|
||||
value: row,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for TypedOffset<T> {}
|
||||
impl<T> Copy for TypedPoint<T> {}
|
||||
impl<T> Copy for TypedPointUtf16<T> {}
|
||||
impl<T> Copy for TypedRow<T> {}
|
||||
|
||||
impl<T> Clone for TypedOffset<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
value: self.value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TypedPoint<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
value: self.value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TypedPointUtf16<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
value: self.value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Clone for TypedRow<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
value: self.value,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for TypedOffset<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(0)
|
||||
}
|
||||
}
|
||||
impl<T> Default for TypedPoint<T> {
|
||||
fn default() -> Self {
|
||||
Self::wrap(Point::default())
|
||||
}
|
||||
}
|
||||
impl<T> Default for TypedPointUtf16<T> {
|
||||
fn default() -> Self {
|
||||
Self::wrap(PointUtf16::default())
|
||||
}
|
||||
}
|
||||
impl<T> Default for TypedRow<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialOrd for TypedOffset<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.value.cmp(&other.value))
|
||||
}
|
||||
}
|
||||
impl<T> PartialOrd for TypedPoint<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.value.cmp(&other.value))
|
||||
}
|
||||
}
|
||||
impl<T> PartialOrd for TypedPointUtf16<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.value.cmp(&other.value))
|
||||
}
|
||||
}
|
||||
impl<T> PartialOrd for TypedRow<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.value.cmp(&other.value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Ord for TypedOffset<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.value.cmp(&other.value)
|
||||
}
|
||||
}
|
||||
impl<T> Ord for TypedPoint<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.value.cmp(&other.value)
|
||||
}
|
||||
}
|
||||
impl<T> Ord for TypedPointUtf16<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.value.cmp(&other.value)
|
||||
}
|
||||
}
|
||||
impl<T> Ord for TypedRow<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.value.cmp(&other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for TypedOffset<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for TypedPoint<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for TypedPointUtf16<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
impl<T> PartialEq for TypedRow<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for TypedOffset<T> {}
|
||||
impl<T> Eq for TypedPoint<T> {}
|
||||
impl<T> Eq for TypedPointUtf16<T> {}
|
||||
impl<T> Eq for TypedRow<T> {}
|
||||
|
||||
impl<T> Debug for TypedOffset<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}Offset({})", type_name::<T>(), self.value)
|
||||
}
|
||||
}
|
||||
impl<T> Debug for TypedPoint<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}Point({}, {})",
|
||||
type_name::<T>(),
|
||||
self.value.row,
|
||||
self.value.column
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> Debug for TypedRow<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}Row({})", type_name::<T>(), self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Display for TypedOffset<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.value, f)
|
||||
}
|
||||
}
|
||||
impl<T> Display for TypedRow<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.value, f)
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name<T>() -> &'static str {
|
||||
std::any::type_name::<T>().split("::").last().unwrap()
|
||||
}
|
||||
|
||||
impl<T> Add<TypedOffset<T>> for TypedOffset<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
TypedOffset::new(self.value + other.value)
|
||||
}
|
||||
}
|
||||
impl<T> Add<TypedPoint<T>> for TypedPoint<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
TypedPoint::wrap(self.value + other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sub<TypedOffset<T>> for TypedOffset<T> {
|
||||
type Output = Self;
|
||||
fn sub(self, other: Self) -> Self {
|
||||
TypedOffset::new(self.value - other.value)
|
||||
}
|
||||
}
|
||||
impl<T> Sub<TypedPoint<T>> for TypedPoint<T> {
|
||||
type Output = Self;
|
||||
fn sub(self, other: Self) -> Self {
|
||||
TypedPoint::wrap(self.value - other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AddAssign<TypedOffset<T>> for TypedOffset<T> {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.value += other.value;
|
||||
}
|
||||
}
|
||||
impl<T> AddAssign<TypedPoint<T>> for TypedPoint<T> {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.value += other.value;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SubAssign<Self> for TypedOffset<T> {
|
||||
fn sub_assign(&mut self, other: Self) {
|
||||
self.value -= other.value;
|
||||
}
|
||||
}
|
||||
impl<T> SubAssign<Self> for TypedRow<T> {
|
||||
fn sub_assign(&mut self, other: Self) {
|
||||
self.value -= other.value;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ snippet.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
terminal.workspace = true
|
||||
text.workspace = true
|
||||
toml.workspace = true
|
||||
util.workspace = true
|
||||
url.workspace = true
|
||||
which.workspace = true
|
||||
|
||||
@@ -5,12 +5,12 @@ use crate::{
|
||||
ProjectItem as _, ProjectPath,
|
||||
};
|
||||
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use client::Client;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt};
|
||||
use git::{blame::Blame, diff::BufferDiff};
|
||||
use git::{blame::Blame, diff::BufferDiff, repository::RepoPath};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Subscription,
|
||||
Task, WeakModel,
|
||||
@@ -24,8 +24,16 @@ use language::{
|
||||
Buffer, BufferEvent, Capability, DiskState, File as _, Language, Operation,
|
||||
};
|
||||
use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};
|
||||
use serde::Deserialize;
|
||||
use smol::channel::Receiver;
|
||||
use std::{io, ops::Range, path::Path, str::FromStr as _, sync::Arc, time::Instant};
|
||||
use std::{
|
||||
io,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr as _,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use text::{BufferId, LineEnding, Rope};
|
||||
use util::{debug_panic, maybe, ResultExt as _, TryFutureExt};
|
||||
use worktree::{File, PathChange, ProjectEntryId, UpdatedGitRepositoriesSet, Worktree, WorktreeId};
|
||||
@@ -1213,11 +1221,36 @@ impl BufferStore {
|
||||
|
||||
match file.worktree.read(cx) {
|
||||
Worktree::Local(worktree) => {
|
||||
let Some(repo) = worktree.local_git_repo(file.path()) else {
|
||||
return Task::ready(Err(anyhow!("no repository for buffer found")));
|
||||
let worktree_path = worktree.abs_path().clone();
|
||||
let Some((repo_entry, repo)) =
|
||||
worktree.repository_for_path(file.path()).and_then(|entry| {
|
||||
let repo = worktree.get_local_repo(&entry)?.repo().clone();
|
||||
Some((entry, repo))
|
||||
})
|
||||
else {
|
||||
// If we're not in a Git repo, check whether this is a Rust source
|
||||
// file in the Cargo registry (presumably opened with go-to-definition
|
||||
// from a normal Rust file). If so, we can put together a permalink
|
||||
// using crate metadata.
|
||||
if !buffer
|
||||
.language()
|
||||
.is_some_and(|lang| lang.name() == "Rust".into())
|
||||
{
|
||||
return Task::ready(Err(anyhow!("no permalink available")));
|
||||
}
|
||||
let file_path = worktree_path.join(file.path());
|
||||
return cx.spawn(|cx| async move {
|
||||
let provider_registry =
|
||||
cx.update(GitHostingProviderRegistry::default_global)?;
|
||||
get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
|
||||
.map_err(|_| anyhow!("no permalink available"))
|
||||
});
|
||||
};
|
||||
|
||||
let path = file.path().clone();
|
||||
let path = match repo_entry.relativize(worktree, file.path()) {
|
||||
Ok(RepoPath(path)) => path,
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
const REMOTE_NAME: &str = "origin";
|
||||
@@ -1238,7 +1271,7 @@ impl BufferStore {
|
||||
|
||||
let path = path
|
||||
.to_str()
|
||||
.context("failed to convert buffer path to string")?;
|
||||
.ok_or_else(|| anyhow!("failed to convert path to string"))?;
|
||||
|
||||
Ok(provider.build_permalink(
|
||||
remote,
|
||||
@@ -2432,3 +2465,52 @@ fn deserialize_blame_buffer_response(
|
||||
remote_url: response.remote_url,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_permalink_in_rust_registry_src(
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
path: PathBuf,
|
||||
selection: Range<u32>,
|
||||
) -> Result<url::Url> {
|
||||
#[derive(Deserialize)]
|
||||
struct CargoVcsGit {
|
||||
sha1: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoVcsInfo {
|
||||
git: CargoVcsGit,
|
||||
path_in_vcs: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoPackage {
|
||||
repository: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoToml {
|
||||
package: CargoPackage,
|
||||
}
|
||||
|
||||
let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
|
||||
let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
|
||||
Some((dir, json))
|
||||
}) else {
|
||||
bail!("No .cargo_vcs_info.json found in parent directories")
|
||||
};
|
||||
let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
|
||||
let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
|
||||
let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
|
||||
let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
|
||||
.ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
|
||||
let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
|
||||
let permalink = provider.build_permalink(
|
||||
remote,
|
||||
BuildPermalinkParams {
|
||||
sha: &cargo_vcs_info.git.sha1,
|
||||
path: &path.to_string_lossy(),
|
||||
selection: Some(selection),
|
||||
},
|
||||
);
|
||||
Ok(permalink)
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ use crate::{
|
||||
|
||||
pub struct ProjectEnvironment {
|
||||
cli_environment: Option<HashMap<String, String>>,
|
||||
get_environment_task: Option<Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
cached_shell_environments: HashMap<WorktreeId, HashMap<String, String>>,
|
||||
environments: HashMap<WorktreeId, Shared<Task<Option<HashMap<String, String>>>>>,
|
||||
environment_error_messages: HashMap<WorktreeId, EnvironmentErrorMessage>,
|
||||
}
|
||||
|
||||
@@ -35,27 +34,15 @@ impl ProjectEnvironment {
|
||||
|
||||
Self {
|
||||
cli_environment,
|
||||
get_environment_task: None,
|
||||
cached_shell_environments: Default::default(),
|
||||
environments: Default::default(),
|
||||
environment_error_messages: Default::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn set_cached(
|
||||
&mut self,
|
||||
shell_environments: &[(WorktreeId, HashMap<String, String>)],
|
||||
) {
|
||||
self.cached_shell_environments = shell_environments
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<_, _>>();
|
||||
}
|
||||
|
||||
pub(crate) fn remove_worktree_environment(&mut self, worktree_id: WorktreeId) {
|
||||
self.cached_shell_environments.remove(&worktree_id);
|
||||
self.environment_error_messages.remove(&worktree_id);
|
||||
self.environments.remove(&worktree_id);
|
||||
}
|
||||
|
||||
/// Returns the inherited CLI environment, if this project was opened from the Zed CLI.
|
||||
@@ -91,96 +78,83 @@ impl ProjectEnvironment {
|
||||
worktree_abs_path: Option<Arc<Path>>,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
if let Some(task) = self.get_environment_task.as_ref() {
|
||||
if cfg!(any(test, feature = "test-support")) {
|
||||
return Task::ready(Some(HashMap::default())).shared();
|
||||
}
|
||||
|
||||
if let Some(cli_environment) = self.get_cli_environment() {
|
||||
return cx
|
||||
.spawn(|_, _| async move {
|
||||
let path = cli_environment
|
||||
.get("PATH")
|
||||
.map(|path| path.as_str())
|
||||
.unwrap_or_default();
|
||||
log::info!(
|
||||
"using project environment variables from CLI. PATH={:?}",
|
||||
path
|
||||
);
|
||||
Some(cli_environment)
|
||||
})
|
||||
.shared();
|
||||
}
|
||||
|
||||
let Some((worktree_id, worktree_abs_path)) = worktree_id.zip(worktree_abs_path) else {
|
||||
return Task::ready(None).shared();
|
||||
};
|
||||
|
||||
if let Some(task) = self.environments.get(&worktree_id) {
|
||||
task.clone()
|
||||
} else {
|
||||
let task = self
|
||||
.build_environment_task(worktree_id, worktree_abs_path, cx)
|
||||
.get_worktree_env(worktree_id, worktree_abs_path, cx)
|
||||
.shared();
|
||||
|
||||
self.get_environment_task = Some(task.clone());
|
||||
self.environments.insert(worktree_id, task.clone());
|
||||
task
|
||||
}
|
||||
}
|
||||
|
||||
fn build_environment_task(
|
||||
&mut self,
|
||||
worktree_id: Option<WorktreeId>,
|
||||
worktree_abs_path: Option<Arc<Path>>,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Option<HashMap<String, String>>> {
|
||||
let worktree = worktree_id.zip(worktree_abs_path);
|
||||
|
||||
let cli_environment = self.get_cli_environment();
|
||||
if let Some(environment) = cli_environment {
|
||||
cx.spawn(|_, _| async move {
|
||||
let path = environment
|
||||
.get("PATH")
|
||||
.map(|path| path.as_str())
|
||||
.unwrap_or_default();
|
||||
log::info!(
|
||||
"using project environment variables from CLI. PATH={:?}",
|
||||
path
|
||||
);
|
||||
Some(environment)
|
||||
})
|
||||
} else if let Some((worktree_id, worktree_abs_path)) = worktree {
|
||||
self.get_worktree_env(worktree_id, worktree_abs_path, cx)
|
||||
} else {
|
||||
Task::ready(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_worktree_env(
|
||||
&mut self,
|
||||
worktree_id: WorktreeId,
|
||||
worktree_abs_path: Arc<Path>,
|
||||
cx: &ModelContext<Self>,
|
||||
) -> Task<Option<HashMap<String, String>>> {
|
||||
let cached_env = self.cached_shell_environments.get(&worktree_id).cloned();
|
||||
if let Some(env) = cached_env {
|
||||
Task::ready(Some(env))
|
||||
} else {
|
||||
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
|
||||
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (mut shell_env, error_message) = cx
|
||||
.background_executor()
|
||||
.spawn({
|
||||
let cwd = worktree_abs_path.clone();
|
||||
async move { load_shell_environment(&cwd, &load_direnv).await }
|
||||
})
|
||||
.await;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (mut shell_env, error_message) = cx
|
||||
.background_executor()
|
||||
.spawn({
|
||||
let worktree_abs_path = worktree_abs_path.clone();
|
||||
async move {
|
||||
load_worktree_shell_environment(&worktree_abs_path, &load_direnv).await
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(shell_env) = shell_env.as_mut() {
|
||||
let path = shell_env
|
||||
.get("PATH")
|
||||
.map(|path| path.as_str())
|
||||
.unwrap_or_default();
|
||||
log::info!(
|
||||
"using project environment variables shell launched in {:?}. PATH={:?}",
|
||||
worktree_abs_path,
|
||||
path
|
||||
);
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.cached_shell_environments
|
||||
.insert(worktree_id, shell_env.clone());
|
||||
})
|
||||
.log_err();
|
||||
if let Some(shell_env) = shell_env.as_mut() {
|
||||
let path = shell_env
|
||||
.get("PATH")
|
||||
.map(|path| path.as_str())
|
||||
.unwrap_or_default();
|
||||
log::info!(
|
||||
"using project environment variables shell launched in {:?}. PATH={:?}",
|
||||
worktree_abs_path,
|
||||
path
|
||||
);
|
||||
|
||||
set_origin_marker(shell_env, EnvironmentOrigin::WorktreeShell);
|
||||
}
|
||||
set_origin_marker(shell_env, EnvironmentOrigin::WorktreeShell);
|
||||
}
|
||||
|
||||
if let Some(error) = error_message {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.environment_error_messages.insert(worktree_id, error);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
if let Some(error) = error_message {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.environment_error_messages.insert(worktree_id, error);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
shell_env
|
||||
})
|
||||
}
|
||||
shell_env
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +187,42 @@ impl EnvironmentErrorMessage {
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_worktree_shell_environment(
|
||||
worktree_abs_path: &Path,
|
||||
load_direnv: &DirenvSettings,
|
||||
) -> (
|
||||
Option<HashMap<String, String>>,
|
||||
Option<EnvironmentErrorMessage>,
|
||||
) {
|
||||
match smol::fs::metadata(worktree_abs_path).await {
|
||||
Ok(meta) => {
|
||||
let dir = if meta.is_dir() {
|
||||
worktree_abs_path
|
||||
} else if let Some(parent) = worktree_abs_path.parent() {
|
||||
parent
|
||||
} else {
|
||||
return (
|
||||
None,
|
||||
Some(EnvironmentErrorMessage(format!(
|
||||
"Failed to load shell environment in {}: not a directory",
|
||||
worktree_abs_path.display()
|
||||
))),
|
||||
);
|
||||
};
|
||||
|
||||
load_shell_environment(&dir, load_direnv).await
|
||||
}
|
||||
Err(err) => (
|
||||
None,
|
||||
Some(EnvironmentErrorMessage(format!(
|
||||
"Failed to load shell environment in {}: {}",
|
||||
worktree_abs_path.display(),
|
||||
err
|
||||
))),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
async fn load_shell_environment(
|
||||
_dir: &Path,
|
||||
|
||||
@@ -1207,13 +1207,6 @@ impl Project {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
let tree_id = tree.read(cx).id();
|
||||
project.environment.update(cx, |environment, _| {
|
||||
environment.set_cached(&[(tree_id, HashMap::default())])
|
||||
});
|
||||
});
|
||||
|
||||
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user