Compare commits
48 Commits
ureq
...
gha_braces
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ea60f64f9 | ||
|
|
0daa070448 | ||
|
|
689da9d0b1 | ||
|
|
1c5be9de4e | ||
|
|
d5f67406b0 | ||
|
|
c3075dfe9a | ||
|
|
caaa9a00a9 | ||
|
|
ffd1083cc1 | ||
|
|
6d4ecac610 | ||
|
|
dc5ffe6994 | ||
|
|
03c7f08581 | ||
|
|
73ff8c0f1f | ||
|
|
1c5d9c221a | ||
|
|
a1d2e1106e | ||
|
|
568a21a700 | ||
|
|
5199135b54 | ||
|
|
8559731e0d | ||
|
|
02d0561586 | ||
|
|
1be3c44550 | ||
|
|
32605e9ea4 | ||
|
|
c83d007138 | ||
|
|
48c6eb9ac7 | ||
|
|
e28496d4e2 | ||
|
|
c1a039a5d7 | ||
|
|
71da81c743 | ||
|
|
11058765be | ||
|
|
c7a79cfc02 | ||
|
|
84a6ded657 | ||
|
|
e5bbd378a6 | ||
|
|
82eb753b31 | ||
|
|
de1889d6a8 | ||
|
|
f143396825 | ||
|
|
7eea1a6f51 | ||
|
|
1a4f9b2891 | ||
|
|
1deed247eb | ||
|
|
db92a31067 | ||
|
|
31902a1b73 | ||
|
|
3f415f3587 | ||
|
|
140d70289e | ||
|
|
b9b689d322 | ||
|
|
2d2e20f9d4 | ||
|
|
b701eab44f | ||
|
|
6167688a63 | ||
|
|
3161aedcb0 | ||
|
|
64532e94e4 | ||
|
|
40408e731e | ||
|
|
7398f795e3 | ||
|
|
4b4565fb7a |
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.github/workflows/bump_patch_version.yml
vendored
2
.github/workflows/bump_patch_version.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
|
||||
which cargo-set-version > /dev/null || cargo install cargo-edit
|
||||
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
||||
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
||||
git tag v${output}${tag_suffix}
|
||||
|
||||
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
@@ -159,7 +159,9 @@ jobs:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
needs: [macos_tests]
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
@@ -172,7 +174,7 @@ jobs:
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
@@ -190,7 +192,7 @@ jobs:
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
@@ -226,34 +228,42 @@ jobs:
|
||||
run: script/bundle-mac
|
||||
|
||||
- name: Rename single-architecture binaries
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
run: |
|
||||
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (universal) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' }} ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' }} ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
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@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' }} ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
name: Upload app bundle to release
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
if: env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable'
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
@@ -272,7 +282,9 @@ jobs:
|
||||
name: Create a Linux bundle
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
needs: [linux_tests]
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
@@ -287,7 +299,7 @@ jobs:
|
||||
run: ./script/linux
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
@@ -318,7 +330,9 @@ jobs:
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' }} ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-*.tar.gz
|
||||
@@ -340,7 +354,9 @@ jobs:
|
||||
name: Create arm64 Linux bundle
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204-arm
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
needs: [linux_tests]
|
||||
env:
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
@@ -355,7 +371,7 @@ jobs:
|
||||
run: ./script/linux
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
@@ -386,14 +402,16 @@ jobs:
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
path: target/release/zed-*.tar.gz
|
||||
|
||||
- name: Upload app bundle to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
if: env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable'
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
|
||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
|
||||
2
.github/workflows/randomized_tests.yml
vendored
2
.github/workflows/randomized_tests.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,7 +10,7 @@
|
||||
/crates/collab/seed.json
|
||||
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
|
||||
/dev.zed.Zed*.json
|
||||
/assets/*licenses.md
|
||||
/assets/*licenses.*
|
||||
**/venv
|
||||
.build
|
||||
*.wasm
|
||||
|
||||
288
Cargo.lock
generated
288
Cargo.lock
generated
@@ -245,7 +245,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -847,8 +846,8 @@ dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"http-types",
|
||||
"hyper 0.14.30",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
@@ -895,9 +894,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.82"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1085,33 +1084,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"mirai-annotations",
|
||||
"paste",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62"
|
||||
dependencies = [
|
||||
"bindgen 0.69.4",
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
"libc",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.4.2"
|
||||
@@ -1364,13 +1336,13 @@ dependencies = [
|
||||
"aws-smithy-types",
|
||||
"bytes 1.7.1",
|
||||
"fastrand 2.1.1",
|
||||
"h2 0.3.26",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"hyper 0.14.30",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
@@ -1460,7 +1432,7 @@ dependencies = [
|
||||
"headers",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.30",
|
||||
"hyper",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
@@ -1609,15 +1581,12 @@ dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.76",
|
||||
"which 4.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2117,9 +2086,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.20.4"
|
||||
version = "0.20.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965"
|
||||
checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"toml 0.8.19",
|
||||
@@ -2313,9 +2282,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2323,9 +2292,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2345,9 +2314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.13"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -2394,7 +2363,7 @@ dependencies = [
|
||||
"clickhouse-derive",
|
||||
"clickhouse-rs-cityhash-sys",
|
||||
"futures 0.3.30",
|
||||
"hyper 0.14.30",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"lz4",
|
||||
"sealed",
|
||||
@@ -2482,15 +2451,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
version = "0.2.3"
|
||||
@@ -2607,7 +2567,7 @@ dependencies = [
|
||||
"headless",
|
||||
"hex",
|
||||
"http_client",
|
||||
"hyper 1.4.1",
|
||||
"hyper",
|
||||
"indoc",
|
||||
"isahc_http_client",
|
||||
"jsonwebtoken",
|
||||
@@ -2889,7 +2849,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"isahc",
|
||||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
@@ -3708,12 +3667,6 @@ dependencies = [
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.11.1"
|
||||
@@ -3776,6 +3729,7 @@ dependencies = [
|
||||
"multi_buffer",
|
||||
"ordered-float 2.10.1",
|
||||
"parking_lot",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
@@ -4173,7 +4127,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"indexed_docs",
|
||||
"isahc",
|
||||
"isahc_http_client",
|
||||
"language",
|
||||
"log",
|
||||
@@ -4334,7 +4287,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"human_bytes",
|
||||
"isahc",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
@@ -4641,12 +4593,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.1.0"
|
||||
@@ -5067,7 +5013,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5236,25 +5181,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes 1.7.1",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap 2.4.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
@@ -5635,7 +5561,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"derive_more",
|
||||
"futures 0.3.30",
|
||||
"http 1.1.0",
|
||||
"http 0.2.12",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5677,7 +5603,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.26",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
@@ -5691,26 +5617,6 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
|
||||
dependencies = [
|
||||
"bytes 1.7.1",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.6",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
@@ -5719,31 +5625,12 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.30",
|
||||
"hyper",
|
||||
"log",
|
||||
"rustls 0.21.12",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"hyper 1.4.1",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rustls 0.23.13",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5753,51 +5640,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes 1.7.1",
|
||||
"hyper 0.14.30",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
|
||||
dependencies = [
|
||||
"bytes 1.7.1",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.4.1",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"derive_more",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"hyper 1.4.1",
|
||||
"hyper-rustls 0.27.3",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"ureq",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
@@ -6436,7 +6284,6 @@ dependencies = [
|
||||
"http_client",
|
||||
"image",
|
||||
"inline_completion_button",
|
||||
"isahc",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
@@ -6582,9 +6429,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
version = "0.2.159"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -7193,12 +7040,6 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mirai-annotations"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
|
||||
|
||||
[[package]]
|
||||
name = "multi_buffer"
|
||||
version = "0.1.0"
|
||||
@@ -7745,7 +7586,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -9276,6 +9116,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"languages",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
@@ -9355,10 +9196,10 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.26",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.30",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
@@ -9755,26 +9596,10 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.17.8",
|
||||
"rustls-webpki 0.101.7",
|
||||
"rustls-webpki",
|
||||
"sct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring 0.17.8",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6.3"
|
||||
@@ -9835,18 +9660,6 @@ dependencies = [
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring 0.17.8",
|
||||
"rustls-pki-types",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
@@ -11995,17 +11808,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.13",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.2"
|
||||
@@ -12725,22 +12527,6 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.13",
|
||||
"rustls-pki-types",
|
||||
"url",
|
||||
"webpki-roots 0.26.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.2"
|
||||
@@ -13044,7 +12830,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.30",
|
||||
"hyper",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
@@ -13614,15 +13400,6 @@ version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.8"
|
||||
@@ -14652,7 +14429,6 @@ dependencies = [
|
||||
"image_viewer",
|
||||
"inline_completion_button",
|
||||
"install_cli",
|
||||
"isahc",
|
||||
"isahc_http_client",
|
||||
"journal",
|
||||
"language",
|
||||
@@ -14904,7 +14680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_terraform"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
@@ -129,7 +129,6 @@ members = [
|
||||
"crates/worktree",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
"crates/hyper_client",
|
||||
|
||||
#
|
||||
# Extensions
|
||||
@@ -357,9 +356,7 @@ git2 = { version = "0.19", default-features = false }
|
||||
globset = "0.4"
|
||||
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1.9"
|
||||
hyper-rustls = "0.27.3"
|
||||
hyper = "0.14"
|
||||
html5ever = "0.27.0"
|
||||
ignore = "0.4.22"
|
||||
image = "0.25.1"
|
||||
|
||||
@@ -196,7 +196,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && in_replace",
|
||||
"context": "BufferSearchBar && in_replace > Editor",
|
||||
"bindings": {
|
||||
"enter": "search::ReplaceNext",
|
||||
"ctrl-enter": "search::ReplaceAll"
|
||||
@@ -310,6 +310,11 @@
|
||||
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-shift-[": "editor::Fold",
|
||||
"ctrl-shift-]": "editor::UnfoldLines",
|
||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"alt-ctrl-r": "editor::RevealInFileManager",
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && in_replace",
|
||||
"context": "BufferSearchBar && in_replace > Editor",
|
||||
"bindings": {
|
||||
"enter": "search::ReplaceNext",
|
||||
"cmd-enter": "search::ReplaceAll"
|
||||
@@ -347,6 +347,11 @@
|
||||
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
"alt-cmd-]": "editor::UnfoldLines",
|
||||
"cmd-k cmd-l": "editor::ToggleFold",
|
||||
"cmd-k cmd-[": "editor::FoldRecursive",
|
||||
"cmd-k cmd-]": "editor::UnfoldRecursive",
|
||||
"cmd-k cmd-0": "editor::FoldAll",
|
||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"cmd-.": "editor::ToggleCodeActions",
|
||||
"alt-cmd-r": "editor::RevealInFileManager",
|
||||
|
||||
@@ -132,9 +132,15 @@
|
||||
"z z": "editor::ScrollCursorCenter",
|
||||
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
||||
"z b": "editor::ScrollCursorBottom",
|
||||
"z a": "editor::ToggleFold",
|
||||
"z A": "editor::ToggleFoldRecursive",
|
||||
"z c": "editor::Fold",
|
||||
"z C": "editor::FoldRecursive",
|
||||
"z o": "editor::UnfoldLines",
|
||||
"z O": "editor::UnfoldRecursive",
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"z M": "editor::FoldAll",
|
||||
"z R": "editor::UnfoldAll",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||
// Count support
|
||||
|
||||
@@ -535,17 +535,16 @@
|
||||
// How to soft-wrap long lines of text.
|
||||
// Possible values:
|
||||
//
|
||||
// 1. Do not soft wrap.
|
||||
// 1. Prefer a single line generally, unless an overly long line is encountered.
|
||||
// "soft_wrap": "none",
|
||||
// 2. Prefer a single line generally, unless an overly long line is encountered.
|
||||
// "soft_wrap": "prefer_line",
|
||||
// 3. Soft wrap lines that overflow the editor.
|
||||
// "soft_wrap": "prefer_line", // (deprecated, same as "none")
|
||||
// 2. Soft wrap lines that overflow the editor.
|
||||
// "soft_wrap": "editor_width",
|
||||
// 4. Soft wrap lines at the preferred line length.
|
||||
// 3. Soft wrap lines at the preferred line length.
|
||||
// "soft_wrap": "preferred_line_length",
|
||||
// 5. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
|
||||
// 4. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
|
||||
// "soft_wrap": "bounded",
|
||||
"soft_wrap": "prefer_line",
|
||||
"soft_wrap": "none",
|
||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
// is enabled.
|
||||
"preferred_line_length": 80,
|
||||
|
||||
@@ -20,7 +20,6 @@ anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::{pin::Pin, str::FromStr};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use isahc::http::{HeaderMap, HeaderValue};
|
||||
use http_client::http::{HeaderMap, HeaderValue};
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
use thiserror::Error;
|
||||
@@ -288,7 +288,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
let serialized_request =
|
||||
serde_json::to_string(&request).context("failed to serialize request")?;
|
||||
|
||||
@@ -72,6 +72,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use text::SelectionGoal;
|
||||
use ui::TintColor;
|
||||
use ui::{
|
||||
prelude::*,
|
||||
@@ -960,7 +961,8 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||
if self.project.read(cx).is_via_collab() {
|
||||
let project = self.project.read(cx);
|
||||
if project.is_via_collab() && project.dev_server_project_id().is_none() {
|
||||
let task = self
|
||||
.context_store
|
||||
.update(cx, |store, cx| store.create_remote_context(cx));
|
||||
@@ -3437,7 +3439,7 @@ impl ContextEditor {
|
||||
|
||||
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
|
||||
if self.editor.read(cx).selections.count() == 1 {
|
||||
let (copied_text, metadata) = self.get_clipboard_contents(cx);
|
||||
let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
copied_text,
|
||||
metadata,
|
||||
@@ -3451,11 +3453,9 @@ impl ContextEditor {
|
||||
|
||||
fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
|
||||
if self.editor.read(cx).selections.count() == 1 {
|
||||
let (copied_text, metadata) = self.get_clipboard_contents(cx);
|
||||
let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
|
||||
editor.transact(cx, |this, cx| {
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select(selections);
|
||||
@@ -3475,52 +3475,71 @@ impl ContextEditor {
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn get_clipboard_contents(&mut self, cx: &mut ViewContext<Self>) -> (String, CopyMetadata) {
|
||||
let creases = self.editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.newest::<Point>(cx);
|
||||
let selection_start = editor.selections.newest::<usize>(cx).start;
|
||||
fn get_clipboard_contents(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
|
||||
let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
|
||||
let mut selection = editor.selections.newest::<Point>(cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map
|
||||
.snapshot(cx)
|
||||
.crease_snapshot
|
||||
.creases_in_range(
|
||||
MultiBufferRow(selection.start.row)..MultiBufferRow(selection.end.row + 1),
|
||||
&snapshot,
|
||||
)
|
||||
.filter_map(|crease| {
|
||||
if let Some(metadata) = &crease.metadata {
|
||||
let start = crease
|
||||
.range
|
||||
.start
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
let end = crease
|
||||
.range
|
||||
.end
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
|
||||
let range_relative_to_selection = start..end;
|
||||
let is_entire_line = selection.is_empty() || editor.selections.line_mode;
|
||||
if is_entire_line {
|
||||
selection.start = Point::new(selection.start.row, 0);
|
||||
selection.end =
|
||||
cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
|
||||
selection.goal = SelectionGoal::None;
|
||||
}
|
||||
|
||||
if range_relative_to_selection.is_empty() {
|
||||
None
|
||||
let selection_start = snapshot.point_to_offset(selection.start);
|
||||
|
||||
(
|
||||
snapshot.clone(),
|
||||
selection.clone(),
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map
|
||||
.snapshot(cx)
|
||||
.crease_snapshot
|
||||
.creases_in_range(
|
||||
MultiBufferRow(selection.start.row)
|
||||
..MultiBufferRow(selection.end.row + 1),
|
||||
&snapshot,
|
||||
)
|
||||
.filter_map(|crease| {
|
||||
if let Some(metadata) = &crease.metadata {
|
||||
let start = crease
|
||||
.range
|
||||
.start
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
let end = crease
|
||||
.range
|
||||
.end
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
|
||||
let range_relative_to_selection = start..end;
|
||||
|
||||
if range_relative_to_selection.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SelectedCreaseMetadata {
|
||||
range_relative_to_selection,
|
||||
crease: metadata.clone(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Some(SelectedCreaseMetadata {
|
||||
range_relative_to_selection,
|
||||
crease: metadata.clone(),
|
||||
})
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
let selection = selection.map(|point| snapshot.point_to_offset(point));
|
||||
let context = self.context.read(cx);
|
||||
let selection = self.editor.read(cx).selections.newest::<usize>(cx);
|
||||
|
||||
let mut text = String::new();
|
||||
for message in context.messages(cx) {
|
||||
if message.offset_range.start >= selection.range().end {
|
||||
@@ -3539,7 +3558,7 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
(text, CopyMetadata { creases })
|
||||
(text, CopyMetadata { creases }, vec![selection])
|
||||
}
|
||||
|
||||
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -1142,7 +1142,7 @@ impl InlineAssistant {
|
||||
for row_range in inserted_row_ranges {
|
||||
editor.highlight_rows::<InlineAssist>(
|
||||
row_range,
|
||||
Some(cx.theme().status().info_background),
|
||||
cx.theme().status().info_background,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
@@ -1208,8 +1208,8 @@ impl InlineAssistant {
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
Anchor::min()..=Anchor::max(),
|
||||
Some(cx.theme().status().deleted_background),
|
||||
Anchor::min()..Anchor::max(),
|
||||
cx.theme().status().deleted_background,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
@@ -2557,7 +2557,7 @@ enum CodegenStatus {
|
||||
#[derive(Default)]
|
||||
struct Diff {
|
||||
deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
|
||||
inserted_row_ranges: Vec<RangeInclusive<Anchor>>,
|
||||
inserted_row_ranges: Vec<Range<Anchor>>,
|
||||
}
|
||||
|
||||
impl Diff {
|
||||
@@ -3103,7 +3103,7 @@ impl CodegenAlternative {
|
||||
new_end_row,
|
||||
new_snapshot.line_len(MultiBufferRow(new_end_row)),
|
||||
));
|
||||
self.diff.inserted_row_ranges.push(start..=end);
|
||||
self.diff.inserted_row_ranges.push(start..end);
|
||||
new_row += lines;
|
||||
}
|
||||
}
|
||||
@@ -3181,7 +3181,7 @@ impl CodegenAlternative {
|
||||
new_end_row,
|
||||
new_snapshot.line_len(MultiBufferRow(new_end_row)),
|
||||
));
|
||||
inserted_row_ranges.push(start..=end);
|
||||
inserted_row_ranges.push(start..end);
|
||||
new_row += line_count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
||||
ViewContext,
|
||||
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
|
||||
Subscription, ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use std::cmp;
|
||||
@@ -90,17 +90,30 @@ impl Render for Breadcrumbs {
|
||||
ButtonLike::new("toggle outline view")
|
||||
.child(breadcrumbs_stack)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.on_click(move |_, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
move |_, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
cx,
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||
Tooltip::for_action_in(
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::for_action(
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
),
|
||||
None => element
|
||||
|
||||
@@ -149,18 +149,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: google-ai
|
||||
key: api_key
|
||||
- name: RUNPOD_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: runpod
|
||||
key: api_key
|
||||
optional: true
|
||||
- name: RUNPOD_API_SUMMARY_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: runpod
|
||||
key: summary
|
||||
optional: true
|
||||
- name: BLOB_STORE_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
@@ -32,6 +32,7 @@ macro_rules! id_type {
|
||||
#[allow(unused)]
|
||||
#[allow(missing_docs)]
|
||||
pub fn from_proto(value: u64) -> Self {
|
||||
debug_assert!(value != 0);
|
||||
Self(value as i32)
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ impl Database {
|
||||
)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
.ok_or_else(|| anyhow!("no such project: {project_id}"))?;
|
||||
|
||||
// Update metadata.
|
||||
worktree::Entity::update(worktree::ActiveModel {
|
||||
|
||||
@@ -170,8 +170,6 @@ pub struct Config {
|
||||
pub anthropic_api_key: Option<Arc<str>>,
|
||||
pub anthropic_staff_api_key: Option<Arc<str>>,
|
||||
pub llm_closed_beta_model_name: Option<Arc<str>>,
|
||||
pub runpod_api_key: Option<Arc<str>>,
|
||||
pub runpod_api_summary_url: Option<Arc<str>>,
|
||||
pub zed_client_checksum_seed: Option<String>,
|
||||
pub slack_panics_webhook: Option<String>,
|
||||
pub auto_join_channel_id: Option<ChannelId>,
|
||||
@@ -235,8 +233,6 @@ impl Config {
|
||||
stripe_api_key: None,
|
||||
stripe_price_id: None,
|
||||
supermaven_admin_api_key: None,
|
||||
runpod_api_key: None,
|
||||
runpod_api_summary_url: None,
|
||||
user_backfiller_github_access_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,42 +400,6 @@ async fn perform_completion(
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
LanguageModelProvider::Zed => {
|
||||
let api_key = state
|
||||
.config
|
||||
.runpod_api_key
|
||||
.as_ref()
|
||||
.context("no Qwen2-7B API key configured on the server")?;
|
||||
let api_url = state
|
||||
.config
|
||||
.runpod_api_summary_url
|
||||
.as_ref()
|
||||
.context("no Qwen2-7B URL configured on the server")?;
|
||||
let chunks = open_ai::stream_completion(
|
||||
&state.http_client,
|
||||
api_url,
|
||||
api_key,
|
||||
serde_json::from_str(params.provider_request.get())?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
chunks
|
||||
.map(|event| {
|
||||
event.map(|chunk| {
|
||||
let input_tokens =
|
||||
chunk.usage.as_ref().map_or(0, |u| u.prompt_tokens) as usize;
|
||||
let output_tokens =
|
||||
chunk.usage.as_ref().map_or(0, |u| u.completion_tokens) as usize;
|
||||
(
|
||||
serde_json::to_vec(&chunk).unwrap(),
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
)
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Body::wrap_stream(TokenCountingStream {
|
||||
|
||||
@@ -77,7 +77,6 @@ fn authorize_access_for_country(
|
||||
LanguageModelProvider::Anthropic => anthropic::is_supported_country(country_code),
|
||||
LanguageModelProvider::OpenAi => open_ai::is_supported_country(country_code),
|
||||
LanguageModelProvider::Google => google_ai::is_supported_country(country_code),
|
||||
LanguageModelProvider::Zed => true,
|
||||
};
|
||||
if !is_country_supported_by_provider {
|
||||
Err(Error::http(
|
||||
@@ -213,7 +212,6 @@ mod tests {
|
||||
(LanguageModelProvider::Anthropic, "T1"), // Tor
|
||||
(LanguageModelProvider::OpenAi, "T1"), // Tor
|
||||
(LanguageModelProvider::Google, "T1"), // Tor
|
||||
(LanguageModelProvider::Zed, "T1"), // Tor
|
||||
];
|
||||
|
||||
for (provider, country_code) in cases {
|
||||
|
||||
@@ -40,15 +40,6 @@ pub async fn seed_database(_config: &Config, db: &mut LlmDatabase, _force: bool)
|
||||
price_per_million_input_tokens: 25, // $0.25/MTok
|
||||
price_per_million_output_tokens: 125, // $1.25/MTok
|
||||
},
|
||||
ModelParams {
|
||||
provider: LanguageModelProvider::Zed,
|
||||
name: "Qwen/Qwen2-7B-Instruct".into(),
|
||||
max_requests_per_minute: 5,
|
||||
max_tokens_per_minute: 25_000, // These are arbitrary limits we've set to cap costs; we control this number
|
||||
max_tokens_per_day: 300_000,
|
||||
price_per_million_input_tokens: 25,
|
||||
price_per_million_output_tokens: 125,
|
||||
},
|
||||
])
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ async fn test_initialize_providers(db: &mut LlmDatabase) {
|
||||
LanguageModelProvider::Anthropic,
|
||||
LanguageModelProvider::Google,
|
||||
LanguageModelProvider::OpenAi,
|
||||
LanguageModelProvider::Zed
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -474,9 +474,6 @@ impl Server {
|
||||
.add_request_handler(user_handler(
|
||||
forward_read_only_project_request::<proto::GetReferences>,
|
||||
))
|
||||
.add_request_handler(user_handler(
|
||||
forward_read_only_project_request::<proto::SearchProject>,
|
||||
))
|
||||
.add_request_handler(user_handler(forward_find_search_candidates_request))
|
||||
.add_request_handler(user_handler(
|
||||
forward_read_only_project_request::<proto::GetDocumentHighlights>,
|
||||
@@ -2298,7 +2295,7 @@ async fn list_remote_directory(
|
||||
let dev_server_connection_id = session
|
||||
.connection_pool()
|
||||
.await
|
||||
.dev_server_connection_id_supporting(dev_server_id, ZedVersion::with_list_directory())?;
|
||||
.online_dev_server_connection_id(dev_server_id)?;
|
||||
|
||||
session
|
||||
.db()
|
||||
@@ -2337,10 +2334,7 @@ async fn update_dev_server_project(
|
||||
let dev_server_connection_id = session
|
||||
.connection_pool()
|
||||
.await
|
||||
.dev_server_connection_id_supporting(
|
||||
dev_server_project.dev_server_id,
|
||||
ZedVersion::with_list_directory(),
|
||||
)?;
|
||||
.online_dev_server_connection_id(dev_server_project.dev_server_id)?;
|
||||
|
||||
session.peer.send(
|
||||
dev_server_connection_id,
|
||||
@@ -2950,40 +2944,6 @@ async fn forward_find_search_candidates_request(
|
||||
.await
|
||||
.host_for_read_only_project_request(project_id, session.connection_id, session.user_id())
|
||||
.await?;
|
||||
|
||||
let host_version = session
|
||||
.connection_pool()
|
||||
.await
|
||||
.connection(host_connection_id)
|
||||
.map(|c| c.zed_version);
|
||||
|
||||
if host_version.is_some_and(|host_version| host_version < ZedVersion::with_search_candidates())
|
||||
{
|
||||
let query = request.query.ok_or_else(|| anyhow!("missing query"))?;
|
||||
let search = proto::SearchProject {
|
||||
project_id: project_id.to_proto(),
|
||||
query: query.query,
|
||||
regex: query.regex,
|
||||
whole_word: query.whole_word,
|
||||
case_sensitive: query.case_sensitive,
|
||||
files_to_include: query.files_to_include,
|
||||
files_to_exclude: query.files_to_exclude,
|
||||
include_ignored: query.include_ignored,
|
||||
};
|
||||
|
||||
let payload = session
|
||||
.peer
|
||||
.forward_request(session.connection_id, host_connection_id, search)
|
||||
.await?;
|
||||
return response.send(proto::FindSearchCandidatesResponse {
|
||||
buffer_ids: payload
|
||||
.locations
|
||||
.into_iter()
|
||||
.map(|loc| loc.buffer_id)
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
|
||||
let payload = session
|
||||
.peer
|
||||
.forward_request(session.connection_id, host_connection_id, request)
|
||||
|
||||
@@ -32,15 +32,7 @@ impl fmt::Display for ZedVersion {
|
||||
|
||||
impl ZedVersion {
|
||||
pub fn can_collaborate(&self) -> bool {
|
||||
self.0 >= SemanticVersion::new(0, 134, 0)
|
||||
}
|
||||
|
||||
pub fn with_list_directory() -> ZedVersion {
|
||||
ZedVersion(SemanticVersion::new(0, 145, 0))
|
||||
}
|
||||
|
||||
pub fn with_search_candidates() -> ZedVersion {
|
||||
ZedVersion(SemanticVersion::new(0, 151, 0))
|
||||
self.0 >= SemanticVersion::new(0, 151, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +161,16 @@ impl ConnectionPool {
|
||||
self.connected_dev_servers.get(&dev_server_id).copied()
|
||||
}
|
||||
|
||||
pub fn online_dev_server_connection_id(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
) -> Result<ConnectionId> {
|
||||
match self.connected_dev_servers.get(&dev_server_id) {
|
||||
Some(cid) => Ok(*cid),
|
||||
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dev_server_connection_id_supporting(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
|
||||
@@ -246,7 +246,7 @@ async fn test_channel_notes_participant_indices(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
|
||||
// Clients A and B open the same file.
|
||||
|
||||
@@ -76,7 +76,7 @@ async fn test_host_disconnect(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
cx_a.background_executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
@@ -192,7 +192,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a buffer as client A
|
||||
let buffer_a = project_a
|
||||
@@ -308,7 +308,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the guest.
|
||||
let buffer_b = project_b
|
||||
@@ -565,7 +565,7 @@ async fn test_collaborating_with_code_actions(
|
||||
.unwrap();
|
||||
|
||||
// Join the project as client B.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
@@ -780,7 +780,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
@@ -1030,7 +1030,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
.await
|
||||
.unwrap();
|
||||
executor.run_until_parked();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
project_b.read_with(cx_b, |project, cx| {
|
||||
let status = project.language_server_statuses(cx).next().unwrap().1;
|
||||
@@ -1126,9 +1126,7 @@ async fn test_share_project(
|
||||
.await
|
||||
.unwrap();
|
||||
let client_b_peer_id = client_b.peer_id().unwrap();
|
||||
let project_b = client_b
|
||||
.build_dev_server_project(initial_project.id, cx_b)
|
||||
.await;
|
||||
let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
|
||||
|
||||
let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
|
||||
|
||||
@@ -1230,9 +1228,7 @@ async fn test_share_project(
|
||||
.update(cx_c, |call, cx| call.accept_incoming(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let _project_c = client_c
|
||||
.build_dev_server_project(initial_project.id, cx_c)
|
||||
.await;
|
||||
let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
|
||||
|
||||
// Client B closes the editor, and client A sees client B's selections removed.
|
||||
cx_b.update(move |_| drop(editor_b));
|
||||
@@ -1291,7 +1287,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the host.
|
||||
let buffer_a = project_a
|
||||
@@ -1411,7 +1407,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the guest.
|
||||
let buffer_b = project_b
|
||||
@@ -1574,7 +1570,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
.unwrap();
|
||||
|
||||
// Client B joins the project
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
@@ -1836,7 +1832,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
@@ -2050,7 +2046,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
.unwrap();
|
||||
|
||||
// Join the project as client B.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
|
||||
@@ -74,7 +74,7 @@ async fn test_basic_following(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
@@ -162,7 +162,7 @@ async fn test_basic_following(
|
||||
|
||||
executor.run_until_parked();
|
||||
let active_call_c = cx_c.read(ActiveCall::global);
|
||||
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c);
|
||||
active_call_c
|
||||
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
|
||||
@@ -175,7 +175,7 @@ async fn test_basic_following(
|
||||
|
||||
cx_d.executor().run_until_parked();
|
||||
let active_call_d = cx_d.read(ActiveCall::global);
|
||||
let project_d = client_d.build_dev_server_project(project_id, cx_d).await;
|
||||
let project_d = client_d.join_remote_project(project_id, cx_d).await;
|
||||
let (workspace_d, cx_d) = client_d.build_workspace(&project_d, cx_d);
|
||||
active_call_d
|
||||
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
|
||||
@@ -569,7 +569,7 @@ async fn test_following_tab_order(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
@@ -686,7 +686,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
.unwrap();
|
||||
|
||||
// Client B joins the project.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
@@ -1199,7 +1199,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
@@ -1335,7 +1335,7 @@ async fn test_peers_simultaneously_following_each_other(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
|
||||
executor.run_until_parked();
|
||||
@@ -1685,7 +1685,7 @@ async fn test_following_into_excluded_file(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
.await
|
||||
|
||||
@@ -1372,7 +1372,7 @@ async fn test_unshare_project(
|
||||
.unwrap();
|
||||
|
||||
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
@@ -1392,7 +1392,7 @@ async fn test_unshare_project(
|
||||
assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected()));
|
||||
|
||||
// Client C opens the project.
|
||||
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
|
||||
// When client A unshares the project, client C's project becomes read-only.
|
||||
project_a
|
||||
@@ -1409,7 +1409,7 @@ async fn test_unshare_project(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
@@ -1514,9 +1514,9 @@ async fn test_project_reconnect(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b1 = client_b.build_dev_server_project(project1_id, cx_b).await;
|
||||
let project_b2 = client_b.build_dev_server_project(project2_id, cx_b).await;
|
||||
let project_b3 = client_b.build_dev_server_project(project3_id, cx_b).await;
|
||||
let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
|
||||
let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
|
||||
let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
|
||||
@@ -2310,8 +2310,8 @@ async fn test_propagate_saves_and_fs_changes(
|
||||
.unwrap();
|
||||
|
||||
// Join that worktree as clients B and C.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let project_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
|
||||
let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
|
||||
|
||||
@@ -2535,7 +2535,7 @@ async fn test_git_diff_base_change(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_remote = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let diff_base = "
|
||||
one
|
||||
@@ -2791,7 +2791,7 @@ async fn test_git_branch_name(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_remote = client_b.join_remote_project(project_id, cx_b).await;
|
||||
client_a
|
||||
.fs()
|
||||
.set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
|
||||
@@ -2836,7 +2836,7 @@ async fn test_git_branch_name(
|
||||
assert_branch(Some("branch-2"), project, cx)
|
||||
});
|
||||
|
||||
let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
project_remote_c.read_with(cx_c, |project, cx| {
|
||||
@@ -2891,7 +2891,7 @@ async fn test_git_status_sync(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_remote = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Wait for it to catch up to the new status
|
||||
executor.run_until_parked();
|
||||
@@ -2967,7 +2967,7 @@ async fn test_git_status_sync(
|
||||
});
|
||||
|
||||
// And synchronization while joining
|
||||
let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
project_remote_c.read_with(cx_c, |project, cx| {
|
||||
@@ -3015,7 +3015,7 @@ async fn test_fs_operations(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
@@ -3316,7 +3316,7 @@ async fn test_local_settings(
|
||||
executor.run_until_parked();
|
||||
|
||||
// As client B, join that project and observe the local settings.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
executor.run_until_parked();
|
||||
@@ -3439,7 +3439,7 @@ async fn test_buffer_conflict_after_save(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
@@ -3503,7 +3503,7 @@ async fn test_buffer_reloading(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a buffer as client B
|
||||
let buffer_b = project_b
|
||||
@@ -3557,7 +3557,7 @@ async fn test_editing_while_guest_opens_buffer(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a buffer as client A
|
||||
let buffer_a = project_a
|
||||
@@ -3605,7 +3605,7 @@ async fn test_leaving_worktree_while_opening_buffer(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// See that a guest has joined as client A.
|
||||
executor.run_until_parked();
|
||||
@@ -3652,7 +3652,7 @@ async fn test_canceling_buffer_opening(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
@@ -3709,8 +3709,8 @@ async fn test_leaving_project(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b1 = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let project_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
|
||||
// Client A sees that a guest has joined.
|
||||
executor.run_until_parked();
|
||||
@@ -3751,7 +3751,7 @@ async fn test_leaving_project(
|
||||
});
|
||||
|
||||
// Client B re-joins the project and can open buffers as before.
|
||||
let project_b2 = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
@@ -3927,7 +3927,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
);
|
||||
|
||||
// Join the worktree as client B.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Wait for server to see the diagnostics update.
|
||||
executor.run_until_parked();
|
||||
@@ -3952,7 +3952,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
});
|
||||
|
||||
// Join project as client C and observe the diagnostics.
|
||||
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
let project_c = client_c.join_remote_project(project_id, cx_c).await;
|
||||
executor.run_until_parked();
|
||||
let project_c_diagnostic_summaries =
|
||||
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
|
||||
@@ -4160,7 +4160,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
.unwrap();
|
||||
|
||||
// Join the project as client B and open all three files.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
|
||||
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
|
||||
}))
|
||||
@@ -4266,7 +4266,7 @@ async fn test_reloading_buffer_manually(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||
@@ -4364,7 +4364,7 @@ async fn test_formatting_buffer(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||
@@ -4486,7 +4486,7 @@ async fn test_prettier_formatting_buffer(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
|
||||
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||
|
||||
@@ -4599,7 +4599,7 @@ async fn test_definition(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open the file on client B.
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||
@@ -4744,7 +4744,7 @@ async fn test_references(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open the file on client B.
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
|
||||
@@ -4901,7 +4901,7 @@ async fn test_project_search(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Perform a search as the guest.
|
||||
let mut results = HashMap::default();
|
||||
@@ -4991,7 +4991,7 @@ async fn test_document_highlights(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open the file on client B.
|
||||
let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
|
||||
@@ -5109,7 +5109,7 @@ async fn test_lsp_hover(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open the file as the guest
|
||||
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
|
||||
@@ -5286,7 +5286,7 @@ async fn test_project_symbols(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Cause the language server to start.
|
||||
let open_buffer_task =
|
||||
@@ -5381,7 +5381,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||
let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap();
|
||||
@@ -6470,7 +6470,7 @@ async fn test_context_collaboration_with_reconnect(
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Client A sees that a guest has joined.
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -9,7 +9,7 @@ use remote_server::HeadlessProject;
|
||||
use serde_json::json;
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
#[gpui::test]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_sharing_an_ssh_remote_project(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
@@ -54,9 +54,8 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
let (project_a, worktree_id) = client_a
|
||||
.build_ssh_project("/code/project1", client_ssh, cx_a)
|
||||
.await;
|
||||
executor.run_until_parked();
|
||||
|
||||
// User A shares the remote project.
|
||||
// While the SSH worktree is being scanned, user A shares the remote project.
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
@@ -64,12 +63,30 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
.unwrap();
|
||||
|
||||
// User B joins the project.
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
let worktree_b = project_b
|
||||
.update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx))
|
||||
.unwrap();
|
||||
|
||||
let worktree_a = project_a
|
||||
.update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx))
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
worktree_a.update(cx_a, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new(".zed"),
|
||||
Path::new(".zed/settings.json"),
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
worktree_b.update(cx_b, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
|
||||
@@ -679,8 +679,6 @@ impl TestServer {
|
||||
stripe_api_key: None,
|
||||
stripe_price_id: None,
|
||||
supermaven_admin_api_key: None,
|
||||
runpod_api_key: None,
|
||||
runpod_api_summary_url: None,
|
||||
user_backfiller_github_access_token: None,
|
||||
},
|
||||
})
|
||||
@@ -921,7 +919,7 @@ impl TestClient {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn build_dev_server_project(
|
||||
pub async fn join_remote_project(
|
||||
&self,
|
||||
host_project_id: u64,
|
||||
guest_cx: &mut TestAppContext,
|
||||
|
||||
@@ -37,7 +37,6 @@ fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
language.workspace = true
|
||||
lsp.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -7,8 +7,7 @@ use chrono::DateTime;
|
||||
use fs::Fs;
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
|
||||
use gpui::{AppContext, AsyncAppContext, Global};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use isahc::config::Configurable;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||
use paths::home_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::watch_config_file;
|
||||
@@ -275,7 +274,7 @@ async fn request_api_token(
|
||||
.header("Accept", "application/json");
|
||||
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
|
||||
let request = request_builder.body(AsyncBody::empty())?;
|
||||
@@ -332,7 +331,7 @@ async fn stream_completion(
|
||||
.header("Copilot-Integration-Id", "vscode-chat");
|
||||
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let mut response = client.send(request).await?;
|
||||
|
||||
@@ -24,7 +24,8 @@ test-support = [
|
||||
"workspace/test-support",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-html"
|
||||
"tree-sitter-html",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
@@ -54,6 +55,7 @@ markdown.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project.workspace = true
|
||||
rand.workspace = true
|
||||
rpc.workspace = true
|
||||
@@ -74,6 +76,7 @@ theme.workspace = true
|
||||
tree-sitter-html = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
unindent = { workspace = true, optional = true }
|
||||
ui.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -230,7 +230,11 @@ gpui::actions!(
|
||||
ExpandMacroRecursively,
|
||||
FindAllReferences,
|
||||
Fold,
|
||||
FoldAll,
|
||||
FoldRecursive,
|
||||
FoldSelectedRanges,
|
||||
ToggleFold,
|
||||
ToggleFoldRecursive,
|
||||
Format,
|
||||
GoToDeclaration,
|
||||
GoToDeclarationSplit,
|
||||
@@ -340,7 +344,9 @@ gpui::actions!(
|
||||
Transpose,
|
||||
Undo,
|
||||
UndoSelection,
|
||||
UnfoldAll,
|
||||
UnfoldLines,
|
||||
UnfoldRecursive,
|
||||
UniqueLinesCaseInsensitive,
|
||||
UniqueLinesCaseSensitive,
|
||||
]
|
||||
|
||||
@@ -98,7 +98,9 @@ use language::{
|
||||
};
|
||||
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
||||
use linked_editing_ranges::refresh_linked_ranges;
|
||||
use proposed_changes_editor::{ProposedChangesBuffer, ProposedChangesEditor};
|
||||
pub use proposed_changes_editor::{
|
||||
ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
@@ -374,12 +376,20 @@ pub enum EditorMode {
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum SoftWrap {
|
||||
/// Prefer not to wrap at all.
|
||||
///
|
||||
/// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
|
||||
/// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
|
||||
GitDiff,
|
||||
/// Prefer a single line generally, unless an overly long line is encountered.
|
||||
None,
|
||||
PreferLine,
|
||||
/// Soft wrap lines that exceed the editor width.
|
||||
EditorWidth,
|
||||
/// Soft wrap lines at the preferred line length.
|
||||
Column(u32),
|
||||
/// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
|
||||
Bounded(u32),
|
||||
}
|
||||
|
||||
@@ -661,7 +671,7 @@ pub struct EditorSnapshot {
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
show_code_actions: Option<bool>,
|
||||
show_runnables: Option<bool>,
|
||||
render_git_blame_gutter: bool,
|
||||
git_blame_gutter_max_author_length: Option<usize>,
|
||||
pub display_snapshot: DisplaySnapshot,
|
||||
pub placeholder_text: Option<Arc<str>>,
|
||||
is_focused: bool,
|
||||
@@ -671,7 +681,7 @@ pub struct EditorSnapshot {
|
||||
gutter_hovered: bool,
|
||||
}
|
||||
|
||||
const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
|
||||
const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct GutterDimensions {
|
||||
@@ -811,8 +821,8 @@ impl SelectionHistory {
|
||||
|
||||
struct RowHighlight {
|
||||
index: usize,
|
||||
range: RangeInclusive<Anchor>,
|
||||
color: Option<Hsla>,
|
||||
range: Range<Anchor>,
|
||||
color: Hsla,
|
||||
should_autoscroll: bool,
|
||||
}
|
||||
|
||||
@@ -1835,7 +1845,7 @@ impl Editor {
|
||||
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
||||
|
||||
let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
|
||||
.then(|| language_settings::SoftWrap::PreferLine);
|
||||
.then(|| language_settings::SoftWrap::None);
|
||||
|
||||
let mut project_subscriptions = Vec::new();
|
||||
if mode == EditorMode::Full {
|
||||
@@ -2209,6 +2219,19 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
|
||||
let git_blame_gutter_max_author_length = self
|
||||
.render_git_blame_gutter(cx)
|
||||
.then(|| {
|
||||
if let Some(blame) = self.blame.as_ref() {
|
||||
let max_author_length =
|
||||
blame.update(cx, |blame, cx| blame.max_author_length(cx));
|
||||
Some(max_author_length)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
|
||||
EditorSnapshot {
|
||||
mode: self.mode,
|
||||
show_gutter: self.show_gutter,
|
||||
@@ -2216,7 +2239,7 @@ impl Editor {
|
||||
show_git_diff_gutter: self.show_git_diff_gutter,
|
||||
show_code_actions: self.show_code_actions,
|
||||
show_runnables: self.show_runnables,
|
||||
render_git_blame_gutter: self.render_git_blame_gutter(cx),
|
||||
git_blame_gutter_max_author_length,
|
||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||
scroll_anchor: self.scroll_manager.anchor(),
|
||||
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
|
||||
@@ -3440,7 +3463,7 @@ impl Editor {
|
||||
s.select(new_selections)
|
||||
});
|
||||
|
||||
if !bracket_inserted && EditorSettings::get_global(cx).use_on_type_format {
|
||||
if !bracket_inserted {
|
||||
if let Some(on_type_format_task) =
|
||||
this.trigger_on_type_formatting(text.to_string(), cx)
|
||||
{
|
||||
@@ -4189,6 +4212,15 @@ impl Editor {
|
||||
.read(cx)
|
||||
.text_anchor_for_position(position, cx)?;
|
||||
|
||||
let settings = language_settings::language_settings(
|
||||
buffer.read(cx).language_at(buffer_position).as_ref(),
|
||||
buffer.read(cx).file(),
|
||||
cx,
|
||||
);
|
||||
if !settings.use_on_type_format {
|
||||
return None;
|
||||
}
|
||||
|
||||
// OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
|
||||
// hence we do LSP request & edit on host side only — add formats to host's history.
|
||||
let push_to_lsp_host_history = true;
|
||||
@@ -9665,7 +9697,7 @@ impl Editor {
|
||||
if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
|
||||
let buffer = target.buffer.read(cx);
|
||||
let range = check_multiline_range(buffer, range);
|
||||
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
} else {
|
||||
@@ -10519,17 +10551,79 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
pub fn toggle_fold(&mut self, _: &actions::ToggleFold, cx: &mut ViewContext<Self>) {
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let range = if selection.is_empty() {
|
||||
let point = selection.head().to_display_point(&display_map);
|
||||
let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
|
||||
let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
|
||||
.to_point(&display_map);
|
||||
start..end
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
if display_map.folds_in_range(range).next().is_some() {
|
||||
self.unfold_lines(&Default::default(), cx)
|
||||
} else {
|
||||
self.fold(&Default::default(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_fold_recursive(
|
||||
&mut self,
|
||||
_: &actions::ToggleFoldRecursive,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let range = if selection.is_empty() {
|
||||
let point = selection.head().to_display_point(&display_map);
|
||||
let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
|
||||
let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
|
||||
.to_point(&display_map);
|
||||
start..end
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
if display_map.folds_in_range(range).next().is_some() {
|
||||
self.unfold_recursive(&Default::default(), cx)
|
||||
} else {
|
||||
self.fold_recursive(&Default::default(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
|
||||
for selection in selections {
|
||||
let range = selection.range().sorted();
|
||||
let buffer_start_row = range.start.row;
|
||||
|
||||
for row in (0..=range.end.row).rev() {
|
||||
if range.start.row != range.end.row {
|
||||
let mut found = false;
|
||||
let mut row = range.start.row;
|
||||
while row <= range.end.row {
|
||||
if let Some((foldable_range, fold_text)) =
|
||||
{ display_map.foldable_range(MultiBufferRow(row)) }
|
||||
{
|
||||
found = true;
|
||||
row = foldable_range.end.row + 1;
|
||||
fold_ranges.push((foldable_range, fold_text));
|
||||
} else {
|
||||
row += 1
|
||||
}
|
||||
}
|
||||
if found {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for row in (0..=range.start.row).rev() {
|
||||
if let Some((foldable_range, fold_text)) =
|
||||
display_map.foldable_range(MultiBufferRow(row))
|
||||
{
|
||||
@@ -10546,6 +10640,61 @@ impl Editor {
|
||||
self.fold_ranges(fold_ranges, true, cx);
|
||||
}
|
||||
|
||||
pub fn fold_all(&mut self, _: &actions::FoldAll, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
|
||||
for row in 0..display_map.max_buffer_row().0 {
|
||||
if let Some((foldable_range, fold_text)) =
|
||||
display_map.foldable_range(MultiBufferRow(row))
|
||||
{
|
||||
fold_ranges.push((foldable_range, fold_text));
|
||||
}
|
||||
}
|
||||
|
||||
self.fold_ranges(fold_ranges, true, cx);
|
||||
}
|
||||
|
||||
pub fn fold_recursive(&mut self, _: &actions::FoldRecursive, cx: &mut ViewContext<Self>) {
|
||||
let mut fold_ranges = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
|
||||
for selection in selections {
|
||||
let range = selection.range().sorted();
|
||||
let buffer_start_row = range.start.row;
|
||||
|
||||
if range.start.row != range.end.row {
|
||||
let mut found = false;
|
||||
for row in range.start.row..=range.end.row {
|
||||
if let Some((foldable_range, fold_text)) =
|
||||
{ display_map.foldable_range(MultiBufferRow(row)) }
|
||||
{
|
||||
found = true;
|
||||
fold_ranges.push((foldable_range, fold_text));
|
||||
}
|
||||
}
|
||||
if found {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for row in (0..=range.start.row).rev() {
|
||||
if let Some((foldable_range, fold_text)) =
|
||||
display_map.foldable_range(MultiBufferRow(row))
|
||||
{
|
||||
if foldable_range.end.row >= buffer_start_row {
|
||||
fold_ranges.push((foldable_range, fold_text));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fold_ranges(fold_ranges, true, cx);
|
||||
}
|
||||
|
||||
pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
|
||||
let buffer_row = fold_at.buffer_row;
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
@@ -10580,6 +10729,24 @@ impl Editor {
|
||||
self.unfold_ranges(ranges, true, true, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_recursive(&mut self, _: &UnfoldRecursive, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
let ranges = selections
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let mut range = s.display_range(&display_map).sorted();
|
||||
*range.start.column_mut() = 0;
|
||||
*range.end.column_mut() = display_map.line_len(range.end.row());
|
||||
let start = range.start.to_point(&display_map);
|
||||
let end = range.end.to_point(&display_map);
|
||||
start..end
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.unfold_ranges(ranges, true, true, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
|
||||
@@ -10598,6 +10765,16 @@ impl Editor {
|
||||
self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
|
||||
}
|
||||
|
||||
pub fn unfold_all(&mut self, _: &actions::UnfoldAll, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
self.unfold_ranges(
|
||||
[Point::zero()..display_map.max_point().to_point(&display_map)],
|
||||
true,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
@@ -10874,8 +11051,9 @@ impl Editor {
|
||||
let settings = self.buffer.read(cx).settings_at(0, cx);
|
||||
let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
|
||||
match mode {
|
||||
language_settings::SoftWrap::None => SoftWrap::None,
|
||||
language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine,
|
||||
language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
|
||||
SoftWrap::None
|
||||
}
|
||||
language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
|
||||
language_settings::SoftWrap::PreferredLineLength => {
|
||||
SoftWrap::Column(settings.preferred_line_length)
|
||||
@@ -10923,9 +11101,10 @@ impl Editor {
|
||||
self.soft_wrap_mode_override.take();
|
||||
} else {
|
||||
let soft_wrap = match self.soft_wrap_mode(cx) {
|
||||
SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
|
||||
SoftWrap::GitDiff => return,
|
||||
SoftWrap::None => language_settings::SoftWrap::EditorWidth,
|
||||
SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
|
||||
language_settings::SoftWrap::PreferLine
|
||||
language_settings::SoftWrap::None
|
||||
}
|
||||
};
|
||||
self.soft_wrap_mode_override = Some(soft_wrap);
|
||||
@@ -11321,56 +11500,130 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
|
||||
/// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
|
||||
/// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
|
||||
/// Adds a row highlight for the given range. If a row has multiple highlights, the
|
||||
/// last highlight added will be used.
|
||||
///
|
||||
/// If the range ends at the beginning of a line, then that line will not be highlighted.
|
||||
pub fn highlight_rows<T: 'static>(
|
||||
&mut self,
|
||||
rows: RangeInclusive<Anchor>,
|
||||
color: Option<Hsla>,
|
||||
range: Range<Anchor>,
|
||||
color: Hsla,
|
||||
should_autoscroll: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
|
||||
let existing_highlight_index = row_highlights.binary_search_by(|highlight| {
|
||||
highlight
|
||||
.range
|
||||
.start()
|
||||
.cmp(rows.start(), &snapshot)
|
||||
.then(highlight.range.end().cmp(rows.end(), &snapshot))
|
||||
let ix = row_highlights.binary_search_by(|highlight| {
|
||||
Ordering::Equal
|
||||
.then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
|
||||
.then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
|
||||
});
|
||||
match (color, existing_highlight_index) {
|
||||
(Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert(
|
||||
ix,
|
||||
RowHighlight {
|
||||
index: post_inc(&mut self.highlight_order),
|
||||
range: rows,
|
||||
should_autoscroll,
|
||||
color,
|
||||
},
|
||||
),
|
||||
(None, Ok(i)) => {
|
||||
row_highlights.remove(i);
|
||||
|
||||
if let Err(mut ix) = ix {
|
||||
let index = post_inc(&mut self.highlight_order);
|
||||
|
||||
// If this range intersects with the preceding highlight, then merge it with
|
||||
// the preceding highlight. Otherwise insert a new highlight.
|
||||
let mut merged = false;
|
||||
if ix > 0 {
|
||||
let prev_highlight = &mut row_highlights[ix - 1];
|
||||
if prev_highlight
|
||||
.range
|
||||
.end
|
||||
.cmp(&range.start, &snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
ix -= 1;
|
||||
if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
|
||||
prev_highlight.range.end = range.end;
|
||||
}
|
||||
merged = true;
|
||||
prev_highlight.index = index;
|
||||
prev_highlight.color = color;
|
||||
prev_highlight.should_autoscroll = should_autoscroll;
|
||||
}
|
||||
}
|
||||
|
||||
if !merged {
|
||||
row_highlights.insert(
|
||||
ix,
|
||||
RowHighlight {
|
||||
range: range.clone(),
|
||||
index,
|
||||
color,
|
||||
should_autoscroll,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// If any of the following highlights intersect with this one, merge them.
|
||||
while let Some(next_highlight) = row_highlights.get(ix + 1) {
|
||||
let highlight = &row_highlights[ix];
|
||||
if next_highlight
|
||||
.range
|
||||
.start
|
||||
.cmp(&highlight.range.end, &snapshot)
|
||||
.is_le()
|
||||
{
|
||||
if next_highlight
|
||||
.range
|
||||
.end
|
||||
.cmp(&highlight.range.end, &snapshot)
|
||||
.is_gt()
|
||||
{
|
||||
row_highlights[ix].range.end = next_highlight.range.end;
|
||||
}
|
||||
row_highlights.remove(ix + 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove any highlighted row ranges of the given type that intersect the
|
||||
/// given ranges.
|
||||
pub fn remove_highlighted_rows<T: 'static>(
|
||||
&mut self,
|
||||
ranges_to_remove: Vec<Range<Anchor>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
|
||||
let mut ranges_to_remove = ranges_to_remove.iter().peekable();
|
||||
row_highlights.retain(|highlight| {
|
||||
while let Some(range_to_remove) = ranges_to_remove.peek() {
|
||||
match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
|
||||
Ordering::Less | Ordering::Equal => {
|
||||
ranges_to_remove.next();
|
||||
}
|
||||
Ordering::Greater => {
|
||||
match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
|
||||
Ordering::Less | Ordering::Equal => {
|
||||
return false;
|
||||
}
|
||||
Ordering::Greater => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
|
||||
pub fn clear_row_highlights<T: 'static>(&mut self) {
|
||||
self.highlighted_rows.remove(&TypeId::of::<T>());
|
||||
}
|
||||
|
||||
/// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
|
||||
pub fn highlighted_rows<T: 'static>(
|
||||
&self,
|
||||
) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
|
||||
Some(
|
||||
self.highlighted_rows
|
||||
.get(&TypeId::of::<T>())?
|
||||
.iter()
|
||||
.map(|highlight| (&highlight.range, highlight.color.as_ref())),
|
||||
)
|
||||
pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
|
||||
self.highlighted_rows
|
||||
.get(&TypeId::of::<T>())
|
||||
.map_or(&[] as &[_], |vec| vec.as_slice())
|
||||
.iter()
|
||||
.map(|highlight| (highlight.range.clone(), highlight.color))
|
||||
}
|
||||
|
||||
/// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
|
||||
@@ -11388,17 +11641,22 @@ impl Editor {
|
||||
.fold(
|
||||
BTreeMap::<DisplayRow, Hsla>::new(),
|
||||
|mut unique_rows, highlight| {
|
||||
let start_row = highlight.range.start().to_display_point(&snapshot).row();
|
||||
let end_row = highlight.range.end().to_display_point(&snapshot).row();
|
||||
for row in start_row.0..=end_row.0 {
|
||||
let start = highlight.range.start.to_display_point(&snapshot);
|
||||
let end = highlight.range.end.to_display_point(&snapshot);
|
||||
let start_row = start.row().0;
|
||||
let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
|
||||
&& end.column() == 0
|
||||
{
|
||||
end.row().0.saturating_sub(1)
|
||||
} else {
|
||||
end.row().0
|
||||
};
|
||||
for row in start_row..=end_row {
|
||||
let used_index =
|
||||
used_highlight_orders.entry(row).or_insert(highlight.index);
|
||||
if highlight.index >= *used_index {
|
||||
*used_index = highlight.index;
|
||||
match highlight.color {
|
||||
Some(hsla) => unique_rows.insert(DisplayRow(row), hsla),
|
||||
None => unique_rows.remove(&DisplayRow(row)),
|
||||
};
|
||||
unique_rows.insert(DisplayRow(row), highlight.color);
|
||||
}
|
||||
}
|
||||
unique_rows
|
||||
@@ -11414,10 +11672,11 @@ impl Editor {
|
||||
.values()
|
||||
.flat_map(|highlighted_rows| highlighted_rows.iter())
|
||||
.filter_map(|highlight| {
|
||||
if highlight.color.is_none() || !highlight.should_autoscroll {
|
||||
return None;
|
||||
if highlight.should_autoscroll {
|
||||
Some(highlight.range.start.to_display_point(snapshot).row())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(highlight.range.start().to_display_point(snapshot).row())
|
||||
})
|
||||
.min()
|
||||
}
|
||||
@@ -11923,12 +12182,19 @@ impl Editor {
|
||||
)),
|
||||
cx,
|
||||
);
|
||||
let editor_settings = EditorSettings::get_global(cx);
|
||||
if let Some(cursor_shape) = editor_settings.cursor_shape {
|
||||
self.cursor_shape = cursor_shape;
|
||||
|
||||
let old_cursor_shape = self.cursor_shape;
|
||||
|
||||
{
|
||||
let editor_settings = EditorSettings::get_global(cx);
|
||||
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
|
||||
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
|
||||
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
|
||||
}
|
||||
|
||||
if old_cursor_shape != self.cursor_shape {
|
||||
cx.emit(EditorEvent::CursorShapeChanged);
|
||||
}
|
||||
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
|
||||
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
|
||||
|
||||
let project_settings = ProjectSettings::get_global(cx);
|
||||
self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
|
||||
@@ -12938,6 +13204,7 @@ impl EditorSnapshot {
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
em_width: Pixels,
|
||||
em_advance: Pixels,
|
||||
max_line_number_width: Pixels,
|
||||
cx: &AppContext,
|
||||
) -> GutterDimensions {
|
||||
@@ -12958,7 +13225,7 @@ impl EditorSnapshot {
|
||||
.unwrap_or(gutter_settings.line_numbers);
|
||||
let line_gutter_width = if show_line_numbers {
|
||||
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
|
||||
let min_width_for_number_on_gutter = em_width * 4.0;
|
||||
let min_width_for_number_on_gutter = em_advance * 4.0;
|
||||
max_line_number_width.max(min_width_for_number_on_gutter)
|
||||
} else {
|
||||
0.0.into()
|
||||
@@ -12970,9 +13237,19 @@ impl EditorSnapshot {
|
||||
|
||||
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
|
||||
|
||||
let git_blame_entries_width = self
|
||||
.render_git_blame_gutter
|
||||
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
|
||||
let git_blame_entries_width =
|
||||
self.git_blame_gutter_max_author_length
|
||||
.map(|max_author_length| {
|
||||
// Length of the author name, but also space for the commit hash,
|
||||
// the spacing and the timestamp.
|
||||
let max_char_count = max_author_length
|
||||
.min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
|
||||
+ 7 // length of commit sha
|
||||
+ 14 // length of max relative timestamp ("60 minutes ago")
|
||||
+ 4; // gaps and margins
|
||||
|
||||
em_advance * max_char_count
|
||||
});
|
||||
|
||||
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
|
||||
left_padding += if show_code_actions || show_runnables {
|
||||
@@ -13125,6 +13402,7 @@ pub enum EditorEvent {
|
||||
TransactionBegun {
|
||||
transaction_id: clock::Lamport,
|
||||
},
|
||||
CursorShapeChanged,
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for Editor {}
|
||||
|
||||
@@ -13,7 +13,6 @@ pub struct EditorSettings {
|
||||
pub show_completions_on_input: bool,
|
||||
pub show_completion_documentation: bool,
|
||||
pub completion_documentation_secondary_query_debounce: u64,
|
||||
pub use_on_type_format: bool,
|
||||
pub toolbar: Toolbar,
|
||||
pub scrollbar: Scrollbar,
|
||||
pub gutter: Gutter,
|
||||
@@ -209,11 +208,6 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: 300 ms
|
||||
pub completion_documentation_secondary_query_debounce: Option<u64>,
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
///
|
||||
/// Default: true
|
||||
pub use_on_type_format: Option<bool>,
|
||||
/// Toolbar related settings
|
||||
pub toolbar: Option<ToolbarContent>,
|
||||
/// Scrollbar related settings
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ use crate::{
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
|
||||
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
@@ -335,8 +335,14 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::open_url);
|
||||
register_action(view, cx, Editor::open_file);
|
||||
register_action(view, cx, Editor::fold);
|
||||
register_action(view, cx, Editor::fold_all);
|
||||
register_action(view, cx, Editor::fold_at);
|
||||
register_action(view, cx, Editor::fold_recursive);
|
||||
register_action(view, cx, Editor::toggle_fold);
|
||||
register_action(view, cx, Editor::toggle_fold_recursive);
|
||||
register_action(view, cx, Editor::unfold_lines);
|
||||
register_action(view, cx, Editor::unfold_recursive);
|
||||
register_action(view, cx, Editor::unfold_all);
|
||||
register_action(view, cx, Editor::unfold_at);
|
||||
register_action(view, cx, Editor::fold_selected_ranges);
|
||||
register_action(view, cx, Editor::show_completions);
|
||||
@@ -1445,7 +1451,7 @@ impl EditorElement {
|
||||
AvailableSpace::MaxContent
|
||||
};
|
||||
let scroll_top = scroll_position.y * line_height;
|
||||
let start_x = em_width * 1;
|
||||
let start_x = em_width;
|
||||
|
||||
let mut last_used_color: Option<(PlayerColor, Oid)> = None;
|
||||
|
||||
@@ -4228,7 +4234,7 @@ fn render_blame_entry(
|
||||
let short_commit_id = blame_entry.sha.display_short();
|
||||
|
||||
let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
|
||||
let name = util::truncate_and_trailoff(author_name, 20);
|
||||
let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED);
|
||||
|
||||
let details = blame.read(cx).details_for_entry(&blame_entry);
|
||||
|
||||
@@ -4240,22 +4246,21 @@ fn render_blame_entry(
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.font_family(style.text.font().family)
|
||||
.line_height(style.text.line_height)
|
||||
.id(("blame", ix))
|
||||
.children([
|
||||
div()
|
||||
.text_color(sha_color.cursor)
|
||||
.child(short_commit_id)
|
||||
.mr_2(),
|
||||
div()
|
||||
.w_full()
|
||||
.h_flex()
|
||||
.justify_between()
|
||||
.text_color(cx.theme().status().hint)
|
||||
.child(name)
|
||||
.child(relative_timestamp),
|
||||
])
|
||||
.text_color(cx.theme().status().hint)
|
||||
.pr_2()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(div().text_color(sha_color.cursor).child(short_commit_id))
|
||||
.child(name),
|
||||
)
|
||||
.child(relative_timestamp)
|
||||
.on_mouse_down(MouseButton::Right, {
|
||||
let blame_entry = blame_entry.clone();
|
||||
let details = details.clone();
|
||||
@@ -4970,6 +4975,7 @@ impl Element for EditorElement {
|
||||
font_id,
|
||||
font_size,
|
||||
em_width,
|
||||
em_advance,
|
||||
self.max_line_number_width(&snapshot, cx),
|
||||
cx,
|
||||
);
|
||||
@@ -4994,10 +5000,8 @@ impl Element for EditorElement {
|
||||
snapshot
|
||||
} else {
|
||||
let wrap_width = match editor.soft_wrap_mode(cx) {
|
||||
SoftWrap::None => None,
|
||||
SoftWrap::PreferLine => {
|
||||
Some((MAX_LINE_LEN / 2) as f32 * em_advance)
|
||||
}
|
||||
SoftWrap::GitDiff => None,
|
||||
SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
|
||||
SoftWrap::EditorWidth => Some(editor_width),
|
||||
SoftWrap::Column(column) => Some(column as f32 * em_advance),
|
||||
SoftWrap::Bounded(column) => {
|
||||
@@ -6283,10 +6287,21 @@ fn compute_auto_height_layout(
|
||||
.unwrap()
|
||||
.size
|
||||
.width;
|
||||
let em_advance = cx
|
||||
.text_system()
|
||||
.advance(font_id, font_size, 'm')
|
||||
.unwrap()
|
||||
.width;
|
||||
|
||||
let mut snapshot = editor.snapshot(cx);
|
||||
let gutter_dimensions =
|
||||
snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
|
||||
let gutter_dimensions = snapshot.gutter_dimensions(
|
||||
font_id,
|
||||
font_size,
|
||||
em_width,
|
||||
em_advance,
|
||||
max_line_number_width,
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.gutter_dimensions = gutter_dimensions;
|
||||
let text_width = width - gutter_dimensions.width;
|
||||
|
||||
@@ -207,6 +207,27 @@ impl GitBlame {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn max_author_length(&mut self, cx: &mut ModelContext<Self>) -> usize {
|
||||
self.sync(cx);
|
||||
|
||||
let mut max_author_length = 0;
|
||||
|
||||
for entry in self.entries.iter() {
|
||||
let author_len = entry
|
||||
.blame
|
||||
.as_ref()
|
||||
.and_then(|entry| entry.author.as_ref())
|
||||
.map(|author| author.len());
|
||||
if let Some(author_len) = author_len {
|
||||
if author_len > max_author_length {
|
||||
max_author_length = author_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max_author_length
|
||||
}
|
||||
|
||||
pub fn blur(&mut self, _: &mut ModelContext<Self>) {
|
||||
self.focused = false;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
|
||||
opening_range.to_anchors(&snapshot.buffer_snapshot),
|
||||
closing_range.to_anchors(&snapshot.buffer_snapshot),
|
||||
],
|
||||
|theme| theme.editor_document_highlight_read_background,
|
||||
|theme| theme.editor_document_highlight_bracket_background,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
|
||||
MultiBufferSnapshot, ToPoint,
|
||||
};
|
||||
use std::{
|
||||
ops::{Range, RangeInclusive},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use ui::{
|
||||
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
|
||||
ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,
|
||||
@@ -19,8 +16,8 @@ use util::RangeExt;
|
||||
use crate::{
|
||||
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, BlockDisposition,
|
||||
BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot,
|
||||
Editor, EditorElement, EditorSnapshot, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
|
||||
RangeToAnchorExt, RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
|
||||
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -219,14 +216,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
for removed_rows in highlights_to_remove {
|
||||
editor.highlight_rows::<DiffRowHighlight>(
|
||||
to_inclusive_row_range(removed_rows, &snapshot),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
|
||||
editor.remove_blocks(blocks_to_remove, None, cx);
|
||||
for hunk in hunks_to_expand {
|
||||
editor.expand_diff_hunk(None, &hunk, cx);
|
||||
@@ -305,8 +295,8 @@ impl Editor {
|
||||
}
|
||||
DiffHunkStatus::Added => {
|
||||
self.highlight_rows::<DiffRowHighlight>(
|
||||
to_inclusive_row_range(hunk_start..hunk_end, &snapshot),
|
||||
Some(added_hunk_color(cx)),
|
||||
hunk_start..hunk_end,
|
||||
added_hunk_color(cx),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
@@ -314,8 +304,8 @@ impl Editor {
|
||||
}
|
||||
DiffHunkStatus::Modified => {
|
||||
self.highlight_rows::<DiffRowHighlight>(
|
||||
to_inclusive_row_range(hunk_start..hunk_end, &snapshot),
|
||||
Some(added_hunk_color(cx)),
|
||||
hunk_start..hunk_end,
|
||||
added_hunk_color(cx),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
@@ -520,49 +510,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.pr_6()
|
||||
.child({
|
||||
let focus = editor.focus_handle(cx);
|
||||
PopoverMenu::new("hunk-controls-dropdown")
|
||||
.trigger(
|
||||
IconButton::new(
|
||||
"toggle_editor_selections_icon",
|
||||
IconName::EllipsisVertical,
|
||||
)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected(
|
||||
hunk_controls_menu_handle.is_deployed(),
|
||||
)
|
||||
.when(
|
||||
!hunk_controls_menu_handle.is_deployed(),
|
||||
|this| {
|
||||
this.tooltip(|cx| {
|
||||
Tooltip::text("Hunk Controls", cx)
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.anchor(AnchorCorner::TopRight)
|
||||
.with_handle(hunk_controls_menu_handle)
|
||||
.menu(move |cx| {
|
||||
let focus = focus.clone();
|
||||
let menu =
|
||||
ContextMenu::build(cx, move |menu, _| {
|
||||
menu.context(focus.clone()).action(
|
||||
"Discard All",
|
||||
RevertFile.boxed_clone(),
|
||||
)
|
||||
});
|
||||
Some(menu)
|
||||
})
|
||||
})
|
||||
)
|
||||
.child(
|
||||
IconButton::new("discard", IconName::RotateCcw)
|
||||
.shape(IconButtonShape::Square)
|
||||
@@ -608,31 +556,70 @@ impl Editor {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("collapse", IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Collapse Hunk",
|
||||
&ToggleHunkDiff,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
let hunk = hunk.clone();
|
||||
move |_event, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_hovered_hunk(&hunk, cx);
|
||||
.child({
|
||||
let focus = editor.focus_handle(cx);
|
||||
PopoverMenu::new("hunk-controls-dropdown")
|
||||
.trigger(
|
||||
IconButton::new(
|
||||
"toggle_editor_selections_icon",
|
||||
IconName::EllipsisVertical,
|
||||
)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected(
|
||||
hunk_controls_menu_handle.is_deployed(),
|
||||
)
|
||||
.when(
|
||||
!hunk_controls_menu_handle.is_deployed(),
|
||||
|this| {
|
||||
this.tooltip(|cx| {
|
||||
Tooltip::text("Hunk Controls", cx)
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.anchor(AnchorCorner::TopRight)
|
||||
.with_handle(hunk_controls_menu_handle)
|
||||
.menu(move |cx| {
|
||||
let focus = focus.clone();
|
||||
let menu =
|
||||
ContextMenu::build(cx, move |menu, _| {
|
||||
menu.context(focus.clone()).action(
|
||||
"Discard All",
|
||||
RevertFile.boxed_clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
Some(menu)
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex().gap_2().pr_6().child(
|
||||
IconButton::new("collapse", IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Collapse Hunk",
|
||||
&ToggleHunkDiff,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
let hunk = hunk.clone();
|
||||
move |_event, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_hovered_hunk(&hunk, cx);
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
@@ -850,14 +837,7 @@ impl Editor {
|
||||
retain
|
||||
});
|
||||
|
||||
for removed_rows in highlights_to_remove {
|
||||
editor.highlight_rows::<DiffRowHighlight>(
|
||||
to_inclusive_row_range(removed_rows, &snapshot),
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
|
||||
editor.remove_blocks(blocks_to_remove, None, cx);
|
||||
|
||||
if let Some(diff_base_buffer) = &diff_base_buffer {
|
||||
@@ -977,8 +957,8 @@ fn editor_with_deleted_text(
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
editor.highlight_rows::<DiffRowHighlight>(
|
||||
Anchor::min()..=Anchor::max(),
|
||||
Some(deleted_color),
|
||||
Anchor::min()..Anchor::max(),
|
||||
deleted_color,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
@@ -1056,21 +1036,6 @@ fn buffer_diff_hunk(
|
||||
None
|
||||
}
|
||||
|
||||
fn to_inclusive_row_range(
|
||||
row_range: Range<Anchor>,
|
||||
snapshot: &EditorSnapshot,
|
||||
) -> RangeInclusive<Anchor> {
|
||||
let mut display_row_range =
|
||||
row_range.start.to_display_point(snapshot)..row_range.end.to_display_point(snapshot);
|
||||
if display_row_range.end.row() > display_row_range.start.row() {
|
||||
*display_row_range.end.row_mut() -= 1;
|
||||
}
|
||||
let point_range = display_row_range.start.to_point(&snapshot.display_snapshot)
|
||||
..display_row_range.end.to_point(&snapshot.display_snapshot);
|
||||
let new_range = point_range.to_anchors(&snapshot.buffer_snapshot);
|
||||
new_range.start..=new_range.end
|
||||
}
|
||||
|
||||
impl DisplayDiffHunk {
|
||||
pub fn start_display_row(&self) -> DisplayRow {
|
||||
match self {
|
||||
|
||||
@@ -6,10 +6,13 @@ use language::{Buffer, BufferEvent, Capability};
|
||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
use project::Project;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{ops::Range, time::Duration};
|
||||
use std::{any::TypeId, ops::Range, time::Duration};
|
||||
use text::ToOffset;
|
||||
use ui::prelude::*;
|
||||
use workspace::Item;
|
||||
use workspace::{
|
||||
searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation,
|
||||
ToolbarItemView,
|
||||
};
|
||||
|
||||
pub struct ProposedChangesEditor {
|
||||
editor: View<Editor>,
|
||||
@@ -23,6 +26,10 @@ pub struct ProposedChangesBuffer<T> {
|
||||
pub ranges: Vec<Range<T>>,
|
||||
}
|
||||
|
||||
pub struct ProposedChangesEditorToolbar {
|
||||
current_editor: Option<View<ProposedChangesEditor>>,
|
||||
}
|
||||
|
||||
impl ProposedChangesEditor {
|
||||
pub fn new<T: ToOffset>(
|
||||
buffers: Vec<ProposedChangesBuffer<T>>,
|
||||
@@ -96,6 +103,17 @@ impl ProposedChangesEditor {
|
||||
self.recalculate_diffs_tx.unbounded_send(buffer).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
|
||||
let buffers = self.editor.read(cx).buffer.read(cx).all_buffers();
|
||||
for branch_buffer in buffers {
|
||||
if let Some(base_buffer) = branch_buffer.read(cx).diff_base_buffer() {
|
||||
base_buffer.update(cx, |base_buffer, cx| {
|
||||
base_buffer.merge(&branch_buffer, None, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProposedChangesEditor {
|
||||
@@ -122,4 +140,66 @@ impl Item for ProposedChangesEditor {
|
||||
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||
Some("Proposed changes".into())
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &'a View<Self>,
|
||||
_: &'a AppContext,
|
||||
) -> Option<gpui::AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.to_any())
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some(self.editor.to_any())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProposedChangesEditorToolbar {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
|
||||
if self.current_editor.is_some() {
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProposedChangesEditorToolbar {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let editor = self.current_editor.clone();
|
||||
Button::new("apply-changes", "Apply All").on_click(move |_, cx| {
|
||||
if let Some(editor) = &editor {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.apply_all_changes(cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for ProposedChangesEditorToolbar {}
|
||||
|
||||
impl ToolbarItemView for ProposedChangesEditorToolbar {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn workspace::ItemHandle>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> workspace::ToolbarItemLocation {
|
||||
self.current_editor =
|
||||
active_pane_item.and_then(|item| item.downcast::<ProposedChangesEditor>());
|
||||
self.get_toolbar_item_location()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,116 +88,3 @@ pub(crate) fn build_editor_with_project(
|
||||
) -> Editor {
|
||||
Editor::new(EditorMode::Full, buffer, Some(project), true, cx)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn editor_hunks(
|
||||
editor: &Editor,
|
||||
snapshot: &DisplaySnapshot,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> Vec<(
|
||||
String,
|
||||
git::diff::DiffHunkStatus,
|
||||
std::ops::Range<crate::DisplayRow>,
|
||||
)> {
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use text::Point;
|
||||
|
||||
use crate::hunk_status;
|
||||
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
|
||||
.map(|hunk| {
|
||||
let display_range = Point::new(hunk.row_range.start.0, 0)
|
||||
.to_display_point(snapshot)
|
||||
.row()
|
||||
..Point::new(hunk.row_range.end.0, 0)
|
||||
.to_display_point(snapshot)
|
||||
.row();
|
||||
let (_, buffer, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(Point::new(hunk.row_range.start.0, 0), cx)
|
||||
.expect("no excerpt for expanded buffer's hunk start");
|
||||
let diff_base = buffer
|
||||
.read(cx)
|
||||
.diff_base()
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.slice(hunk.diff_base_byte_range.clone())
|
||||
.to_string();
|
||||
(diff_base, hunk_status(&hunk), display_range)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn expanded_hunks(
|
||||
editor: &Editor,
|
||||
snapshot: &DisplaySnapshot,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> Vec<(
|
||||
String,
|
||||
git::diff::DiffHunkStatus,
|
||||
std::ops::Range<crate::DisplayRow>,
|
||||
)> {
|
||||
editor
|
||||
.expanded_hunks
|
||||
.hunks(false)
|
||||
.map(|expanded_hunk| {
|
||||
let hunk_display_range = expanded_hunk
|
||||
.hunk_range
|
||||
.start
|
||||
.to_display_point(snapshot)
|
||||
.row()
|
||||
..expanded_hunk
|
||||
.hunk_range
|
||||
.end
|
||||
.to_display_point(snapshot)
|
||||
.row();
|
||||
let (_, buffer, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(expanded_hunk.hunk_range.start, cx)
|
||||
.expect("no excerpt for expanded buffer's hunk start");
|
||||
let diff_base = buffer
|
||||
.read(cx)
|
||||
.diff_base()
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.slice(expanded_hunk.diff_base_byte_range.clone())
|
||||
.to_string();
|
||||
(diff_base, expanded_hunk.status, hunk_display_range)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn expanded_hunks_background_highlights(
|
||||
editor: &mut Editor,
|
||||
cx: &mut gpui::WindowContext,
|
||||
) -> Vec<std::ops::RangeInclusive<crate::DisplayRow>> {
|
||||
use crate::DisplayRow;
|
||||
|
||||
let mut highlights = Vec::new();
|
||||
|
||||
let mut range_start = 0;
|
||||
let mut previous_highlighted_row = None;
|
||||
for (highlighted_row, _) in editor.highlighted_display_rows(cx) {
|
||||
match previous_highlighted_row {
|
||||
Some(previous_row) => {
|
||||
if previous_row + 1 != highlighted_row.0 {
|
||||
highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row));
|
||||
range_start = highlighted_row.0;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
range_start = highlighted_row.0;
|
||||
}
|
||||
}
|
||||
previous_highlighted_row = Some(highlighted_row.0);
|
||||
}
|
||||
if let Some(previous_row) = previous_highlighted_row {
|
||||
highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row));
|
||||
}
|
||||
|
||||
highlights
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
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;
|
||||
use git::diff::DiffHunkStatus;
|
||||
use gpui::{
|
||||
AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext,
|
||||
VisualTestContext,
|
||||
VisualTestContext, WindowHandle,
|
||||
};
|
||||
use indoc::indoc;
|
||||
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::{
|
||||
@@ -71,6 +71,16 @@ impl EditorTestContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn for_editor(editor: WindowHandle<Editor>, cx: &mut gpui::TestAppContext) -> Self {
|
||||
let editor_view = editor.root_view(cx).unwrap();
|
||||
Self {
|
||||
cx: VisualTestContext::from_window(*editor.deref(), cx),
|
||||
window: editor.into(),
|
||||
editor: editor_view,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_multibuffer<const COUNT: usize>(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
excerpts: [&str; COUNT],
|
||||
@@ -297,19 +307,85 @@ impl EditorTestContext {
|
||||
state_context
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_diff_hunks(&mut self, expected_diff: String) {
|
||||
// Normalize the expected diff. If it has no diff markers, then insert blank markers
|
||||
// before each line. Strip any whitespace-only lines.
|
||||
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}")
|
||||
}
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
// Read the actual diff from the editor's row highlights and block
|
||||
// decorations.
|
||||
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let text = editor.text(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
|
||||
.expanded_hunks
|
||||
.hunks
|
||||
.iter()
|
||||
.filter_map(|hunk| {
|
||||
if hunk.blocks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
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 deleted_text = buffer
|
||||
.read(cx)
|
||||
.diff_base()
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.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(text, deletions, insertions)
|
||||
});
|
||||
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
|
||||
}
|
||||
|
||||
/// Make an assertion about the editor's text and the ranges and directions
|
||||
/// of its selections using a string containing embedded range markers.
|
||||
///
|
||||
/// See the `util::test::marked_text_ranges` function for more information.
|
||||
#[track_caller]
|
||||
pub fn assert_editor_state(&mut self, marked_text: &str) {
|
||||
let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
|
||||
let buffer_text = self.buffer_text();
|
||||
|
||||
if buffer_text != unmarked_text {
|
||||
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}\nRaw unmarked text\n{unmarked_text}");
|
||||
}
|
||||
|
||||
let (expected_text, expected_selections) = marked_text_ranges(marked_text, true);
|
||||
pretty_assertions::assert_eq!(self.buffer_text(), expected_text, "unexpected buffer text");
|
||||
self.assert_selections(expected_selections, marked_text.to_string())
|
||||
}
|
||||
|
||||
@@ -382,25 +458,56 @@ impl EditorTestContext {
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
if expected_selections != actual_selections {
|
||||
panic!(
|
||||
indoc! {"
|
||||
|
||||
{}Editor has unexpected selections.
|
||||
|
||||
Expected selections:
|
||||
{}
|
||||
|
||||
Actual selections:
|
||||
{}
|
||||
"},
|
||||
self.assertion_context(),
|
||||
expected_marked_text,
|
||||
pretty_assertions::assert_eq!(
|
||||
actual_marked_text,
|
||||
expected_marked_text,
|
||||
"{}Editor has unexpected selections",
|
||||
self.assertion_context(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
isahc.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
|
||||
@@ -664,7 +664,7 @@ impl ExtensionStore {
|
||||
|
||||
let content_length = response
|
||||
.headers()
|
||||
.get(isahc::http::header::CONTENT_LENGTH)
|
||||
.get(http_client::http::header::CONTENT_LENGTH)
|
||||
.and_then(|value| value.to_str().ok()?.parse::<usize>().ok());
|
||||
|
||||
let mut body = BufReader::new(response.body_mut());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::http_client::AsyncBody;
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
@@ -8,7 +8,6 @@ use async_trait::async_trait;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use isahc::config::{Configurable, RedirectPolicy};
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
@@ -297,10 +296,12 @@ fn convert_request(
|
||||
let mut request = ::http_client::Request::builder()
|
||||
.method(::http_client::Method::from(extension_request.method))
|
||||
.uri(&extension_request.url)
|
||||
.redirect_policy(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => RedirectPolicy::None,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => RedirectPolicy::Limit(limit),
|
||||
http_client::RedirectPolicy::FollowAll => RedirectPolicy::Follow,
|
||||
.follow_redirects(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
::http_client::RedirectPolicy::FollowLimit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
|
||||
});
|
||||
for (key, value) in &extension_request.headers {
|
||||
request = request.header(key, value);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::http_client::AsyncBody;
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
@@ -8,7 +8,6 @@ use async_trait::async_trait;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use isahc::config::{Configurable, RedirectPolicy};
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
@@ -213,10 +212,12 @@ fn convert_request(
|
||||
let mut request = ::http_client::Request::builder()
|
||||
.method(::http_client::Method::from(extension_request.method))
|
||||
.uri(&extension_request.url)
|
||||
.redirect_policy(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => RedirectPolicy::None,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => RedirectPolicy::Limit(limit),
|
||||
http_client::RedirectPolicy::FollowAll => RedirectPolicy::Follow,
|
||||
.follow_redirects(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
::http_client::RedirectPolicy::FollowLimit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
|
||||
});
|
||||
for (key, value) in &extension_request.headers {
|
||||
request = request.header(key, value);
|
||||
|
||||
@@ -23,7 +23,6 @@ editor.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
human_bytes = "0.4.1"
|
||||
isahc.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -11,7 +11,6 @@ use gpui::{
|
||||
PromptLevel, Render, Task, View, ViewContext,
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use isahc::Request;
|
||||
use language::Buffer;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
@@ -299,7 +298,7 @@ impl FeedbackModal {
|
||||
is_staff: is_staff.unwrap_or(false),
|
||||
};
|
||||
let json_bytes = serde_json::to_vec(&request)?;
|
||||
let request = Request::post(feedback_endpoint)
|
||||
let request = http_client::http::Request::post(feedback_endpoint)
|
||||
.header("content-type", "application/json")
|
||||
.body(json_bytes.into())?;
|
||||
let mut response = http_client.send(request).await?;
|
||||
|
||||
@@ -394,7 +394,7 @@ fn matching_history_items<'a>(
|
||||
.chars(),
|
||||
),
|
||||
};
|
||||
candidates_paths.insert(Arc::clone(&found_path.project.path), found_path);
|
||||
candidates_paths.insert(&found_path.project, found_path);
|
||||
Some((found_path.project.worktree_id, candidate))
|
||||
})
|
||||
.fold(
|
||||
@@ -419,17 +419,21 @@ fn matching_history_items<'a>(
|
||||
max_results,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|path_match| {
|
||||
let (_, found_path) = candidates_paths
|
||||
.remove_entry(&path_match.path)
|
||||
.expect("candidate info not found");
|
||||
(
|
||||
Arc::clone(&path_match.path),
|
||||
Match::History {
|
||||
path: found_path.clone(),
|
||||
panel_match: Some(ProjectPanelOrdMatch(path_match)),
|
||||
},
|
||||
)
|
||||
.filter_map(|path_match| {
|
||||
candidates_paths
|
||||
.remove_entry(&ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(path_match.worktree_id),
|
||||
path: Arc::clone(&path_match.path),
|
||||
})
|
||||
.map(|(_, found_path)| {
|
||||
(
|
||||
Arc::clone(&path_match.path),
|
||||
Match::History {
|
||||
path: found_path.clone(),
|
||||
panel_match: Some(ProjectPanelOrdMatch(path_match)),
|
||||
},
|
||||
)
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::{AsyncBody, HttpClient, Request};
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
@@ -49,14 +49,16 @@ impl Codeberg {
|
||||
let url =
|
||||
format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url).header("Content-Type", "application/json");
|
||||
let mut request = Request::get(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.follow_redirects(http_client::RedirectPolicy::FollowAll);
|
||||
|
||||
if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", codeberg_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send_with_redirect_policy(request.body(AsyncBody::default())?, true)
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching Codeberg commit details at {:?}", url))?;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::{AsyncBody, HttpClient, Request};
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
@@ -53,14 +53,16 @@ impl Github {
|
||||
) -> Result<Option<User>> {
|
||||
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url).header("Content-Type", "application/json");
|
||||
let mut request = Request::get(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.follow_redirects(http_client::RedirectPolicy::FollowAll);
|
||||
|
||||
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", github_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send_with_redirect_policy(request.body(AsyncBody::default())?, true)
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching GitHub commit details at {:?}", url))?;
|
||||
|
||||
|
||||
@@ -116,12 +116,14 @@ impl GoToLine {
|
||||
if let Some(point) = self.point_from_query(cx) {
|
||||
self.active_editor.update(cx, |active_editor, cx| {
|
||||
let snapshot = active_editor.snapshot(cx).display_snapshot;
|
||||
let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
|
||||
let anchor = snapshot.buffer_snapshot.anchor_before(point);
|
||||
let start = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
|
||||
let end = start + Point::new(1, 0);
|
||||
let start = snapshot.buffer_snapshot.anchor_before(start);
|
||||
let end = snapshot.buffer_snapshot.anchor_after(end);
|
||||
active_editor.clear_row_highlights::<GoToLineRowHighlights>();
|
||||
active_editor.highlight_rows::<GoToLineRowHighlights>(
|
||||
anchor..=anchor,
|
||||
Some(cx.theme().colors().editor_highlighted_line_background),
|
||||
start..end,
|
||||
cx.theme().colors().editor_highlighted_line_background,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
@@ -244,13 +246,13 @@ mod tests {
|
||||
field_1: i32, // display line 3
|
||||
field_2: i32, // display line 4
|
||||
} // display line 5
|
||||
// display line 7
|
||||
struct Another { // display line 8
|
||||
field_1: i32, // display line 9
|
||||
field_2: i32, // display line 10
|
||||
field_3: i32, // display line 11
|
||||
field_4: i32, // display line 12
|
||||
} // display line 13
|
||||
// display line 6
|
||||
struct Another { // display line 7
|
||||
field_1: i32, // display line 8
|
||||
field_2: i32, // display line 9
|
||||
field_3: i32, // display line 10
|
||||
field_4: i32, // display line 11
|
||||
} // display line 12
|
||||
"}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@ schemars = ["dep:schemars"]
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -2,8 +2,7 @@ mod supported_countries;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use isahc::config::Configurable;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -30,7 +29,7 @@ pub async fn stream_generate_content(
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
};
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
@@ -85,7 +84,7 @@ pub async fn count_tokens(
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
|
||||
let http_request = request_builder.body(AsyncBody::from(request))?;
|
||||
|
||||
@@ -1524,10 +1524,9 @@ pub struct KeystrokeEvent {
|
||||
struct NullHttpClient;
|
||||
|
||||
impl HttpClient for NullHttpClient {
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
_req: http_client::Request<http_client::AsyncBody>,
|
||||
_follow_redirects: bool,
|
||||
) -> futures::future::BoxFuture<
|
||||
'static,
|
||||
Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>,
|
||||
|
||||
@@ -16,7 +16,7 @@ path = "src/http_client.rs"
|
||||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
http = "1.1.0"
|
||||
http = "0.2"
|
||||
anyhow.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
@@ -10,22 +10,46 @@ use futures::future::BoxFuture;
|
||||
use http::request::Builder;
|
||||
#[cfg(feature = "test-support")]
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
pub use url::Url;
|
||||
|
||||
pub struct ReadTimeout(pub Duration);
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub enum RedirectPolicy {
|
||||
#[default]
|
||||
NoFollow,
|
||||
FollowLimit(u32),
|
||||
FollowAll,
|
||||
}
|
||||
pub struct FollowRedirects(pub bool);
|
||||
|
||||
pub trait HttpRequestExt {
|
||||
/// Set a read timeout on the request.
|
||||
/// For isahc, this is the low_speed_timeout.
|
||||
/// For other clients, this is the timeout used for read calls when reading the response.
|
||||
/// In all cases this prevents servers stalling completely, but allows them to send data slowly.
|
||||
fn read_timeout(self, timeout: Duration) -> Self;
|
||||
/// Whether or not to follow redirects
|
||||
fn follow_redirects(self, follow: RedirectPolicy) -> Self;
|
||||
}
|
||||
|
||||
impl HttpRequestExt for http::request::Builder {
|
||||
fn read_timeout(self, timeout: Duration) -> Self {
|
||||
self.extension(ReadTimeout(timeout))
|
||||
}
|
||||
|
||||
fn follow_redirects(self, follow: RedirectPolicy) -> Self {
|
||||
self.extension(follow)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HttpClient: 'static + Send + Sync {
|
||||
fn send(
|
||||
&self,
|
||||
req: http::Request<AsyncBody>,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.send_with_redirect_policy(req, false)
|
||||
}
|
||||
|
||||
// TODO: Make a better API for this
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>>;
|
||||
|
||||
fn get<'a>(
|
||||
@@ -34,14 +58,17 @@ pub trait HttpClient: 'static + Send + Sync {
|
||||
body: AsyncBody,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'a, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
let request = Builder::new().uri(uri).body(body);
|
||||
let request = Builder::new()
|
||||
.uri(uri)
|
||||
.follow_redirects(if follow_redirects {
|
||||
RedirectPolicy::FollowAll
|
||||
} else {
|
||||
RedirectPolicy::NoFollow
|
||||
})
|
||||
.body(body);
|
||||
|
||||
match request {
|
||||
Ok(request) => Box::pin(async move {
|
||||
self.send_with_redirect_policy(request, follow_redirects)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}),
|
||||
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }),
|
||||
Err(e) => Box::pin(async move { Err(e.into()) }),
|
||||
}
|
||||
}
|
||||
@@ -92,12 +119,11 @@ impl HttpClientWithProxy {
|
||||
}
|
||||
|
||||
impl HttpClient for HttpClientWithProxy {
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
self.client.send(req)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -106,12 +132,11 @@ impl HttpClient for HttpClientWithProxy {
|
||||
}
|
||||
|
||||
impl HttpClient for Arc<HttpClientWithProxy> {
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
self.client.send(req)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -218,12 +243,11 @@ impl HttpClientWithUrl {
|
||||
}
|
||||
|
||||
impl HttpClient for Arc<HttpClientWithUrl> {
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
self.client.send(req)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -232,12 +256,11 @@ impl HttpClient for Arc<HttpClientWithUrl> {
|
||||
}
|
||||
|
||||
impl HttpClient for HttpClientWithUrl {
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.client.send_with_redirect_policy(req, follow_redirects)
|
||||
self.client.send(req)
|
||||
}
|
||||
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
@@ -283,14 +306,6 @@ impl HttpClient for BlockedHttpClient {
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
_: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
self.send(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
@@ -352,10 +367,9 @@ impl fmt::Debug for FakeHttpClient {
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
impl HttpClient for FakeHttpClient {
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
req: Request<AsyncBody>,
|
||||
_follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
|
||||
let future = (self.handler)(req);
|
||||
future
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use anyhow::Error;
|
||||
use futures::future::BoxFuture;
|
||||
use http::{Response, Uri};
|
||||
use hyper_util::client::legacy::{connect::HttpConnector, Client};
|
||||
|
||||
struct HyperBasedHttpClient {
|
||||
client: Client<HttpConnector, Vec<u8>>,
|
||||
}
|
||||
|
||||
struct Executor {
|
||||
executor: gpui::BackgroundExecutor,
|
||||
}
|
||||
impl impl HyperBasedHttpClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: Client::builder().build(HttpConnector::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for HyperBasedHttpClient {
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn send(
|
||||
&self,
|
||||
request: HttpRequest,
|
||||
method: &str,
|
||||
) -> BoxFuture<'static, Result<Response, Error>> {
|
||||
let request = request.into_hyper_request(method);
|
||||
Box::pin(async move {
|
||||
let response = self.client.request(request).await?;
|
||||
Ok(response.into())
|
||||
})
|
||||
}
|
||||
|
||||
fn send_response(&self, response: HttpResponse) -> BoxFuture<'static, Result<(), Error>> {
|
||||
let response = response.into_hyper_response();
|
||||
Box::pin(async move {
|
||||
let _ = self.client.request(response).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
[package]
|
||||
name = "hyper_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[lib]
|
||||
path = "src/hyper_client.rs"
|
||||
doctest = true
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
path = "examples/client.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
url.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
|
||||
ureq = "2.10.1"
|
||||
|
||||
hyper.workspace = true
|
||||
hyper-util = {workspace = true, features = ["client", "http1", "http2", "client-legacy"]}
|
||||
hyper-rustls.workspace = true
|
||||
@@ -1,20 +0,0 @@
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::{AsyncBody, HttpClient};
|
||||
use hyper_client::UreqHttpClient;
|
||||
|
||||
fn main() {
|
||||
gpui::App::headless().run(|cx| {
|
||||
dbg!(std::thread::current().id());
|
||||
cx.spawn(|cx| async move {
|
||||
let resp = UreqHttpClient::new(cx.background_executor().clone())
|
||||
.get("http://zed.dev", AsyncBody::empty(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut body = String::new();
|
||||
resp.into_body().read_to_string(&mut body).await.unwrap();
|
||||
dbg!(&body.len());
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
use std::io::Read;
|
||||
use std::{pin::Pin, task::Poll};
|
||||
|
||||
use anyhow::Error;
|
||||
use futures::channel::mpsc;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{AsyncRead, StreamExt};
|
||||
use gpui::AppContext;
|
||||
use http_client::{http, AsyncBody, HttpClient, Inner};
|
||||
use hyper::body::{Body, Bytes, Frame, Incoming, SizeHint};
|
||||
use hyper::http::{Response, Uri};
|
||||
use hyper_util::client::legacy::{connect::HttpConnector, Client};
|
||||
use smol::future::FutureExt;
|
||||
use std::future::Future;
|
||||
|
||||
pub struct UreqHttpClient {
|
||||
client: ureq::Agent,
|
||||
background_executor: gpui::BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl UreqHttpClient {
|
||||
pub fn new(background_executor: gpui::BackgroundExecutor) -> Self {
|
||||
Self {
|
||||
client: ureq::agent(),
|
||||
background_executor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UreqResponseReader {
|
||||
task: gpui::Task<()>,
|
||||
receiver: mpsc::Receiver<std::io::Result<Vec<u8>>>,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl UreqResponseReader {
|
||||
fn new(background_executor: gpui::BackgroundExecutor, response: ureq::Response) -> Self {
|
||||
let (mut sender, receiver) = mpsc::channel(1);
|
||||
let mut reader = response.into_reader();
|
||||
let task = background_executor.spawn(async move {
|
||||
let mut buffer = vec![0; 8192];
|
||||
loop {
|
||||
let n = match reader.read(&mut buffer) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
let _ = sender.try_send(Err(e));
|
||||
break;
|
||||
}
|
||||
};
|
||||
let _ = sender.try_send(Ok(buffer[..n].to_vec()));
|
||||
}
|
||||
});
|
||||
|
||||
UreqResponseReader {
|
||||
task,
|
||||
receiver,
|
||||
buffer: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for UreqResponseReader {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
let now = std::time::Instant::now();
|
||||
if self.buffer.is_empty() {
|
||||
match self.receiver.poll_next_unpin(cx) {
|
||||
Poll::Ready(Some(Ok(data))) => self.buffer.extend(data),
|
||||
Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
|
||||
Poll::Ready(None) => return Poll::Ready(Ok(0)), // EOF
|
||||
Poll::Pending => {
|
||||
dbg!(now.elapsed());
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let n = std::cmp::min(buf.len(), self.buffer.len());
|
||||
dbg!(buf.len(), self.buffer.len(), now.elapsed());
|
||||
dbg!(std::thread::current().id());
|
||||
buf[..n].copy_from_slice(&self.buffer[..n]);
|
||||
self.buffer.drain(..n);
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpClient for UreqHttpClient {
|
||||
fn proxy(&self) -> Option<&Uri> {
|
||||
None
|
||||
}
|
||||
|
||||
fn send_with_redirect_policy(
|
||||
&self,
|
||||
request: http::Request<AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<http::Response<AsyncBody>, Error>> {
|
||||
let method = request.method().clone();
|
||||
let url = request.uri().to_string();
|
||||
let headers = request.headers().clone();
|
||||
let mut req = self.client.request(method.as_str(), &url);
|
||||
for (name, value) in headers.iter() {
|
||||
req = req.set(name.as_str(), value.to_str().unwrap());
|
||||
}
|
||||
let executor = self.background_executor.clone();
|
||||
let req = executor.spawn(async move {
|
||||
let resp = req.send(request.into_body());
|
||||
dbg!(std::thread::current().id());
|
||||
resp
|
||||
});
|
||||
|
||||
// Set follow_redirects policy
|
||||
// req = req.redirects(if follow_redirects { 10 } else { 0 });
|
||||
|
||||
async move {
|
||||
// Set headers
|
||||
// Send the request
|
||||
let response = req.await?;
|
||||
dbg!(std::thread::current().id());
|
||||
|
||||
// Convert ureq response to http::Response
|
||||
let mut builder = http::Response::builder()
|
||||
.status(response.status())
|
||||
.version(http::Version::HTTP_11);
|
||||
|
||||
// Set response headers
|
||||
for name in response.headers_names() {
|
||||
if let Some(value) = response.header(&name) {
|
||||
builder = builder.header(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
let body = AsyncBody::from_reader(UreqResponseReader::new(executor, response));
|
||||
let http_response = builder.body(body)?;
|
||||
|
||||
Ok(http_response)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::{mem, sync::Arc, time::Duration};
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use isahc::config::RedirectPolicy;
|
||||
use util::maybe;
|
||||
|
||||
pub use isahc::config::Configurable;
|
||||
@@ -36,18 +35,29 @@ impl HttpClient for IsahcHttpClient {
|
||||
None
|
||||
}
|
||||
|
||||
fn send_with_redirect_policy(
|
||||
fn send(
|
||||
&self,
|
||||
req: http_client::http::Request<http_client::AsyncBody>,
|
||||
follow_redirects: bool,
|
||||
) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
|
||||
{
|
||||
let redirect_policy = req
|
||||
.extensions()
|
||||
.get::<http_client::RedirectPolicy>()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let read_timeout = req
|
||||
.extensions()
|
||||
.get::<http_client::ReadTimeout>()
|
||||
.map(|t| t.0);
|
||||
let req = maybe!({
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let mut builder = isahc::Request::builder()
|
||||
.method(parts.method)
|
||||
.uri(parts.uri)
|
||||
.version(parts.version);
|
||||
if let Some(read_timeout) = read_timeout {
|
||||
builder = builder.low_speed_timeout(100, read_timeout);
|
||||
}
|
||||
|
||||
let headers = builder.headers_mut()?;
|
||||
mem::swap(headers, &mut parts.headers);
|
||||
@@ -64,10 +74,12 @@ impl HttpClient for IsahcHttpClient {
|
||||
};
|
||||
|
||||
builder
|
||||
.redirect_policy(if follow_redirects {
|
||||
RedirectPolicy::Follow
|
||||
} else {
|
||||
RedirectPolicy::None
|
||||
.redirect_policy(match redirect_policy {
|
||||
http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
isahc::config::RedirectPolicy::Limit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None,
|
||||
})
|
||||
.body(isahc_body)
|
||||
.ok()
|
||||
|
||||
@@ -87,7 +87,11 @@ pub type BufferRow = u32;
|
||||
#[derive(Clone)]
|
||||
enum BufferDiffBase {
|
||||
Git(Rope),
|
||||
PastBufferVersion(Model<Buffer>, BufferSnapshot),
|
||||
PastBufferVersion {
|
||||
buffer: Model<Buffer>,
|
||||
rope: Rope,
|
||||
operations_to_ignore: Vec<clock::Lamport>,
|
||||
},
|
||||
}
|
||||
|
||||
/// An in-memory representation of a source code file, including its text,
|
||||
@@ -795,19 +799,15 @@ impl Buffer {
|
||||
let this = cx.handle();
|
||||
cx.new_model(|cx| {
|
||||
let mut branch = Self {
|
||||
diff_base: Some(BufferDiffBase::PastBufferVersion(
|
||||
this.clone(),
|
||||
self.snapshot(),
|
||||
)),
|
||||
diff_base: Some(BufferDiffBase::PastBufferVersion {
|
||||
buffer: this.clone(),
|
||||
rope: self.as_rope().clone(),
|
||||
operations_to_ignore: Vec::new(),
|
||||
}),
|
||||
language: self.language.clone(),
|
||||
has_conflict: self.has_conflict,
|
||||
has_unsaved_edits: Cell::new(self.has_unsaved_edits.get_mut().clone()),
|
||||
_subscriptions: vec![cx.subscribe(&this, |branch: &mut Self, _, event, cx| {
|
||||
if let BufferEvent::Operation { operation, .. } = event {
|
||||
branch.apply_ops([operation.clone()], cx);
|
||||
branch.diff_base_version += 1;
|
||||
}
|
||||
})],
|
||||
_subscriptions: vec![cx.subscribe(&this, Self::on_base_buffer_event)],
|
||||
..Self::build(
|
||||
self.text.branch(),
|
||||
None,
|
||||
@@ -823,18 +823,74 @@ impl Buffer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, branch: &Model<Self>, cx: &mut ModelContext<Self>) {
|
||||
let branch = branch.read(cx);
|
||||
let edits = branch
|
||||
.edits_since::<usize>(&self.version)
|
||||
.map(|edit| {
|
||||
(
|
||||
edit.old,
|
||||
branch.text_for_range(edit.new).collect::<String>(),
|
||||
/// Applies all of the changes in `branch` buffer that intersect the given `range`
|
||||
/// to this buffer.
|
||||
pub fn merge(
|
||||
&mut self,
|
||||
branch: &Model<Self>,
|
||||
range: Option<Range<Anchor>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let edits = branch.read_with(cx, |branch, _| {
|
||||
branch
|
||||
.edits_since_in_range::<usize>(
|
||||
&self.version,
|
||||
range.unwrap_or(Anchor::MIN..Anchor::MAX),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.edit(edits, None, cx);
|
||||
.map(|edit| {
|
||||
(
|
||||
edit.old,
|
||||
branch.text_for_range(edit.new).collect::<String>(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let operation = self.edit(edits, None, cx);
|
||||
|
||||
// Prevent this operation from being reapplied to the branch.
|
||||
branch.update(cx, |branch, cx| {
|
||||
if let Some(BufferDiffBase::PastBufferVersion {
|
||||
operations_to_ignore,
|
||||
..
|
||||
}) = &mut branch.diff_base
|
||||
{
|
||||
operations_to_ignore.extend(operation);
|
||||
}
|
||||
cx.emit(BufferEvent::Edited)
|
||||
});
|
||||
}
|
||||
|
||||
fn on_base_buffer_event(
|
||||
&mut self,
|
||||
_: Model<Buffer>,
|
||||
event: &BufferEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let BufferEvent::Operation { operation, .. } = event {
|
||||
if let Some(BufferDiffBase::PastBufferVersion {
|
||||
operations_to_ignore,
|
||||
..
|
||||
}) = &mut self.diff_base
|
||||
{
|
||||
let mut is_ignored = false;
|
||||
if let Operation::Buffer(text::Operation::Edit(buffer_operation)) = &operation {
|
||||
operations_to_ignore.retain(|operation_to_ignore| {
|
||||
match buffer_operation.timestamp.cmp(&operation_to_ignore) {
|
||||
Ordering::Less => true,
|
||||
Ordering::Equal => {
|
||||
is_ignored = true;
|
||||
false
|
||||
}
|
||||
Ordering::Greater => false,
|
||||
}
|
||||
});
|
||||
}
|
||||
if !is_ignored {
|
||||
self.apply_ops([operation.clone()], cx);
|
||||
self.diff_base_version += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1017,9 +1073,8 @@ impl Buffer {
|
||||
/// Returns the current diff base, see [Buffer::set_diff_base].
|
||||
pub fn diff_base(&self) -> Option<&Rope> {
|
||||
match self.diff_base.as_ref()? {
|
||||
BufferDiffBase::Git(rope) => Some(rope),
|
||||
BufferDiffBase::PastBufferVersion(_, buffer_snapshot) => {
|
||||
Some(buffer_snapshot.as_rope())
|
||||
BufferDiffBase::Git(rope) | BufferDiffBase::PastBufferVersion { rope, .. } => {
|
||||
Some(rope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1050,29 +1105,36 @@ impl Buffer {
|
||||
self.diff_base_version
|
||||
}
|
||||
|
||||
pub fn diff_base_buffer(&self) -> Option<Model<Self>> {
|
||||
match self.diff_base.as_ref()? {
|
||||
BufferDiffBase::Git(_) => None,
|
||||
BufferDiffBase::PastBufferVersion { buffer, .. } => Some(buffer.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Recomputes the diff.
|
||||
pub fn recalculate_diff(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
|
||||
let diff_base_rope = match self.diff_base.as_mut()? {
|
||||
let diff_base_rope = match self.diff_base.as_ref()? {
|
||||
BufferDiffBase::Git(rope) => rope.clone(),
|
||||
BufferDiffBase::PastBufferVersion(base_buffer, base_buffer_snapshot) => {
|
||||
let new_base_snapshot = base_buffer.read(cx).snapshot();
|
||||
*base_buffer_snapshot = new_base_snapshot;
|
||||
base_buffer_snapshot.as_rope().clone()
|
||||
}
|
||||
BufferDiffBase::PastBufferVersion { buffer, .. } => buffer.read(cx).as_rope().clone(),
|
||||
};
|
||||
let snapshot = self.snapshot();
|
||||
|
||||
let snapshot = self.snapshot();
|
||||
let mut diff = self.git_diff.clone();
|
||||
let diff = cx.background_executor().spawn(async move {
|
||||
diff.update(&diff_base_rope, &snapshot).await;
|
||||
diff
|
||||
(diff, diff_base_rope)
|
||||
});
|
||||
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
let buffer_diff = diff.await;
|
||||
let (buffer_diff, diff_base_rope) = diff.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.git_diff = buffer_diff;
|
||||
this.non_text_state_update_count += 1;
|
||||
if let Some(BufferDiffBase::PastBufferVersion { rope, .. }) = &mut this.diff_base {
|
||||
*rope = diff_base_rope;
|
||||
cx.emit(BufferEvent::DiffBaseChanged);
|
||||
}
|
||||
cx.emit(BufferEvent::DiffUpdated);
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -2413,80 +2413,98 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Edits to the branch are not applied to the base.
|
||||
branch_buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[(Point::new(1, 0)..Point::new(1, 0), "ONE_POINT_FIVE\n")],
|
||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
||||
branch_buffer.edit(
|
||||
[
|
||||
(Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
|
||||
(Point::new(2, 0)..Point::new(2, 5), "THREE"),
|
||||
],
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
||||
assert_eq!(base_buffer.read(cx).text(), "one\ntwo\nthree\n");
|
||||
assert_eq!(branch_buffer.text(), "one\nONE_POINT_FIVE\ntwo\nthree\n");
|
||||
assert_eq!(branch_buffer.text(), "one\n1.5\ntwo\nTHREE\n");
|
||||
});
|
||||
|
||||
// The branch buffer maintains a diff with respect to its base buffer.
|
||||
start_recalculating_diff(&branch_buffer, cx);
|
||||
cx.run_until_parked();
|
||||
assert_diff_hunks(
|
||||
&branch_buffer,
|
||||
cx,
|
||||
&[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
|
||||
);
|
||||
|
||||
// Edits to the base are applied to the branch.
|
||||
base_buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
|
||||
});
|
||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
||||
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\nthree\n");
|
||||
assert_eq!(
|
||||
branch_buffer.text(),
|
||||
"ZERO\none\nONE_POINT_FIVE\ntwo\nthree\n"
|
||||
);
|
||||
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
|
||||
});
|
||||
|
||||
assert_diff_hunks(&branch_buffer, cx, &[(2..3, "", "ONE_POINT_FIVE\n")]);
|
||||
// Until the git diff recalculation is complete, the git diff references
|
||||
// the previous content of the base buffer, so that it stays in sync.
|
||||
start_recalculating_diff(&branch_buffer, cx);
|
||||
assert_diff_hunks(
|
||||
&branch_buffer,
|
||||
cx,
|
||||
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
||||
);
|
||||
cx.run_until_parked();
|
||||
assert_diff_hunks(
|
||||
&branch_buffer,
|
||||
cx,
|
||||
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
|
||||
);
|
||||
|
||||
// Edits to any replica of the base are applied to the branch.
|
||||
base_buffer_replica.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[(Point::new(2, 0)..Point::new(2, 0), "TWO_POINT_FIVE\n")],
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
|
||||
});
|
||||
branch_buffer.read_with(cx, |branch_buffer, cx| {
|
||||
assert_eq!(
|
||||
base_buffer.read(cx).text(),
|
||||
"ZERO\none\ntwo\nTWO_POINT_FIVE\nthree\n"
|
||||
);
|
||||
assert_eq!(
|
||||
branch_buffer.text(),
|
||||
"ZERO\none\nONE_POINT_FIVE\ntwo\nTWO_POINT_FIVE\nthree\n"
|
||||
);
|
||||
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
|
||||
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||
});
|
||||
|
||||
// Merging the branch applies all of its changes to the base.
|
||||
base_buffer.update(cx, |base_buffer, cx| {
|
||||
base_buffer.merge(&branch_buffer, cx);
|
||||
base_buffer.merge(&branch_buffer, None, cx);
|
||||
});
|
||||
|
||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
||||
assert_eq!(
|
||||
base_buffer.text(),
|
||||
"ZERO\none\nONE_POINT_FIVE\ntwo\nTWO_POINT_FIVE\nthree\n"
|
||||
base_buffer.read(cx).text(),
|
||||
"ZERO\none\n1.5\ntwo\n2.5\nTHREE\n"
|
||||
);
|
||||
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
|
||||
});
|
||||
}
|
||||
|
||||
fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
|
||||
buffer
|
||||
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
|
||||
.detach();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_diff_hunks(
|
||||
buffer: &Model<Buffer>,
|
||||
cx: &mut TestAppContext,
|
||||
expected_hunks: &[(Range<u32>, &str, &str)],
|
||||
) {
|
||||
buffer
|
||||
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
|
||||
.detach();
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
assert_hunks(
|
||||
snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
|
||||
&snapshot,
|
||||
&buffer.diff_base().unwrap().to_string(),
|
||||
expected_hunks,
|
||||
);
|
||||
let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
|
||||
(buffer.snapshot(), buffer.diff_base().unwrap().to_string())
|
||||
});
|
||||
assert_hunks(
|
||||
snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
|
||||
&snapshot,
|
||||
&diff_base,
|
||||
expected_hunks,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
|
||||
@@ -113,6 +113,9 @@ pub struct LanguageSettings {
|
||||
pub use_autoclose: bool,
|
||||
/// Whether to automatically surround text with brackets.
|
||||
pub use_auto_surround: bool,
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
pub use_on_type_format: bool,
|
||||
// Controls how the editor handles the autoclosed characters.
|
||||
pub always_treat_brackets_as_autoclosed: bool,
|
||||
/// Which code actions to run on save
|
||||
@@ -333,6 +336,11 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub always_treat_brackets_as_autoclosed: Option<bool>,
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
///
|
||||
/// Default: true
|
||||
pub use_on_type_format: Option<bool>,
|
||||
/// Which code actions to run on save after the formatter.
|
||||
/// These are not run if formatting is off.
|
||||
///
|
||||
@@ -371,15 +379,16 @@ pub struct FeaturesContent {
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SoftWrap {
|
||||
/// Do not soft wrap.
|
||||
/// Prefer a single line generally, unless an overly long line is encountered.
|
||||
None,
|
||||
/// Deprecated: use None instead. Left to avoid breakin existing users' configs.
|
||||
/// Prefer a single line generally, unless an overly long line is encountered.
|
||||
PreferLine,
|
||||
/// Soft wrap lines that exceed the editor width
|
||||
/// Soft wrap lines that exceed the editor width.
|
||||
EditorWidth,
|
||||
/// Soft wrap lines at the preferred line length
|
||||
/// Soft wrap lines at the preferred line length.
|
||||
PreferredLineLength,
|
||||
/// Soft wrap line at the preferred line length or the editor width (whichever is smaller)
|
||||
/// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
|
||||
Bounded,
|
||||
}
|
||||
|
||||
@@ -1045,6 +1054,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
||||
merge(&mut settings.soft_wrap, src.soft_wrap);
|
||||
merge(&mut settings.use_autoclose, src.use_autoclose);
|
||||
merge(&mut settings.use_auto_surround, src.use_auto_surround);
|
||||
merge(&mut settings.use_on_type_format, src.use_on_type_format);
|
||||
merge(
|
||||
&mut settings.always_treat_brackets_as_autoclosed,
|
||||
src.always_treat_brackets_as_autoclosed,
|
||||
|
||||
@@ -32,7 +32,6 @@ futures.workspace = true
|
||||
google_ai = { workspace = true, features = ["schemars"] }
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
inline_completion_button.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -12,7 +12,6 @@ pub enum CloudModel {
|
||||
Anthropic(anthropic::Model),
|
||||
OpenAi(open_ai::Model),
|
||||
Google(google_ai::Model),
|
||||
Zed(ZedModel),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, EnumIter)]
|
||||
@@ -21,26 +20,6 @@ pub enum ZedModel {
|
||||
Qwen2_7bInstruct,
|
||||
}
|
||||
|
||||
impl ZedModel {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
ZedModel::Qwen2_7bInstruct => "Qwen/Qwen2-7B-Instruct",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &str {
|
||||
match self {
|
||||
ZedModel::Qwen2_7bInstruct => "Qwen2 7B Instruct",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
ZedModel::Qwen2_7bInstruct => 28000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CloudModel {
|
||||
fn default() -> Self {
|
||||
Self::Anthropic(anthropic::Model::default())
|
||||
@@ -53,7 +32,6 @@ impl CloudModel {
|
||||
Self::Anthropic(model) => model.id(),
|
||||
Self::OpenAi(model) => model.id(),
|
||||
Self::Google(model) => model.id(),
|
||||
Self::Zed(model) => model.id(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +40,6 @@ impl CloudModel {
|
||||
Self::Anthropic(model) => model.display_name(),
|
||||
Self::OpenAi(model) => model.display_name(),
|
||||
Self::Google(model) => model.display_name(),
|
||||
Self::Zed(model) => model.display_name(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +55,6 @@ impl CloudModel {
|
||||
Self::Anthropic(model) => model.max_token_count(),
|
||||
Self::OpenAi(model) => model.max_token_count(),
|
||||
Self::Google(model) => model.max_token_count(),
|
||||
Self::Zed(model) => model.max_token_count(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +91,6 @@ impl CloudModel {
|
||||
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
|
||||
}
|
||||
},
|
||||
Self::Zed(model) => match model {
|
||||
ZedModel::Qwen2_7bInstruct => LanguageModelAvailability::RequiresPlan(Plan::ZedPro),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::provider::anthropic::map_to_language_model_completion_events;
|
||||
use crate::{
|
||||
settings::AllLanguageModelSettings, CloudModel, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, RateLimiter, ZedModel,
|
||||
LanguageModelProviderState, LanguageModelRequest, RateLimiter,
|
||||
};
|
||||
use anthropic::AnthropicError;
|
||||
use anyhow::{anyhow, Result};
|
||||
@@ -18,8 +18,7 @@ use gpui::{
|
||||
AnyElement, AnyView, AppContext, AsyncAppContext, FontWeight, Model, ModelContext,
|
||||
Subscription, Task,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Response};
|
||||
use isahc::config::Configurable;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Response};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
@@ -220,9 +219,6 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
|
||||
models.insert(model.id().to_string(), CloudModel::Google(model));
|
||||
}
|
||||
}
|
||||
for model in ZedModel::iter() {
|
||||
models.insert(model.id().to_string(), CloudModel::Zed(model));
|
||||
}
|
||||
} else {
|
||||
models.insert(
|
||||
anthropic::Model::Claude3_5Sonnet.id().to_string(),
|
||||
@@ -396,7 +392,7 @@ impl CloudLanguageModel {
|
||||
let response = loop {
|
||||
let mut request_builder = http_client::Request::builder();
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
};
|
||||
let request = request_builder
|
||||
.method(Method::POST)
|
||||
@@ -473,7 +469,7 @@ impl LanguageModel for CloudLanguageModel {
|
||||
min_total_token: cache.min_total_token,
|
||||
})
|
||||
}
|
||||
CloudModel::OpenAi(_) | CloudModel::Google(_) | CloudModel::Zed(_) => None,
|
||||
CloudModel::OpenAi(_) | CloudModel::Google(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,9 +499,6 @@ impl LanguageModel for CloudLanguageModel {
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
CloudModel::Zed(_) => {
|
||||
count_open_ai_tokens(request, open_ai::Model::ThreePointFiveTurbo, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,35 +597,6 @@ impl LanguageModel for CloudLanguageModel {
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
CloudModel::Zed(model) => {
|
||||
let client = self.client.clone();
|
||||
let mut request = request.into_open_ai(model.id().into(), None);
|
||||
request.max_tokens = Some(4000);
|
||||
let llm_api_token = self.llm_api_token.clone();
|
||||
let future = self.request_limiter.stream(async move {
|
||||
let response = Self::perform_llm_completion(
|
||||
client.clone(),
|
||||
llm_api_token,
|
||||
PerformCompletionParams {
|
||||
provider: client::LanguageModelProvider::Zed,
|
||||
model: request.model.clone(),
|
||||
provider_request: RawValue::from_string(serde_json::to_string(
|
||||
&request,
|
||||
)?)?,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(open_ai::extract_text_from_events(response_lines(response)))
|
||||
});
|
||||
async move {
|
||||
Ok(future
|
||||
.await?
|
||||
.map(|result| result.map(LanguageModelCompletionEvent::Text))
|
||||
.boxed())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,51 +700,6 @@ impl LanguageModel for CloudLanguageModel {
|
||||
CloudModel::Google(_) => {
|
||||
future::ready(Err(anyhow!("tool use not implemented for Google AI"))).boxed()
|
||||
}
|
||||
CloudModel::Zed(model) => {
|
||||
// All Zed models are OpenAI-based at the time of writing.
|
||||
let mut request = request.into_open_ai(model.id().into(), None);
|
||||
request.tool_choice = Some(open_ai::ToolChoice::Other(
|
||||
open_ai::ToolDefinition::Function {
|
||||
function: open_ai::FunctionDefinition {
|
||||
name: tool_name.clone(),
|
||||
description: None,
|
||||
parameters: None,
|
||||
},
|
||||
},
|
||||
));
|
||||
request.tools = vec![open_ai::ToolDefinition::Function {
|
||||
function: open_ai::FunctionDefinition {
|
||||
name: tool_name.clone(),
|
||||
description: Some(tool_description),
|
||||
parameters: Some(input_schema),
|
||||
},
|
||||
}];
|
||||
|
||||
self.request_limiter
|
||||
.run(async move {
|
||||
let response = Self::perform_llm_completion(
|
||||
client.clone(),
|
||||
llm_api_token,
|
||||
PerformCompletionParams {
|
||||
provider: client::LanguageModelProvider::Zed,
|
||||
model: request.model.clone(),
|
||||
provider_request: RawValue::from_string(serde_json::to_string(
|
||||
&request,
|
||||
)?)?,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(open_ai::extract_tool_args_from_events(
|
||||
tool_name,
|
||||
Box::pin(response_lines(response)),
|
||||
)
|
||||
.await?
|
||||
.boxed())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,25 @@ workspace = true
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
load-grammars = [
|
||||
"tree-sitter-bash",
|
||||
"tree-sitter-c",
|
||||
"tree-sitter-cpp",
|
||||
"tree-sitter-css",
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-go-mod",
|
||||
"tree-sitter-gowork",
|
||||
"tree-sitter-jsdoc",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-md",
|
||||
"protols-tree-sitter-proto",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-regex",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-yaml",
|
||||
"tree-sitter"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -36,25 +55,26 @@ settings.workspace = true
|
||||
smol.workspace = true
|
||||
task.workspace = true
|
||||
toml.workspace = true
|
||||
tree-sitter-bash.workspace = true
|
||||
tree-sitter-c.workspace = true
|
||||
tree-sitter-cpp.workspace = true
|
||||
tree-sitter-css.workspace = true
|
||||
tree-sitter-go.workspace = true
|
||||
tree-sitter-go-mod.workspace = true
|
||||
tree-sitter-gowork.workspace = true
|
||||
tree-sitter-jsdoc.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
protols-tree-sitter-proto.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
tree-sitter-regex.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-yaml.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
tree-sitter-bash = {workspace = true, optional = true}
|
||||
tree-sitter-c = {workspace = true, optional = true}
|
||||
tree-sitter-cpp = {workspace = true, optional = true}
|
||||
tree-sitter-css = {workspace = true, optional = true}
|
||||
tree-sitter-go = {workspace = true, optional = true}
|
||||
tree-sitter-go-mod = {workspace = true, optional = true}
|
||||
tree-sitter-gowork = {workspace = true, optional = true}
|
||||
tree-sitter-jsdoc = {workspace = true, optional = true}
|
||||
tree-sitter-json = {workspace = true, optional = true}
|
||||
tree-sitter-md = {workspace = true, optional = true}
|
||||
protols-tree-sitter-proto = {workspace = true, optional = true}
|
||||
tree-sitter-python = {workspace = true, optional = true}
|
||||
tree-sitter-regex = {workspace = true, optional = true}
|
||||
tree-sitter-rust = {workspace = true, optional = true}
|
||||
tree-sitter-typescript = {workspace = true, optional = true}
|
||||
tree-sitter-yaml = {workspace = true, optional = true}
|
||||
tree-sitter = {workspace = true, optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
text.workspace = true
|
||||
theme = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -31,6 +31,7 @@ mod yaml;
|
||||
struct LanguageDir;
|
||||
|
||||
pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut AppContext) {
|
||||
#[cfg(feature = "load-grammars")]
|
||||
languages.register_native_grammars([
|
||||
("bash", tree_sitter_bash::LANGUAGE),
|
||||
("c", tree_sitter_c::LANGUAGE),
|
||||
@@ -282,9 +283,21 @@ fn load_config(name: &str) -> LanguageConfig {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
::toml::from_str(&config_toml)
|
||||
#[allow(unused_mut)]
|
||||
let mut config: LanguageConfig = ::toml::from_str(&config_toml)
|
||||
.with_context(|| format!("failed to load config.toml for language {name:?}"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(feature = "load-grammars"))]
|
||||
{
|
||||
config = LanguageConfig {
|
||||
name: config.name,
|
||||
matcher: config.matcher,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn load_queries(name: &str) -> LanguageQueries {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
|
||||
use http_client::{http, AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use http_client::{http, AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{value::RawValue, Value};
|
||||
@@ -83,7 +83,7 @@ fn get_max_tokens(name: &str) -> usize {
|
||||
"codellama" | "starcoder2" => 16384,
|
||||
"mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "dolphin-mixtral" => 32768,
|
||||
"llama3.1" | "phi3" | "phi3.5" | "command-r" | "deepseek-coder-v2" | "yi-coder"
|
||||
| "qwen2.5-coder" => 128000,
|
||||
| "llama3.2" | "qwen2.5-coder" => 128000,
|
||||
_ => DEFAULT_TOKENS,
|
||||
}
|
||||
.clamp(1, MAXIMUM_TOKENS)
|
||||
@@ -262,14 +262,18 @@ pub async fn stream_chat_completion(
|
||||
client: &dyn HttpClient,
|
||||
api_url: &str,
|
||||
request: ChatRequest,
|
||||
_: Option<Duration>,
|
||||
low_speed_timeout: Option<Duration>,
|
||||
) -> Result<BoxStream<'static, Result<ChatResponseDelta>>> {
|
||||
let uri = format!("{api_url}/api/chat");
|
||||
let request_builder = http::Request::builder()
|
||||
let mut request_builder = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let mut response = client.send(request).await?;
|
||||
if response.status().is_success() {
|
||||
|
||||
@@ -19,7 +19,6 @@ schemars = ["dep:schemars"]
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -6,8 +6,7 @@ use futures::{
|
||||
stream::{self, BoxStream},
|
||||
AsyncBufReadExt, AsyncReadExt, Stream, StreamExt,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use isahc::config::Configurable;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
@@ -318,7 +317,7 @@ pub async fn complete(
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key));
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
};
|
||||
|
||||
let mut request_body = request;
|
||||
@@ -413,7 +412,7 @@ pub async fn stream_completion(
|
||||
.header("Authorization", format!("Bearer {}", api_key));
|
||||
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
};
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
|
||||
@@ -143,8 +143,8 @@ impl OutlineViewDelegate {
|
||||
self.active_editor.update(cx, |active_editor, cx| {
|
||||
active_editor.clear_row_highlights::<OutlineRowHighlights>();
|
||||
active_editor.highlight_rows::<OutlineRowHighlights>(
|
||||
outline_item.range.start..=outline_item.range.end,
|
||||
Some(cx.theme().colors().editor_highlighted_line_background),
|
||||
outline_item.range.start..outline_item.range.end,
|
||||
cx.theme().colors().editor_highlighted_line_background,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
@@ -240,12 +240,12 @@ impl PickerDelegate for OutlineViewDelegate {
|
||||
self.prev_scroll_position.take();
|
||||
|
||||
self.active_editor.update(cx, |active_editor, cx| {
|
||||
if let Some(rows) = active_editor
|
||||
let highlight = active_editor
|
||||
.highlighted_rows::<OutlineRowHighlights>()
|
||||
.and_then(|highlights| highlights.into_iter().next().map(|(rows, _)| rows.clone()))
|
||||
{
|
||||
.next();
|
||||
if let Some((rows, _)) = highlight {
|
||||
active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
|
||||
s.select_ranges([*rows.start()..*rows.start()])
|
||||
s.select_ranges([rows.start..rows.start])
|
||||
});
|
||||
active_editor.clear_row_highlights::<OutlineRowHighlights>();
|
||||
active_editor.focus(cx);
|
||||
|
||||
@@ -36,10 +36,10 @@ use language::{
|
||||
markdown, point_to_lsp, prepare_completion_documentation,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
|
||||
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig,
|
||||
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus,
|
||||
LanguageServerName, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
|
||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
|
||||
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
|
||||
@@ -53,7 +53,7 @@ use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
|
||||
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient};
|
||||
use rpc::AnyProtoClient;
|
||||
use serde::Serialize;
|
||||
use settings::{Settings, SettingsLocation, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -644,16 +644,15 @@ pub struct RemoteLspStore {
|
||||
|
||||
impl RemoteLspStore {}
|
||||
|
||||
pub struct SshLspStore {
|
||||
upstream_client: AnyProtoClient,
|
||||
current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
|
||||
}
|
||||
// pub struct SshLspStore {
|
||||
// upstream_client: AnyProtoClient,
|
||||
// current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
|
||||
// }
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum LspStoreMode {
|
||||
Local(LocalLspStore), // ssh host and collab host
|
||||
Remote(RemoteLspStore), // collab guest
|
||||
Ssh(SshLspStore), // ssh client
|
||||
}
|
||||
|
||||
impl LspStoreMode {
|
||||
@@ -661,10 +660,6 @@ impl LspStoreMode {
|
||||
matches!(self, LspStoreMode::Local(_))
|
||||
}
|
||||
|
||||
fn is_ssh(&self) -> bool {
|
||||
matches!(self, LspStoreMode::Ssh(_))
|
||||
}
|
||||
|
||||
fn is_remote(&self) -> bool {
|
||||
matches!(self, LspStoreMode::Remote(_))
|
||||
}
|
||||
@@ -787,13 +782,6 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ssh(&self) -> Option<&SshLspStore> {
|
||||
match &self.mode {
|
||||
LspStoreMode::Ssh(ssh_lsp_store) => Some(ssh_lsp_store),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_local(&self) -> Option<&LocalLspStore> {
|
||||
match &self.mode {
|
||||
LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
|
||||
@@ -810,9 +798,6 @@ impl LspStore {
|
||||
|
||||
pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
|
||||
match &self.mode {
|
||||
LspStoreMode::Ssh(SshLspStore {
|
||||
upstream_client, ..
|
||||
}) => Some((upstream_client.clone(), SSH_PROJECT_ID)),
|
||||
LspStoreMode::Remote(RemoteLspStore {
|
||||
upstream_client,
|
||||
upstream_project_id,
|
||||
@@ -827,11 +812,7 @@ impl LspStore {
|
||||
new_settings: HashMap<LanguageServerName, LspSettings>,
|
||||
) -> Option<HashMap<LanguageServerName, LspSettings>> {
|
||||
match &mut self.mode {
|
||||
LspStoreMode::Ssh(SshLspStore {
|
||||
current_lsp_settings,
|
||||
..
|
||||
})
|
||||
| LspStoreMode::Local(LocalLspStore {
|
||||
LspStoreMode::Local(LocalLspStore {
|
||||
current_lsp_settings,
|
||||
..
|
||||
}) => {
|
||||
@@ -919,43 +900,6 @@ impl LspStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_ssh(
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
upstream_client: AnyProtoClient,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||
.detach();
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
mode: LspStoreMode::Ssh(SshLspStore {
|
||||
upstream_client,
|
||||
current_lsp_settings: Default::default(),
|
||||
}),
|
||||
downstream_client: None,
|
||||
buffer_store,
|
||||
worktree_store,
|
||||
languages: languages.clone(),
|
||||
language_server_ids: Default::default(),
|
||||
language_server_statuses: Default::default(),
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
buffer_snapshots: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
|
||||
diagnostics: Default::default(),
|
||||
active_entry: None,
|
||||
_maintain_workspace_config: Self::maintain_workspace_config(cx),
|
||||
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remote(
|
||||
buffer_store: Model<BufferStore>,
|
||||
worktree_store: Model<WorktreeStore>,
|
||||
@@ -2948,11 +2892,27 @@ impl LspStore {
|
||||
let file = File::from_dyn(buffer.read(cx).file())?;
|
||||
let worktree_id = file.worktree_id(cx);
|
||||
let abs_path = file.as_local()?.abs_path(cx);
|
||||
let worktree_path = file.as_local()?.path();
|
||||
let text_document = lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(abs_path).log_err()?,
|
||||
};
|
||||
|
||||
let watched_paths_for_server = &self.as_local()?.language_server_watched_paths;
|
||||
for server in self.language_servers_for_worktree(worktree_id) {
|
||||
let should_notify = maybe!({
|
||||
Some(
|
||||
watched_paths_for_server
|
||||
.get(&server.server_id())?
|
||||
.read(cx)
|
||||
.worktree_paths
|
||||
.get(&worktree_id)?
|
||||
.is_match(worktree_path),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if !should_notify {
|
||||
continue;
|
||||
}
|
||||
if let Some(include_text) = include_text(server.as_ref()) {
|
||||
let text = if include_text {
|
||||
Some(buffer.read(cx).text())
|
||||
@@ -3697,11 +3657,11 @@ impl LspStore {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::MultiLspQueryResponse> {
|
||||
let response_from_ssh = this.update(&mut cx, |this, _| {
|
||||
let ssh = this.as_ssh()?;
|
||||
let (upstream_client, project_id) = this.upstream_client()?;
|
||||
let mut payload = envelope.payload.clone();
|
||||
payload.project_id = SSH_PROJECT_ID;
|
||||
payload.project_id = project_id;
|
||||
|
||||
Some(ssh.upstream_client.request(payload))
|
||||
Some(upstream_client.request(payload))
|
||||
})?;
|
||||
if let Some(response_from_ssh) = response_from_ssh {
|
||||
return response_from_ssh.await;
|
||||
@@ -5009,165 +4969,6 @@ impl LspStore {
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
pub async fn handle_create_language_server(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::CreateLanguageServer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let server_name = LanguageServerName::from_proto(envelope.payload.name);
|
||||
|
||||
let binary = envelope
|
||||
.payload
|
||||
.binary
|
||||
.ok_or_else(|| anyhow!("missing binary"))?;
|
||||
let binary = LanguageServerBinary {
|
||||
path: PathBuf::from(binary.path),
|
||||
env: None,
|
||||
arguments: binary.arguments.into_iter().map(Into::into).collect(),
|
||||
};
|
||||
let language = envelope
|
||||
.payload
|
||||
.language
|
||||
.ok_or_else(|| anyhow!("missing language"))?;
|
||||
let language_name = LanguageName::from_proto(language.name);
|
||||
let matcher: LanguageMatcher = serde_json::from_str(&language.matcher)?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let Some(worktree) = this
|
||||
.worktree_store
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
else {
|
||||
return Err(anyhow!("worktree not found"));
|
||||
};
|
||||
|
||||
this.languages
|
||||
.register_language(language_name.clone(), None, matcher.clone(), {
|
||||
let language_name = language_name.clone();
|
||||
move || {
|
||||
Ok((
|
||||
LanguageConfig {
|
||||
name: language_name.clone(),
|
||||
matcher: matcher.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
});
|
||||
cx.background_executor()
|
||||
.spawn(this.languages.language_for_name(language_name.0.as_ref()))
|
||||
.detach();
|
||||
|
||||
// host
|
||||
let adapter = this.languages.get_or_register_lsp_adapter(
|
||||
language_name.clone(),
|
||||
server_name.clone(),
|
||||
|| {
|
||||
Arc::new(SshLspAdapter::new(
|
||||
server_name,
|
||||
binary,
|
||||
envelope.payload.initialization_options,
|
||||
envelope.payload.code_action_kinds,
|
||||
))
|
||||
},
|
||||
);
|
||||
|
||||
this.start_language_server(&worktree, adapter, language_name, cx);
|
||||
Ok(())
|
||||
})??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
pub async fn handle_which_command(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::WhichCommand>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::WhichCommandResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let command = PathBuf::from(envelope.payload.command);
|
||||
let response = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let worktree = this.worktree_for_id(worktree_id, cx)?;
|
||||
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
|
||||
anyhow::Ok(
|
||||
cx.spawn(|_, _| async move { delegate.which(command.as_os_str()).await }),
|
||||
)
|
||||
})??
|
||||
.await;
|
||||
|
||||
Ok(proto::WhichCommandResponse {
|
||||
path: response.map(|path| path.to_string_lossy().to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn handle_shell_env(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ShellEnv>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ShellEnvResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let response = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let worktree = this.worktree_for_id(worktree_id, cx)?;
|
||||
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
|
||||
anyhow::Ok(cx.spawn(|_, _| async move { delegate.shell_env().await }))
|
||||
})??
|
||||
.await;
|
||||
|
||||
Ok(proto::ShellEnvResponse {
|
||||
env: response.into_iter().collect(),
|
||||
})
|
||||
}
|
||||
pub async fn handle_try_exec(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::TryExec>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let binary = envelope
|
||||
.payload
|
||||
.binary
|
||||
.ok_or_else(|| anyhow!("missing binary"))?;
|
||||
let binary = LanguageServerBinary {
|
||||
path: PathBuf::from(binary.path),
|
||||
env: None,
|
||||
arguments: binary.arguments.into_iter().map(Into::into).collect(),
|
||||
};
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let worktree = this.worktree_for_id(worktree_id, cx)?;
|
||||
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
|
||||
anyhow::Ok(cx.spawn(|_, _| async move { delegate.try_exec(binary).await }))
|
||||
})??
|
||||
.await?;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
pub async fn handle_read_text_file(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ReadTextFile>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ReadTextFileResponse> {
|
||||
let path = envelope
|
||||
.payload
|
||||
.path
|
||||
.ok_or_else(|| anyhow!("missing path"))?;
|
||||
let worktree_id = WorktreeId::from_proto(path.worktree_id);
|
||||
let path = PathBuf::from(path.path);
|
||||
let response = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let worktree = this.worktree_for_id(worktree_id, cx)?;
|
||||
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
|
||||
anyhow::Ok(cx.spawn(|_, _| async move { delegate.read_text_file(path).await }))
|
||||
})??
|
||||
.await?;
|
||||
|
||||
Ok(proto::ReadTextFileResponse { text: response })
|
||||
}
|
||||
|
||||
async fn handle_apply_additional_edits_for_completion(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
|
||||
@@ -5388,89 +5189,6 @@ impl LspStore {
|
||||
.reorder_language_servers(&language, enabled_lsp_adapters);
|
||||
}
|
||||
|
||||
fn start_language_server_on_ssh_host(
|
||||
&mut self,
|
||||
worktree: &Model<Worktree>,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
language: LanguageName,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let ssh = self.as_ssh().unwrap();
|
||||
|
||||
let delegate = Arc::new(SshLspAdapterDelegate {
|
||||
lsp_store: cx.handle().downgrade(),
|
||||
worktree: worktree.read(cx).snapshot(),
|
||||
upstream_client: ssh.upstream_client.clone(),
|
||||
language_registry: self.languages.clone(),
|
||||
}) as Arc<dyn LspAdapterDelegate>;
|
||||
|
||||
let Some((upstream_client, project_id)) = self.upstream_client() else {
|
||||
return;
|
||||
};
|
||||
let worktree_id = worktree.read(cx).id().to_proto();
|
||||
let name = adapter.name().to_string();
|
||||
|
||||
let Some(available_language) = self.languages.available_language_for_name(&language) else {
|
||||
log::error!("failed to find available language {language}");
|
||||
return;
|
||||
};
|
||||
|
||||
let user_binary_task =
|
||||
self.get_language_server_binary(adapter.clone(), delegate.clone(), false, cx);
|
||||
|
||||
let task = cx.spawn(|_, _| async move {
|
||||
let binary = user_binary_task.await?;
|
||||
let name = adapter.name();
|
||||
let code_action_kinds = adapter
|
||||
.adapter
|
||||
.code_action_kinds()
|
||||
.map(|kinds| serde_json::to_string(&kinds))
|
||||
.transpose()?;
|
||||
let get_options = adapter.adapter.clone().initialization_options(&delegate);
|
||||
let initialization_options = get_options
|
||||
.await?
|
||||
.map(|options| serde_json::to_string(&options))
|
||||
.transpose()?;
|
||||
|
||||
let language_server_command = proto::LanguageServerCommand {
|
||||
path: binary.path.to_string_lossy().to_string(),
|
||||
arguments: binary
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|args| args.to_string_lossy().to_string())
|
||||
.collect(),
|
||||
env: binary.env.unwrap_or_default().into_iter().collect(),
|
||||
};
|
||||
|
||||
upstream_client
|
||||
.request(proto::CreateLanguageServer {
|
||||
project_id,
|
||||
worktree_id,
|
||||
name: name.0.to_string(),
|
||||
binary: Some(language_server_command),
|
||||
initialization_options,
|
||||
code_action_kinds,
|
||||
language: Some(proto::AvailableLanguage {
|
||||
name: language.to_proto(),
|
||||
matcher: serde_json::to_string(&available_language.matcher())?,
|
||||
}),
|
||||
})
|
||||
.await
|
||||
});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Err(e) = task.await {
|
||||
this.update(&mut cx, |_this, cx| {
|
||||
cx.emit(LspStoreEvent::Notification(format!(
|
||||
"failed to start {}: {}",
|
||||
name, e
|
||||
)))
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn get_language_server_binary(
|
||||
&self,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
@@ -5558,11 +5276,6 @@ impl LspStore {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.mode.is_ssh() {
|
||||
self.start_language_server_on_ssh_host(worktree_handle, adapter, language, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let project_settings = ProjectSettings::get(
|
||||
Some(SettingsLocation {
|
||||
worktree_id,
|
||||
@@ -5852,9 +5565,6 @@ impl LspStore {
|
||||
} else {
|
||||
Task::ready(Vec::new())
|
||||
}
|
||||
} else if self.mode.is_ssh() {
|
||||
// TODO ssh
|
||||
Task::ready(Vec::new())
|
||||
} else {
|
||||
Task::ready(Vec::new())
|
||||
}
|
||||
@@ -7905,116 +7615,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
struct SshLspAdapterDelegate {
|
||||
lsp_store: WeakModel<LspStore>,
|
||||
worktree: worktree::Snapshot,
|
||||
upstream_client: AnyProtoClient,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapterDelegate for SshLspAdapterDelegate {
|
||||
fn show_notification(&self, message: &str, cx: &mut AppContext) {
|
||||
self.lsp_store
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(LspStoreEvent::Notification(message.to_owned()))
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&self,
|
||||
_package_name: &str,
|
||||
) -> Result<Option<(PathBuf, String)>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn http_client(&self) -> Arc<dyn HttpClient> {
|
||||
Arc::new(BlockedHttpClient)
|
||||
}
|
||||
|
||||
fn worktree_id(&self) -> WorktreeId {
|
||||
self.worktree.id()
|
||||
}
|
||||
|
||||
fn worktree_root_path(&self) -> &Path {
|
||||
self.worktree.abs_path().as_ref()
|
||||
}
|
||||
|
||||
async fn shell_env(&self) -> HashMap<String, String> {
|
||||
use rpc::proto::SSH_PROJECT_ID;
|
||||
|
||||
self.upstream_client
|
||||
.request(proto::ShellEnv {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
worktree_id: self.worktree_id().to_proto(),
|
||||
})
|
||||
.await
|
||||
.map(|response| response.env.into_iter().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
|
||||
use rpc::proto::SSH_PROJECT_ID;
|
||||
|
||||
self.upstream_client
|
||||
.request(proto::WhichCommand {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
worktree_id: self.worktree_id().to_proto(),
|
||||
command: command.to_string_lossy().to_string(),
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
.and_then(|response| response.path)
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
|
||||
self.upstream_client
|
||||
.request(proto::TryExec {
|
||||
project_id: rpc::proto::SSH_PROJECT_ID,
|
||||
worktree_id: self.worktree.id().to_proto(),
|
||||
binary: Some(proto::LanguageServerCommand {
|
||||
path: command.path.to_string_lossy().to_string(),
|
||||
arguments: command
|
||||
.arguments
|
||||
.into_iter()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.collect(),
|
||||
env: command.env.unwrap_or_default().into_iter().collect(),
|
||||
}),
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn language_server_download_dir(&self, _: &LanguageServerName) -> Option<Arc<Path>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn update_status(
|
||||
&self,
|
||||
server_name: LanguageServerName,
|
||||
status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(server_name, status);
|
||||
}
|
||||
|
||||
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
|
||||
self.upstream_client
|
||||
.request(proto::ReadTextFile {
|
||||
project_id: rpc::proto::SSH_PROJECT_ID,
|
||||
path: Some(proto::ProjectPath {
|
||||
worktree_id: self.worktree.id().to_proto(),
|
||||
path: path.to_string_lossy().to_string(),
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.map(|r| r.text)
|
||||
}
|
||||
}
|
||||
|
||||
async fn populate_labels_for_symbols(
|
||||
symbols: Vec<CoreSymbol>,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
|
||||
@@ -558,7 +558,6 @@ impl Project {
|
||||
client.add_model_message_handler(Self::handle_update_worktree);
|
||||
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
||||
|
||||
client.add_model_request_handler(Self::handle_search_project);
|
||||
client.add_model_request_handler(Self::handle_search_candidate_buffers);
|
||||
client.add_model_request_handler(Self::handle_open_buffer_by_id);
|
||||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||
@@ -706,11 +705,12 @@ impl Project {
|
||||
|
||||
let environment = ProjectEnvironment::new(&worktree_store, None, cx);
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new_ssh(
|
||||
LspStore::new_remote(
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
languages.clone(),
|
||||
ssh.clone().into(),
|
||||
SSH_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -886,6 +886,9 @@ impl Project {
|
||||
cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
|
||||
.detach();
|
||||
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||
.detach();
|
||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||
@@ -2691,9 +2694,9 @@ impl Project {
|
||||
let (result_tx, result_rx) = smol::channel::unbounded();
|
||||
|
||||
let matching_buffers_rx = if query.is_opened_only() {
|
||||
self.sort_candidate_buffers(&query, cx)
|
||||
self.sort_search_candidates(&query, cx)
|
||||
} else {
|
||||
self.search_for_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
|
||||
self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
|
||||
};
|
||||
|
||||
cx.spawn(|_, cx| async move {
|
||||
@@ -2756,7 +2759,7 @@ impl Project {
|
||||
result_rx
|
||||
}
|
||||
|
||||
fn search_for_candidate_buffers(
|
||||
fn find_search_candidate_buffers(
|
||||
&mut self,
|
||||
query: &SearchQuery,
|
||||
limit: usize,
|
||||
@@ -2768,11 +2771,11 @@ impl Project {
|
||||
buffer_store.find_search_candidates(query, limit, fs, cx)
|
||||
})
|
||||
} else {
|
||||
self.search_for_candidate_buffers_remote(query, limit, cx)
|
||||
self.find_search_candidates_remote(query, limit, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_candidate_buffers(
|
||||
fn sort_search_candidates(
|
||||
&mut self,
|
||||
search_query: &SearchQuery,
|
||||
cx: &mut ModelContext<Project>,
|
||||
@@ -2814,7 +2817,7 @@ impl Project {
|
||||
rx
|
||||
}
|
||||
|
||||
fn search_for_candidate_buffers_remote(
|
||||
fn find_search_candidates_remote(
|
||||
&mut self,
|
||||
query: &SearchQuery,
|
||||
limit: usize,
|
||||
@@ -3655,46 +3658,6 @@ impl Project {
|
||||
Ok(proto::TaskTemplatesResponse { templates })
|
||||
}
|
||||
|
||||
async fn handle_search_project(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::SearchProject>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::SearchProjectResponse> {
|
||||
let peer_id = envelope.original_sender_id()?;
|
||||
let query = SearchQuery::from_proto_v1(envelope.payload)?;
|
||||
let mut result = this.update(&mut cx, |this, cx| this.search(query, cx))?;
|
||||
|
||||
cx.spawn(move |mut cx| async move {
|
||||
let mut locations = Vec::new();
|
||||
let mut limit_reached = false;
|
||||
while let Some(result) = result.next().await {
|
||||
match result {
|
||||
SearchResult::Buffer { buffer, ranges } => {
|
||||
for range in ranges {
|
||||
let start = serialize_anchor(&range.start);
|
||||
let end = serialize_anchor(&range.end);
|
||||
let buffer_id = this.update(&mut cx, |this, cx| {
|
||||
this.create_buffer_for_peer(&buffer, peer_id, cx).into()
|
||||
})?;
|
||||
locations.push(proto::Location {
|
||||
buffer_id,
|
||||
start: Some(start),
|
||||
end: Some(end),
|
||||
});
|
||||
}
|
||||
}
|
||||
SearchResult::LimitReached => limit_reached = true,
|
||||
}
|
||||
}
|
||||
Ok(proto::SearchProjectResponse {
|
||||
locations,
|
||||
limit_reached,
|
||||
// will restart
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_search_candidate_buffers(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::FindSearchCandidates>,
|
||||
@@ -3708,7 +3671,7 @@ impl Project {
|
||||
.ok_or_else(|| anyhow!("missing query field"))?,
|
||||
)?;
|
||||
let mut results = this.update(&mut cx, |this, cx| {
|
||||
this.search_for_candidate_buffers(&query, message.limit as _, cx)
|
||||
this.find_search_candidate_buffers(&query, message.limit as _, cx)
|
||||
})?;
|
||||
|
||||
let mut response = proto::FindSearchCandidatesResponse {
|
||||
|
||||
@@ -386,6 +386,34 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
// A server is started up, and it is notified about Rust files.
|
||||
let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
|
||||
fake_rust_server
|
||||
.request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
|
||||
registrations: vec![lsp::Registration {
|
||||
id: Default::default(),
|
||||
method: "workspace/didChangeWatchedFiles".to_string(),
|
||||
register_options: serde_json::to_value(
|
||||
lsp::DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: vec![
|
||||
lsp::FileSystemWatcher {
|
||||
glob_pattern: lsp::GlobPattern::String(
|
||||
"/the-root/Cargo.toml".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
lsp::FileSystemWatcher {
|
||||
glob_pattern: lsp::GlobPattern::String(
|
||||
"/the-root/*.rs".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
.ok(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fake_rust_server
|
||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
@@ -433,6 +461,24 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
// A json language server is started up and is only notified about the json buffer.
|
||||
let mut fake_json_server = fake_json_servers.next().await.unwrap();
|
||||
fake_json_server
|
||||
.request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
|
||||
registrations: vec![lsp::Registration {
|
||||
id: Default::default(),
|
||||
method: "workspace/didChangeWatchedFiles".to_string(),
|
||||
register_options: serde_json::to_value(
|
||||
lsp::DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: vec![lsp::FileSystemWatcher {
|
||||
glob_pattern: lsp::GlobPattern::String("/the-root/*.json".to_string()),
|
||||
kind: None,
|
||||
}],
|
||||
},
|
||||
)
|
||||
.ok(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fake_json_server
|
||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
@@ -483,7 +529,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
)
|
||||
);
|
||||
|
||||
// Save notifications are reported to all servers.
|
||||
// Save notifications are reported only to servers that signed up for a given extension.
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(toml_buffer, cx))
|
||||
.await
|
||||
@@ -495,13 +541,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
.text_document,
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
fake_json_server
|
||||
.receive_notification::<lsp::notification::DidSaveTextDocument>()
|
||||
.await
|
||||
.text_document,
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
|
||||
);
|
||||
|
||||
// Renames are reported only to servers matching the buffer's language.
|
||||
fs.rename(
|
||||
|
||||
@@ -147,30 +147,6 @@ impl SearchQuery {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_proto_v1(message: proto::SearchProject) -> Result<Self> {
|
||||
if message.regex {
|
||||
Self::regex(
|
||||
message.query,
|
||||
message.whole_word,
|
||||
message.case_sensitive,
|
||||
message.include_ignored,
|
||||
deserialize_path_matches(&message.files_to_include)?,
|
||||
deserialize_path_matches(&message.files_to_exclude)?,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
Self::text(
|
||||
message.query,
|
||||
message.whole_word,
|
||||
message.case_sensitive,
|
||||
message.include_ignored,
|
||||
deserialize_path_matches(&message.files_to_include)?,
|
||||
deserialize_path_matches(&message.files_to_exclude)?,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_proto(message: proto::SearchQuery) -> Result<Self> {
|
||||
if message.regex {
|
||||
Self::regex(
|
||||
@@ -194,6 +170,7 @@ impl SearchQuery {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_replacement(mut self, new_replacement: String) -> Self {
|
||||
match self {
|
||||
Self::Text {
|
||||
@@ -209,18 +186,6 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn to_protov1(&self, project_id: u64) -> proto::SearchProject {
|
||||
proto::SearchProject {
|
||||
project_id,
|
||||
query: self.as_str().to_string(),
|
||||
regex: self.is_regex(),
|
||||
whole_word: self.whole_word(),
|
||||
case_sensitive: self.case_sensitive(),
|
||||
include_ignored: self.include_ignored(),
|
||||
files_to_include: self.files_to_include().sources().join(","),
|
||||
files_to_exclude: self.files_to_exclude().sources().join(","),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::SearchQuery {
|
||||
proto::SearchQuery {
|
||||
|
||||
@@ -204,8 +204,11 @@ impl WorktreeStore {
|
||||
self.loading_worktrees.insert(path.clone(), task.shared());
|
||||
}
|
||||
let task = self.loading_worktrees.get(&path).unwrap().clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
match task.await {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let result = task.await;
|
||||
this.update(&mut cx, |this, _| this.loading_worktrees.remove(&path))
|
||||
.ok();
|
||||
match result {
|
||||
Ok(worktree) => Ok(worktree),
|
||||
Err(err) => Err((*err).cloned()),
|
||||
}
|
||||
@@ -219,7 +222,8 @@ impl WorktreeStore {
|
||||
visible: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
|
||||
let mut abs_path = abs_path.as_ref().to_string_lossy().to_string();
|
||||
let path_key: Arc<Path> = abs_path.as_ref().into();
|
||||
let mut abs_path = path_key.clone().to_string_lossy().to_string();
|
||||
// If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/`
|
||||
// in which case want to strip the leading the `/`.
|
||||
// On the host-side, the `~` will get expanded.
|
||||
@@ -261,8 +265,9 @@ impl WorktreeStore {
|
||||
)
|
||||
})?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.add(&worktree, cx);
|
||||
})?;
|
||||
Ok(worktree)
|
||||
})
|
||||
}
|
||||
@@ -280,10 +285,6 @@ impl WorktreeStore {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await;
|
||||
|
||||
this.update(&mut cx, |project, _| {
|
||||
project.loading_worktrees.remove(&path);
|
||||
})?;
|
||||
|
||||
let worktree = worktree?;
|
||||
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?;
|
||||
|
||||
@@ -317,7 +318,7 @@ impl WorktreeStore {
|
||||
});
|
||||
|
||||
let abs_path = abs_path.as_ref().to_path_buf();
|
||||
cx.spawn(move |project, mut cx| async move {
|
||||
cx.spawn(move |project, cx| async move {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let tx = RefCell::new(Some(tx));
|
||||
let Some(project) = project.upgrade() else {
|
||||
@@ -339,14 +340,10 @@ impl WorktreeStore {
|
||||
request.await?;
|
||||
let worktree = rx.await.map_err(|e| anyhow!(e))?;
|
||||
drop(observer);
|
||||
project.update(&mut cx, |project, _| {
|
||||
project.loading_worktrees.remove(&path);
|
||||
})?;
|
||||
Ok(worktree)
|
||||
})
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn add(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
debug_assert!(self.worktrees().all(|w| w.read(cx).id() != worktree_id));
|
||||
@@ -553,9 +550,12 @@ impl WorktreeStore {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
if client.is_via_collab() {
|
||||
client.request(update).map(|result| result.is_ok()).await
|
||||
client
|
||||
.request(update)
|
||||
.map(|result| result.log_err().is_some())
|
||||
.await
|
||||
} else {
|
||||
client.send(update).is_ok()
|
||||
client.send(update).log_err().is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,6 @@ message Envelope {
|
||||
PrepareRenameResponse prepare_rename_response = 84;
|
||||
PerformRename perform_rename = 85;
|
||||
PerformRenameResponse perform_rename_response = 86;
|
||||
SearchProject search_project = 87;
|
||||
SearchProjectResponse search_project_response = 88;
|
||||
|
||||
UpdateContacts update_contacts = 89;
|
||||
UpdateInviteInfo update_invite_info = 90;
|
||||
@@ -283,25 +281,15 @@ message Envelope {
|
||||
CloseBuffer close_buffer = 245;
|
||||
UpdateUserSettings update_user_settings = 246;
|
||||
|
||||
CreateLanguageServer create_language_server = 247;
|
||||
|
||||
WhichCommand which_command = 248;
|
||||
WhichCommandResponse which_command_response = 249;
|
||||
|
||||
ShellEnv shell_env = 250;
|
||||
ShellEnvResponse shell_env_response = 251;
|
||||
|
||||
TryExec try_exec = 252;
|
||||
ReadTextFile read_text_file = 253;
|
||||
ReadTextFileResponse read_text_file_response = 254;
|
||||
|
||||
CheckFileExists check_file_exists = 255;
|
||||
CheckFileExistsResponse check_file_exists_response = 256; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
reserved 158 to 161;
|
||||
reserved 166 to 169;
|
||||
reserved 224 to 229;
|
||||
reserved 247 to 254;
|
||||
}
|
||||
|
||||
// Messages
|
||||
@@ -1249,22 +1237,6 @@ message PerformRenameResponse {
|
||||
ProjectTransaction transaction = 2;
|
||||
}
|
||||
|
||||
message SearchProject {
|
||||
uint64 project_id = 1;
|
||||
string query = 2;
|
||||
bool regex = 3;
|
||||
bool whole_word = 4;
|
||||
bool case_sensitive = 5;
|
||||
string files_to_include = 6;
|
||||
string files_to_exclude = 7;
|
||||
bool include_ignored = 8;
|
||||
}
|
||||
|
||||
message SearchProjectResponse {
|
||||
repeated Location locations = 1;
|
||||
bool limit_reached = 2;
|
||||
}
|
||||
|
||||
message SearchQuery {
|
||||
string query = 2;
|
||||
bool regex = 3;
|
||||
@@ -2517,67 +2489,6 @@ message UpdateUserSettings {
|
||||
string content = 2;
|
||||
}
|
||||
|
||||
message LanguageServerCommand {
|
||||
string path = 1;
|
||||
repeated string arguments = 2;
|
||||
map<string, string> env = 3;
|
||||
}
|
||||
|
||||
message AvailableLanguage {
|
||||
string name = 7;
|
||||
string matcher = 8;
|
||||
}
|
||||
|
||||
message CreateLanguageServer {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
string name = 3;
|
||||
|
||||
LanguageServerCommand binary = 4;
|
||||
optional string initialization_options = 5;
|
||||
optional string code_action_kinds = 6;
|
||||
|
||||
AvailableLanguage language = 7;
|
||||
}
|
||||
|
||||
message WhichCommand {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
string command = 3;
|
||||
}
|
||||
|
||||
message WhichCommandResponse {
|
||||
optional string path = 1;
|
||||
}
|
||||
|
||||
message ShellEnv {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
}
|
||||
|
||||
message ShellEnvResponse {
|
||||
map<string, string> env = 1;
|
||||
}
|
||||
|
||||
message ReadTextFile {
|
||||
uint64 project_id = 1;
|
||||
ProjectPath path = 2;
|
||||
}
|
||||
|
||||
message ReadTextFileResponse {
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
message TryExec {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
LanguageServerCommand binary = 3;
|
||||
}
|
||||
|
||||
message TryExecResponse {
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
message CheckFileExists {
|
||||
uint64 project_id = 1;
|
||||
string path = 2;
|
||||
|
||||
@@ -279,8 +279,6 @@ messages!(
|
||||
(SaveBuffer, Foreground),
|
||||
(SetChannelMemberRole, Foreground),
|
||||
(SetChannelVisibility, Foreground),
|
||||
(SearchProject, Background),
|
||||
(SearchProjectResponse, Background),
|
||||
(SendChannelMessage, Background),
|
||||
(SendChannelMessageResponse, Background),
|
||||
(ShareProject, Foreground),
|
||||
@@ -365,14 +363,6 @@ messages!(
|
||||
(FindSearchCandidatesResponse, Background),
|
||||
(CloseBuffer, Foreground),
|
||||
(UpdateUserSettings, Foreground),
|
||||
(CreateLanguageServer, Foreground),
|
||||
(WhichCommand, Foreground),
|
||||
(WhichCommandResponse, Foreground),
|
||||
(ShellEnv, Foreground),
|
||||
(ShellEnvResponse, Foreground),
|
||||
(TryExec, Foreground),
|
||||
(ReadTextFile, Foreground),
|
||||
(ReadTextFileResponse, Foreground),
|
||||
(CheckFileExists, Background),
|
||||
(CheckFileExistsResponse, Background)
|
||||
);
|
||||
@@ -462,7 +452,6 @@ request_messages!(
|
||||
(RespondToChannelInvite, Ack),
|
||||
(RespondToContactRequest, Ack),
|
||||
(SaveBuffer, BufferSaved),
|
||||
(SearchProject, SearchProjectResponse),
|
||||
(FindSearchCandidates, FindSearchCandidatesResponse),
|
||||
(SendChannelMessage, SendChannelMessageResponse),
|
||||
(SetChannelMemberRole, Ack),
|
||||
@@ -498,11 +487,6 @@ request_messages!(
|
||||
(SynchronizeContexts, SynchronizeContextsResponse),
|
||||
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
|
||||
(AddWorktree, AddWorktreeResponse),
|
||||
(CreateLanguageServer, Ack),
|
||||
(WhichCommand, WhichCommandResponse),
|
||||
(ShellEnv, ShellEnvResponse),
|
||||
(ReadTextFile, ReadTextFileResponse),
|
||||
(TryExec, Ack),
|
||||
(CheckFileExists, CheckFileExistsResponse)
|
||||
);
|
||||
|
||||
@@ -554,7 +538,6 @@ entity_messages!(
|
||||
ResolveCompletionDocumentation,
|
||||
ResolveInlayHint,
|
||||
SaveBuffer,
|
||||
SearchProject,
|
||||
StartLanguageServer,
|
||||
SynchronizeBuffers,
|
||||
TaskContextForLocation,
|
||||
@@ -577,11 +560,6 @@ entity_messages!(
|
||||
SynchronizeContexts,
|
||||
LspExtSwitchSourceHeader,
|
||||
UpdateUserSettings,
|
||||
CreateLanguageServer,
|
||||
WhichCommand,
|
||||
ShellEnv,
|
||||
TryExec,
|
||||
ReadTextFile,
|
||||
CheckFileExists,
|
||||
);
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ use editor::actions::{
|
||||
use editor::{Editor, EditorSettings};
|
||||
|
||||
use gpui::{
|
||||
Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement,
|
||||
Render, Styled, Subscription, View, ViewContext, WeakView,
|
||||
Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
|
||||
};
|
||||
use search::{buffer_search, BufferSearchBar};
|
||||
use settings::{Settings, SettingsStore};
|
||||
@@ -110,12 +110,15 @@ impl Render for QuickActionBar {
|
||||
)
|
||||
};
|
||||
|
||||
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||
|
||||
let search_button = editor.is_singleton(cx).then(|| {
|
||||
QuickActionBarButton::new(
|
||||
"toggle buffer search",
|
||||
IconName::MagnifyingGlass,
|
||||
!self.buffer_search_bar.read(cx).is_dismissed(),
|
||||
Box::new(buffer_search::Deploy::find()),
|
||||
focus_handle.clone(),
|
||||
"Buffer Search",
|
||||
{
|
||||
let buffer_search_bar = self.buffer_search_bar.clone();
|
||||
@@ -133,6 +136,7 @@ impl Render for QuickActionBar {
|
||||
IconName::ZedAssistant,
|
||||
false,
|
||||
Box::new(InlineAssist::default()),
|
||||
focus_handle.clone(),
|
||||
"Inline Assist",
|
||||
{
|
||||
let workspace = self.workspace.clone();
|
||||
@@ -321,6 +325,7 @@ struct QuickActionBarButton {
|
||||
icon: IconName,
|
||||
toggled: bool,
|
||||
action: Box<dyn Action>,
|
||||
focus_handle: FocusHandle,
|
||||
tooltip: SharedString,
|
||||
on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
||||
}
|
||||
@@ -331,6 +336,7 @@ impl QuickActionBarButton {
|
||||
icon: IconName,
|
||||
toggled: bool,
|
||||
action: Box<dyn Action>,
|
||||
focus_handle: FocusHandle,
|
||||
tooltip: impl Into<SharedString>,
|
||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
@@ -339,6 +345,7 @@ impl QuickActionBarButton {
|
||||
icon,
|
||||
toggled,
|
||||
action,
|
||||
focus_handle,
|
||||
tooltip: tooltip.into(),
|
||||
on_click: Box::new(on_click),
|
||||
}
|
||||
@@ -355,7 +362,9 @@ impl RenderOnce for QuickActionBarButton {
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected(self.toggled)
|
||||
.tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in(tooltip.clone(), &*action, &self.focus_handle, cx)
|
||||
})
|
||||
.on_click(move |event, cx| (self.on_click)(event, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ use ui::{
|
||||
};
|
||||
use ui_input::{FieldLabelLayout, TextField};
|
||||
use util::ResultExt;
|
||||
use workspace::notifications::NotifyResultExt;
|
||||
use workspace::OpenOptions;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB};
|
||||
|
||||
@@ -1133,7 +1132,8 @@ impl DevServerProjects {
|
||||
let dev_server_id = state.dev_server_id;
|
||||
let access_token = state.access_token.clone();
|
||||
let ssh_prompt = state.ssh_prompt.clone();
|
||||
let use_direct_ssh = SshSettings::get_global(cx).use_direct_ssh();
|
||||
let use_direct_ssh = SshSettings::get_global(cx).use_direct_ssh()
|
||||
|| Client::global(cx).status().borrow().is_signed_out();
|
||||
|
||||
let mut kind = state.kind;
|
||||
if use_direct_ssh && kind == NewServerKind::LegacySSH {
|
||||
@@ -1407,7 +1407,6 @@ impl DevServerProjects {
|
||||
is_creating = Some(*creating);
|
||||
creating_dev_server = Some(*dev_server_id);
|
||||
};
|
||||
let is_signed_out = Client::global(cx).status().borrow().is_signed_out();
|
||||
|
||||
Modal::new("remote-projects", Some(self.scroll_handle.clone()))
|
||||
.header(
|
||||
@@ -1415,82 +1414,58 @@ impl DevServerProjects {
|
||||
.show_dismiss_button(true)
|
||||
.child(Headline::new("Remote Projects (alpha)").size(HeadlineSize::Small)),
|
||||
)
|
||||
.when(is_signed_out, |modal| {
|
||||
modal
|
||||
.section(Section::new().child(div().child(Label::new(
|
||||
"To continue with the remote development features, you need to sign in to Zed.",
|
||||
))))
|
||||
.footer(
|
||||
ModalFooter::new().end_slot(
|
||||
Button::new("sign_in", "Sign in with GitHub")
|
||||
.icon(IconName::Github)
|
||||
.icon_position(IconPosition::Start)
|
||||
.full_width()
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
let client = Client::global(cx).clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.await
|
||||
.notify_async_err(&mut cx);
|
||||
})
|
||||
.detach();
|
||||
cx.emit(gpui::DismissEvent);
|
||||
})),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(!is_signed_out, |modal| {
|
||||
modal.section(
|
||||
Section::new().child(
|
||||
div().child(
|
||||
List::new()
|
||||
.empty_message("No dev servers registered yet.")
|
||||
.header(Some(
|
||||
ListHeader::new("Connections").end_slot(
|
||||
Button::new("register-dev-server-button", "Connect New Server")
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(
|
||||
CreateDevServer {
|
||||
kind: if SshSettings::get_global(cx).use_direct_ssh() { NewServerKind::DirectSSH } else { NewServerKind::LegacySSH },
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
this.dev_server_name_input.update(
|
||||
cx,
|
||||
|text_field, cx| {
|
||||
text_field.editor().update(
|
||||
cx,
|
||||
|editor, cx| {
|
||||
editor.set_text("", cx);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
))
|
||||
.children(ssh_connections.iter().cloned().enumerate().map(|(ix, connection)| {
|
||||
.section(
|
||||
Section::new().child(
|
||||
div().child(
|
||||
List::new()
|
||||
.empty_message("No dev servers registered yet.")
|
||||
.header(Some(
|
||||
ListHeader::new("Connections").end_slot(
|
||||
Button::new("register-dev-server-button", "Connect New Server")
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
kind: if SshSettings::get_global(cx)
|
||||
.use_direct_ssh()
|
||||
{
|
||||
NewServerKind::DirectSSH
|
||||
} else {
|
||||
NewServerKind::LegacySSH
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
this.dev_server_name_input.update(
|
||||
cx,
|
||||
|text_field, cx| {
|
||||
text_field.editor().update(cx, |editor, cx| {
|
||||
editor.set_text("", cx);
|
||||
});
|
||||
},
|
||||
);
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
))
|
||||
.children(ssh_connections.iter().cloned().enumerate().map(
|
||||
|(ix, connection)| {
|
||||
self.render_ssh_connection(ix, connection, cx)
|
||||
.into_any_element()
|
||||
}))
|
||||
.children(dev_servers.iter().map(|dev_server| {
|
||||
let creating = if creating_dev_server == Some(dev_server.id) {
|
||||
is_creating
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.render_dev_server(dev_server, creating, cx)
|
||||
.into_any_element()
|
||||
})),
|
||||
),
|
||||
},
|
||||
))
|
||||
.children(dev_servers.iter().map(|dev_server| {
|
||||
let creating = if creating_dev_server == Some(dev_server.id) {
|
||||
is_creating
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.render_dev_server(dev_server, creating, cx)
|
||||
.into_any_element()
|
||||
})),
|
||||
),
|
||||
)
|
||||
})
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use ui::{
|
||||
h_flex, v_flex, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement,
|
||||
Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext, WindowContext,
|
||||
h_flex, v_flex, Color, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement,
|
||||
IntoElement, Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
use workspace::{AppState, ModalView, Workspace};
|
||||
|
||||
@@ -79,6 +80,7 @@ impl Settings for SshSettings {
|
||||
pub struct SshPrompt {
|
||||
connection_string: SharedString,
|
||||
status_message: Option<SharedString>,
|
||||
error_message: Option<SharedString>,
|
||||
prompt: Option<(SharedString, oneshot::Sender<Result<String>>)>,
|
||||
editor: View<Editor>,
|
||||
}
|
||||
@@ -92,6 +94,7 @@ impl SshPrompt {
|
||||
Self {
|
||||
connection_string,
|
||||
status_message: None,
|
||||
error_message: None,
|
||||
prompt: None,
|
||||
editor: cx.new_view(Editor::single_line),
|
||||
}
|
||||
@@ -121,6 +124,11 @@ impl SshPrompt {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_error(&mut self, error_message: String, cx: &mut ViewContext<Self>) {
|
||||
self.error_message = Some(error_message.into());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn confirm(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some((_, tx)) = self.prompt.take() {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
@@ -140,7 +148,12 @@ impl Render for SshPrompt {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
.child(if self.error_message.is_some() {
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Medium)
|
||||
.color(Color::Error)
|
||||
.into_any_element()
|
||||
} else {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Medium)
|
||||
.with_animation(
|
||||
@@ -149,16 +162,21 @@ impl Render for SshPrompt {
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.child(
|
||||
Label::new(format!("ssh {}…", self.connection_string))
|
||||
.size(ui::LabelSize::Large),
|
||||
),
|
||||
)
|
||||
.when_some(self.status_message.as_ref(), |el, status| {
|
||||
el.child(Label::new(status.clone()))
|
||||
.when_some(self.error_message.as_ref(), |el, error| {
|
||||
el.child(Label::new(error.clone()))
|
||||
})
|
||||
.when(
|
||||
self.error_message.is_none() && self.status_message.is_some(),
|
||||
|el| el.child(Label::new(self.status_message.clone().unwrap())),
|
||||
)
|
||||
.when_some(self.prompt.as_ref(), |el, prompt| {
|
||||
el.child(Label::new(prompt.0.clone()))
|
||||
.child(self.editor.clone())
|
||||
@@ -238,6 +256,10 @@ impl remote::SshClientDelegate for SshClientDelegate {
|
||||
self.update_status(status, cx)
|
||||
}
|
||||
|
||||
fn set_error(&self, error: String, cx: &mut AsyncAppContext) {
|
||||
self.update_error(error, cx)
|
||||
}
|
||||
|
||||
fn get_server_binary(
|
||||
&self,
|
||||
platform: SshPlatform,
|
||||
@@ -270,6 +292,16 @@ impl SshClientDelegate {
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn update_error(&self, error: String, cx: &mut AsyncAppContext) {
|
||||
self.window
|
||||
.update(cx, |_, cx| {
|
||||
self.ui.update(cx, |modal, cx| {
|
||||
modal.set_error(error, cx);
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
async fn get_server_binary_impl(
|
||||
&self,
|
||||
platform: SshPlatform,
|
||||
@@ -388,7 +420,7 @@ pub async fn open_ssh_project(
|
||||
})?
|
||||
};
|
||||
|
||||
let result = window
|
||||
let session = window
|
||||
.update(cx, |workspace, cx| {
|
||||
cx.activate_window();
|
||||
workspace.toggle_modal(cx, |cx| SshConnectionModal::new(&connection_options, cx));
|
||||
@@ -400,12 +432,7 @@ pub async fn open_ssh_project(
|
||||
.clone();
|
||||
connect_over_ssh(connection_options.clone(), ui, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
window.update(cx, |_, cx| cx.remove_window()).ok();
|
||||
}
|
||||
let session = result?;
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| {
|
||||
workspace::open_ssh_project(window, connection_options, session, app_state, paths, cx)
|
||||
|
||||
@@ -129,6 +129,7 @@ pub trait SshClientDelegate {
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> oneshot::Receiver<Result<(PathBuf, SemanticVersion)>>;
|
||||
fn set_status(&self, status: Option<&str>, cx: &mut AsyncAppContext);
|
||||
fn set_error(&self, error_message: String, cx: &mut AsyncAppContext);
|
||||
}
|
||||
|
||||
type ResponseChannels = Mutex<HashMap<MessageId, oneshot::Sender<(Envelope, oneshot::Sender<()>)>>>;
|
||||
@@ -208,16 +209,16 @@ impl SshSession {
|
||||
|
||||
result = child_stdout.read(&mut stdout_buffer).fuse() => {
|
||||
match result {
|
||||
Ok(len) => {
|
||||
if len == 0 {
|
||||
child_stdin.close().await?;
|
||||
let status = remote_server_child.status().await?;
|
||||
if !status.success() {
|
||||
log::info!("channel exited with status: {status:?}");
|
||||
}
|
||||
return Ok(());
|
||||
Ok(0) => {
|
||||
child_stdin.close().await?;
|
||||
outgoing_rx.close();
|
||||
let status = remote_server_child.status().await?;
|
||||
if !status.success() {
|
||||
log::error!("channel exited with status: {status:?}");
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Ok(len) => {
|
||||
if len < stdout_buffer.len() {
|
||||
child_stdout.read_exact(&mut stdout_buffer[len..]).await?;
|
||||
}
|
||||
@@ -419,8 +420,13 @@ impl SshSession {
|
||||
let mut response_channels_lock = self.response_channels.lock();
|
||||
response_channels_lock.insert(MessageId(envelope.id), tx);
|
||||
drop(response_channels_lock);
|
||||
self.outgoing_tx.unbounded_send(envelope).ok();
|
||||
let result = self.outgoing_tx.unbounded_send(envelope);
|
||||
async move {
|
||||
if let Err(error) = &result {
|
||||
log::error!("failed to send message: {}", error);
|
||||
return Err(anyhow!("failed to send message: {}", error));
|
||||
}
|
||||
|
||||
let response = rx.await.context("connection lost")?.0;
|
||||
if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
|
||||
return Err(RpcError::from_proto(error, type_name));
|
||||
@@ -525,22 +531,25 @@ impl SshClientState {
|
||||
let listener =
|
||||
UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?;
|
||||
|
||||
let askpass_task = cx.spawn(|mut cx| async move {
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
let mut buffer = Vec::new();
|
||||
let mut reader = BufReader::new(&mut stream);
|
||||
if reader.read_until(b'\0', &mut buffer).await.is_err() {
|
||||
buffer.clear();
|
||||
}
|
||||
let password_prompt = String::from_utf8_lossy(&buffer);
|
||||
if let Some(password) = delegate
|
||||
.ask_password(password_prompt.to_string(), &mut cx)
|
||||
.await
|
||||
.context("failed to get ssh password")
|
||||
.and_then(|p| p)
|
||||
.log_err()
|
||||
{
|
||||
stream.write_all(password.as_bytes()).await.log_err();
|
||||
let askpass_task = cx.spawn({
|
||||
let delegate = delegate.clone();
|
||||
|mut cx| async move {
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
let mut buffer = Vec::new();
|
||||
let mut reader = BufReader::new(&mut stream);
|
||||
if reader.read_until(b'\0', &mut buffer).await.is_err() {
|
||||
buffer.clear();
|
||||
}
|
||||
let password_prompt = String::from_utf8_lossy(&buffer);
|
||||
if let Some(password) = delegate
|
||||
.ask_password(password_prompt.to_string(), &mut cx)
|
||||
.await
|
||||
.context("failed to get ssh password")
|
||||
.and_then(|p| p)
|
||||
.log_err()
|
||||
{
|
||||
stream.write_all(password.as_bytes()).await.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -575,7 +584,22 @@ impl SshClientState {
|
||||
// has completed.
|
||||
let stdout = master_process.stdout.as_mut().unwrap();
|
||||
let mut output = Vec::new();
|
||||
stdout.read_to_end(&mut output).await?;
|
||||
let connection_timeout = std::time::Duration::from_secs(10);
|
||||
let result = read_with_timeout(stdout, connection_timeout, &mut output).await;
|
||||
if let Err(e) = result {
|
||||
let error_message = if e.kind() == std::io::ErrorKind::TimedOut {
|
||||
format!(
|
||||
"Failed to connect to host. Timed out after {:?}.",
|
||||
connection_timeout
|
||||
)
|
||||
} else {
|
||||
format!("Failed to connect to host: {}.", e)
|
||||
};
|
||||
|
||||
delegate.set_error(error_message, cx);
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
drop(askpass_task);
|
||||
|
||||
if master_process.try_status()?.is_some() {
|
||||
@@ -716,6 +740,29 @@ impl SshClientState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn read_with_timeout(
|
||||
stdout: &mut process::ChildStdout,
|
||||
timeout: std::time::Duration,
|
||||
output: &mut Vec<u8>,
|
||||
) -> Result<(), std::io::Error> {
|
||||
smol::future::or(
|
||||
async {
|
||||
stdout.read_to_end(output).await?;
|
||||
Ok::<_, std::io::Error>(())
|
||||
},
|
||||
async {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
"Read operation timed out",
|
||||
))
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
impl Drop for SshClientState {
|
||||
fn drop(&mut self) {
|
||||
if let Err(error) = self.master_process.kill() {
|
||||
|
||||
@@ -39,6 +39,7 @@ shellexpand.workspace = true
|
||||
smol.workspace = true
|
||||
worktree.workspace = true
|
||||
language.workspace = true
|
||||
languages.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -44,6 +44,10 @@ impl HeadlessProject {
|
||||
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
||||
let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
|
||||
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
languages::init(languages.clone(), node_runtime.clone(), cx);
|
||||
|
||||
let worktree_store = cx.new_model(|cx| {
|
||||
let mut store = WorktreeStore::local(true, fs.clone());
|
||||
store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
|
||||
@@ -56,7 +60,7 @@ impl HeadlessProject {
|
||||
});
|
||||
let prettier_store = cx.new_model(|cx| {
|
||||
PrettierStore::new(
|
||||
NodeRuntime::unavailable(),
|
||||
node_runtime,
|
||||
fs.clone(),
|
||||
languages.clone(),
|
||||
worktree_store.clone(),
|
||||
@@ -116,12 +120,6 @@ impl HeadlessProject {
|
||||
client.add_model_request_handler(BufferStore::handle_update_buffer);
|
||||
client.add_model_message_handler(BufferStore::handle_close_buffer);
|
||||
|
||||
client.add_model_request_handler(LspStore::handle_create_language_server);
|
||||
client.add_model_request_handler(LspStore::handle_which_command);
|
||||
client.add_model_request_handler(LspStore::handle_shell_env);
|
||||
client.add_model_request_handler(LspStore::handle_try_exec);
|
||||
client.add_model_request_handler(LspStore::handle_read_text_file);
|
||||
|
||||
BufferStore::init(&client);
|
||||
WorktreeStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
|
||||
@@ -564,6 +564,48 @@ async fn test_canceling_buffer_opening(cx: &mut TestAppContext, server_cx: &mut
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_adding_then_removing_then_adding_worktrees(
|
||||
cx: &mut TestAppContext,
|
||||
server_cx: &mut TestAppContext,
|
||||
) {
|
||||
let (project, _headless, _fs) = init_test(cx, server_cx).await;
|
||||
let (_worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project1", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (worktree_2, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project2", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let worktree_id_2 = worktree_2.read_with(cx, |tree, _| tree.id());
|
||||
|
||||
project.update(cx, |project, cx| project.remove_worktree(worktree_id_2, cx));
|
||||
|
||||
let (worktree_2, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project2", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
worktree_2.update(cx, |worktree, _cx| {
|
||||
assert!(worktree.is_visible());
|
||||
let entries = worktree.entries(true, 0).collect::<Vec<_>>();
|
||||
assert_eq!(entries.len(), 2);
|
||||
assert_eq!(
|
||||
entries[1].path.to_string_lossy().to_string(),
|
||||
"README.md".to_string()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::try_init().ok();
|
||||
|
||||
@@ -12,7 +12,6 @@ pub enum LanguageModelProvider {
|
||||
Anthropic,
|
||||
OpenAi,
|
||||
Google,
|
||||
Zed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -13,9 +13,10 @@ use editor::{
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView, Hsla,
|
||||
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,
|
||||
Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _, WindowContext,
|
||||
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, Hsla, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _,
|
||||
Render, ScrollHandle, Styled, Subscription, Task, TextStyle, View, ViewContext,
|
||||
VisualContext as _, WindowContext,
|
||||
};
|
||||
use project::{
|
||||
search::SearchQuery,
|
||||
@@ -142,6 +143,8 @@ impl Render for BufferSearchBar {
|
||||
return div().id("search_bar");
|
||||
}
|
||||
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
let narrow_mode =
|
||||
self.scroll_handle.bounds().size.width / cx.rem_size() < 340. / BASE_REM_SIZE_IN_PX;
|
||||
let hide_inline_icons = self.editor_needed_width
|
||||
@@ -217,6 +220,7 @@ impl Render for BufferSearchBar {
|
||||
div.children(supported_options.case.then(|| {
|
||||
self.render_search_option_button(
|
||||
SearchOptions::CASE_SENSITIVE,
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| {
|
||||
this.toggle_case_sensitive(&ToggleCaseSensitive, cx)
|
||||
}),
|
||||
@@ -225,6 +229,7 @@ impl Render for BufferSearchBar {
|
||||
.children(supported_options.word.then(|| {
|
||||
self.render_search_option_button(
|
||||
SearchOptions::WHOLE_WORD,
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| {
|
||||
this.toggle_whole_word(&ToggleWholeWord, cx)
|
||||
}),
|
||||
@@ -233,6 +238,7 @@ impl Render for BufferSearchBar {
|
||||
.children(supported_options.regex.then(|| {
|
||||
self.render_search_option_button(
|
||||
SearchOptions::REGEX,
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| this.toggle_regex(&ToggleRegex, cx)),
|
||||
)
|
||||
}))
|
||||
@@ -250,7 +256,17 @@ impl Render for BufferSearchBar {
|
||||
}))
|
||||
.selected(self.replace_enabled)
|
||||
.size(ButtonSize::Compact)
|
||||
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle replace",
|
||||
&ToggleReplace,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(supported_options.selection, |this| {
|
||||
@@ -268,8 +284,16 @@ impl Render for BufferSearchBar {
|
||||
}))
|
||||
.selected(self.selection_search_enabled)
|
||||
.size(ButtonSize::Compact)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action("Toggle search selection", &ToggleSelection, cx)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle Search Selection",
|
||||
&ToggleSelection,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -280,21 +304,31 @@ impl Render for BufferSearchBar {
|
||||
IconButton::new("select-all", ui::IconName::SelectAll)
|
||||
.on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
|
||||
.size(ButtonSize::Compact)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Select All Matches",
|
||||
&SelectAllMatches,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(render_nav_button(
|
||||
ui::IconName::ChevronLeft,
|
||||
self.active_match_index.is_some(),
|
||||
"Select previous match",
|
||||
"Select Previous Match",
|
||||
&SelectPrevMatch,
|
||||
focus_handle.clone(),
|
||||
))
|
||||
.child(render_nav_button(
|
||||
ui::IconName::ChevronRight,
|
||||
self.active_match_index.is_some(),
|
||||
"Select next match",
|
||||
"Select Next Match",
|
||||
&SelectNextMatch,
|
||||
focus_handle.clone(),
|
||||
))
|
||||
.when(!narrow_mode, |this| {
|
||||
this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child(
|
||||
@@ -335,8 +369,16 @@ impl Render for BufferSearchBar {
|
||||
.flex_none()
|
||||
.child(
|
||||
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action("Replace next", &ReplaceNext, cx)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace Next Match",
|
||||
&ReplaceNext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(|this, _, cx| this.replace_next(&ReplaceNext, cx)),
|
||||
@@ -344,8 +386,16 @@ impl Render for BufferSearchBar {
|
||||
)
|
||||
.child(
|
||||
IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action("Replace all", &ReplaceAll, cx)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace All Matches",
|
||||
&ReplaceAll,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(|this, _, cx| this.replace_all(&ReplaceAll, cx)),
|
||||
@@ -392,7 +442,7 @@ impl Render for BufferSearchBar {
|
||||
div.child(
|
||||
IconButton::new(SharedString::from("Close"), IconName::Close)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action("Close search bar", &Dismiss, cx)
|
||||
Tooltip::for_action("Close Search Bar", &Dismiss, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
|
||||
this.dismiss(&Dismiss, cx)
|
||||
@@ -719,10 +769,11 @@ impl BufferSearchBar {
|
||||
fn render_search_option_button(
|
||||
&self,
|
||||
option: SearchOptions,
|
||||
focus_handle: FocusHandle,
|
||||
action: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> impl IntoElement {
|
||||
let is_active = self.search_options.contains(option);
|
||||
option.as_button(is_active, action)
|
||||
option.as_button(is_active, focus_handle, action)
|
||||
}
|
||||
|
||||
pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
|
||||
@@ -1122,6 +1173,7 @@ impl BufferSearchBar {
|
||||
});
|
||||
cx.focus(handle);
|
||||
}
|
||||
|
||||
fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
|
||||
if self.active_searchable_item.is_some() {
|
||||
self.replace_enabled = !self.replace_enabled;
|
||||
@@ -1134,6 +1186,7 @@ impl BufferSearchBar {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
|
||||
let mut should_propagate = true;
|
||||
if !self.dismissed && self.active_search.is_some() {
|
||||
@@ -1161,6 +1214,7 @@ impl BufferSearchBar {
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
|
||||
if !self.dismissed && self.active_search.is_some() {
|
||||
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
|
||||
|
||||
@@ -1551,6 +1551,7 @@ impl Render for ProjectSearchBar {
|
||||
return div();
|
||||
};
|
||||
let search = search.read(cx);
|
||||
let focus_handle = search.focus_handle(cx);
|
||||
|
||||
let query_column = h_flex()
|
||||
.flex_1()
|
||||
@@ -1571,18 +1572,21 @@ impl Render for ProjectSearchBar {
|
||||
h_flex()
|
||||
.child(SearchOptions::CASE_SENSITIVE.as_button(
|
||||
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| {
|
||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
||||
}),
|
||||
))
|
||||
.child(SearchOptions::WHOLE_WORD.as_button(
|
||||
self.is_option_enabled(SearchOptions::WHOLE_WORD, cx),
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| {
|
||||
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
||||
}),
|
||||
))
|
||||
.child(SearchOptions::REGEX.as_button(
|
||||
self.is_option_enabled(SearchOptions::REGEX, cx),
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| {
|
||||
this.toggle_search_option(SearchOptions::REGEX, cx);
|
||||
}),
|
||||
@@ -1603,7 +1607,17 @@ impl Render for ProjectSearchBar {
|
||||
.map(|search| search.read(cx).filters_enabled)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx)),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle filters",
|
||||
&ToggleFilters,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("project-search-toggle-replace", IconName::Replace)
|
||||
@@ -1616,7 +1630,17 @@ impl Render for ProjectSearchBar {
|
||||
.map(|search| search.read(cx).replace_enabled)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle replace",
|
||||
&ToggleReplace,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1650,8 +1674,16 @@ impl Render for ProjectSearchBar {
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action("Go to previous match", &SelectPrevMatch, cx)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Go to previous match",
|
||||
&SelectPrevMatch,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -1664,7 +1696,17 @@ impl Render for ProjectSearchBar {
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Go to next match",
|
||||
&SelectNextMatch,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -1702,6 +1744,7 @@ impl Render for ProjectSearchBar {
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_lg()
|
||||
.child(self.render_text_input(&search.replacement_editor, cx));
|
||||
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
|
||||
let replace_actions = h_flex().when(search.replace_enabled, |this| {
|
||||
this.child(
|
||||
IconButton::new("project-search-replace-next", IconName::ReplaceNext)
|
||||
@@ -1712,7 +1755,17 @@ impl Render for ProjectSearchBar {
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace next match",
|
||||
&ReplaceNext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
|
||||
@@ -1723,7 +1776,17 @@ impl Render for ProjectSearchBar {
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::for_action("Replace all matches", &ReplaceAll, cx)),
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace all matches",
|
||||
&ReplaceAll,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
h_flex()
|
||||
@@ -1790,6 +1853,7 @@ impl Render for ProjectSearchBar {
|
||||
search
|
||||
.search_options
|
||||
.contains(SearchOptions::INCLUDE_IGNORED),
|
||||
focus_handle.clone(),
|
||||
cx.listener(|this, _, cx| {
|
||||
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use bitflags::bitflags;
|
||||
pub use buffer_search::BufferSearchBar;
|
||||
use editor::SearchSettings;
|
||||
use gpui::{actions, Action, AppContext, IntoElement};
|
||||
use gpui::{actions, Action, AppContext, FocusHandle, IntoElement};
|
||||
use project::search::SearchQuery;
|
||||
pub use project_search::ProjectSearchView;
|
||||
use ui::{prelude::*, Tooltip};
|
||||
@@ -53,10 +53,10 @@ bitflags! {
|
||||
impl SearchOptions {
|
||||
pub fn label(&self) -> &'static str {
|
||||
match *self {
|
||||
SearchOptions::WHOLE_WORD => "Match whole words",
|
||||
SearchOptions::CASE_SENSITIVE => "Match case sensitively",
|
||||
SearchOptions::WHOLE_WORD => "Match Whole Words",
|
||||
SearchOptions::CASE_SENSITIVE => "Match Case Sensitively",
|
||||
SearchOptions::INCLUDE_IGNORED => "Also search files ignored by configuration",
|
||||
SearchOptions::REGEX => "Use regular expressions",
|
||||
SearchOptions::REGEX => "Use Regular Expressions",
|
||||
_ => panic!("{:?} is not a named SearchOption", self),
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@ impl SearchOptions {
|
||||
pub fn as_button(
|
||||
&self,
|
||||
active: bool,
|
||||
focus_handle: FocusHandle,
|
||||
action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> impl IntoElement {
|
||||
IconButton::new(self.label(), self.icon())
|
||||
@@ -115,7 +116,7 @@ impl SearchOptions {
|
||||
.tooltip({
|
||||
let action = self.to_toggle_action();
|
||||
let label = self.label();
|
||||
move |cx| Tooltip::for_action(label, &*action, cx)
|
||||
move |cx| Tooltip::for_action_in(label, &*action, &focus_handle, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui::{Action, IntoElement};
|
||||
use gpui::{Action, FocusHandle, IntoElement};
|
||||
use ui::IconButton;
|
||||
use ui::{prelude::*, Tooltip};
|
||||
|
||||
@@ -7,12 +7,13 @@ pub(super) fn render_nav_button(
|
||||
active: bool,
|
||||
tooltip: &'static str,
|
||||
action: &'static dyn Action,
|
||||
focus_handle: FocusHandle,
|
||||
) -> impl IntoElement {
|
||||
IconButton::new(
|
||||
SharedString::from(format!("search-nav-button-{}", action.name())),
|
||||
icon,
|
||||
)
|
||||
.on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
.tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
|
||||
.tooltip(move |cx| Tooltip::for_action_in(tooltip, action, &focus_handle, cx))
|
||||
.disabled(!active)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ pub enum ComponentStory {
|
||||
impl ComponentStory {
|
||||
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
|
||||
match self {
|
||||
Self::ApplicationMenu => cx.new_view(|_| title_bar::ApplicationMenuStory).into(),
|
||||
Self::ApplicationMenu => cx
|
||||
.new_view(|cx| title_bar::ApplicationMenuStory::new(cx))
|
||||
.into(),
|
||||
Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
|
||||
Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
|
||||
Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user