Compare commits
6 Commits
v0.153.0-p
...
v0.152.2-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1705af1684 | ||
|
|
08d8ab1121 | ||
|
|
9981f3614c | ||
|
|
1ff6ef3fa7 | ||
|
|
c304bf9a2c | ||
|
|
ac93366295 |
@@ -1,8 +1,8 @@
|
||||
We have two cloudflare workers that let us serve some assets of this repo
|
||||
from Cloudflare.
|
||||
|
||||
- `open-source-website-assets` is used for `install.sh`
|
||||
- `docs-proxy` is used for `https://zed.dev/docs`
|
||||
* `open-source-website-assets` is used for `install.sh`
|
||||
* `docs-proxy` is used for `https://zed.dev/docs`
|
||||
|
||||
On push to `main`, both of these (and the files they depend on) are uploaded to Cloudflare.
|
||||
|
||||
|
||||
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -232,20 +232,20 @@ jobs:
|
||||
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
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.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
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
bundle-linux-aarch64: # this runs on ubuntu22.04
|
||||
bundle-linux-aarch64:
|
||||
timeout-minutes: 60
|
||||
name: Create arm64 Linux bundle
|
||||
runs-on:
|
||||
@@ -360,8 +360,8 @@ jobs:
|
||||
- name: Set up Clang
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y llvm-15 clang-15 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-15/bin" >> $GITHUB_PATH
|
||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
|
||||
with:
|
||||
@@ -406,7 +406,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5
|
||||
- uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
architecture: "x64"
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5
|
||||
- uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
architecture: "x64"
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"hard_tabs": false,
|
||||
"formatter": "auto",
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
"ensure_final_newline_on_save": true
|
||||
|
||||
52
Cargo.lock
generated
52
Cargo.lock
generated
@@ -243,6 +243,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"collections",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
@@ -832,9 +833,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-stripe"
|
||||
version = "0.39.1"
|
||||
version = "0.38.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58d670cf4d47a1b8ffef54286a5625382e360a34ee76902fd93ad8c7032a0c30"
|
||||
checksum = "97ddaa6999d246ba2c6c84d830a1ba0cd16c9234d58701988b3869f0e5bd732d"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
@@ -3284,7 +3285,7 @@ version = "3.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.29.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -4928,7 +4929,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.30",
|
||||
"http_client",
|
||||
"isahc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -6138,7 +6138,7 @@ dependencies = [
|
||||
"lsp",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rpc",
|
||||
@@ -6658,7 +6658,7 @@ dependencies = [
|
||||
"linkify",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
@@ -6678,8 +6678,7 @@ dependencies = [
|
||||
"linkify",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"settings",
|
||||
"pulldown-cmark",
|
||||
"theme",
|
||||
"ui",
|
||||
"workspace",
|
||||
@@ -6774,7 +6773,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"opener",
|
||||
"pathdiff",
|
||||
"pulldown-cmark 0.10.3",
|
||||
"pulldown-cmark",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -7068,6 +7067,18 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
@@ -8594,17 +8605,6 @@ dependencies = [
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "666f0f59e259aea2d72e6012290c09877a780935cc3c18b1ceded41f3890d59c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-escape"
|
||||
version = "0.10.1"
|
||||
@@ -9183,7 +9183,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"linkify",
|
||||
"pulldown-cmark 0.12.1",
|
||||
"pulldown-cmark",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -14155,7 +14155,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix",
|
||||
"nix 0.29.0",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
@@ -14197,7 +14197,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.153.0"
|
||||
version = "0.152.2"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -14253,7 +14253,7 @@ dependencies = [
|
||||
"markdown_preview",
|
||||
"menu",
|
||||
"mimalloc",
|
||||
"nix",
|
||||
"nix 0.28.0",
|
||||
"node_runtime",
|
||||
"notifications",
|
||||
"outline",
|
||||
@@ -14484,14 +14484,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_svelte"
|
||||
version = "0.1.1"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_terraform"
|
||||
version = "0.1.0"
|
||||
version = "0.0.4"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
16
Cargo.toml
16
Cargo.toml
@@ -365,7 +365,7 @@ linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nix = "0.29"
|
||||
nix = "0.28"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
ordered-float = "2.1.1"
|
||||
@@ -378,7 +378,7 @@ pretty_assertions = "1.3.0"
|
||||
prost = "0.9"
|
||||
prost-build = "0.9"
|
||||
prost-types = "0.9"
|
||||
pulldown-cmark = { version = "0.12.0", default-features = false }
|
||||
pulldown-cmark = { version = "0.10.0", default-features = false }
|
||||
rand = "0.8.5"
|
||||
regex = "1.5"
|
||||
repair_json = "0.1.0"
|
||||
@@ -386,6 +386,7 @@ rsa = "0.9.6"
|
||||
runtimelib = { version = "0.15", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
@@ -466,7 +467,7 @@ which = "6.0.0"
|
||||
wit-component = "0.201"
|
||||
|
||||
[workspace.dependencies.async-stripe]
|
||||
version = "0.39"
|
||||
version = "0.38"
|
||||
default-features = false
|
||||
features = [
|
||||
"runtime-tokio-hyper-rustls",
|
||||
@@ -572,17 +573,8 @@ single_range_in_vec_init = "allow"
|
||||
# allow all of those, for now.
|
||||
style = { level = "allow", priority = -1 }
|
||||
|
||||
# Temporary list of style lints that we've fixed so far.
|
||||
module_inception = { level = "deny" }
|
||||
question_mark = { level = "deny" }
|
||||
redundant_closure = { level = "deny" }
|
||||
# Individual rules that have violations in the codebase:
|
||||
type_complexity = "allow"
|
||||
# We often return trait objects from `new` functions.
|
||||
new_ret_no_self = { level = "allow" }
|
||||
# We have a few `next` functions that differ in lifetimes
|
||||
# compared to Iterator::next. Yet, clippy complains about those.
|
||||
should_implement_trait = { level = "allow" }
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.81-bookworm as builder
|
||||
FROM rust:1.80-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
|
||||
|
||||
---
|
||||
--------
|
||||
|
||||
### Installation
|
||||
|
||||
|
||||
<a href="https://repology.org/project/zed-editor/versions">
|
||||
<img src="https://repology.org/badge/vertical-allrepos/zed-editor.svg?minversion=0.143.5" alt="Packaging status" align="right">
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 10V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.71429 7.088C6.7142 7.26164 6.66963 7.43181 6.58559 7.57938C6.50156 7.72695 6.38138 7.84606 6.23857 7.92333L5.47571 8.34333C5.33291 8.4206 5.21273 8.53972 5.12869 8.68729C5.04465 8.83486 5.00008 9.00503 5 9.17867V9.53333C5 9.6571 5.04515 9.7758 5.12553 9.86332C5.2059 9.95083 5.31491 10 5.42857 10H10.5714C10.6851 10 10.7941 9.95083 10.8745 9.86332C10.9548 9.7758 11 9.6571 11 9.53333V9.17867C10.9999 9.00503 10.9553 8.83486 10.8713 8.68729C10.7873 8.53972 10.6671 8.4206 10.5243 8.34333L9.76143 7.92333C9.61862 7.84606 9.49844 7.72695 9.41441 7.57938C9.33037 7.43181 9.2858 7.26164 9.28571 7.088V5.33333C9.28571 5.20957 9.33087 5.09087 9.41124 5.00335C9.49161 4.91583 9.60062 4.86667 9.71429 4.86667C9.94161 4.86667 10.1596 4.76833 10.3204 4.5933C10.4811 4.41827 10.5714 4.18087 10.5714 3.93333C10.5714 3.6858 10.4811 3.4484 10.3204 3.27337C10.1596 3.09833 9.94161 3 9.71429 3H6.28571C6.05839 3 5.84037 3.09833 5.67962 3.27337C5.51888 3.4484 5.42857 3.6858 5.42857 3.93333C5.42857 4.18087 5.51888 4.41827 5.67962 4.5933C5.84037 4.76833 6.05839 4.86667 6.28571 4.86667C6.39938 4.86667 6.50839 4.91583 6.58876 5.00335C6.66913 5.09087 6.71429 5.20957 6.71429 5.33333V7.088Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 447 B |
@@ -1,4 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.28561 2.25C5.83777 2.25 5.42262 2.44429 5.12712 2.76607C4.8336 3.08568 4.67847 3.50652 4.67847 3.93333C4.67847 4.00474 4.68281 4.07598 4.69142 4.14661L10.0356 6.63864V5.58279C10.3579 5.51401 10.6495 5.3435 10.8728 5.10053C11.1662 4.78092 11.3213 4.36012 11.3213 3.93333C11.3213 3.50655 11.1661 3.08559 10.8726 2.76598C10.5771 2.44432 10.1621 2.25 9.71419 2.25H6.28561Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.70131 4.68303C1.87636 4.30763 2.3226 4.14521 2.698 4.32027L13.9361 9.5607C14.3115 9.73575 14.474 10.182 14.2989 10.5574C14.1239 10.9328 13.6776 11.0952 13.3022 10.9202L11.6006 10.1267C11.5536 10.2137 11.4958 10.2957 11.4272 10.3704C11.2121 10.6045 10.9057 10.75 10.5715 10.75H8.75007V13C8.75007 13.4142 8.41428 13.75 8.00007 13.75C7.58585 13.75 7.25007 13.4142 7.25007 13V10.75H5.42864C5.09445 10.75 4.78832 10.6049 4.57321 10.3706C4.36002 10.1385 4.25007 9.83637 4.25007 9.53334V9.17868C4.25021 8.87805 4.32715 8.57932 4.47703 8.31614C4.6266 8.05351 4.84546 7.83234 5.11609 7.68521L5.68873 7.36993L2.06407 5.67973C1.68867 5.50467 1.52625 5.05844 1.70131 4.68303Z" fill="black"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin-off"><path d="M12 17v5"/><path d="M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89"/><path d="m2 2 20 20"/><path d="M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 401 B |
@@ -169,8 +169,7 @@
|
||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
"ctrl-k h": "assistant::DeployHistory",
|
||||
"ctrl-k l": "assistant::DeployPromptLibrary",
|
||||
"ctrl-n": "assistant::NewContext"
|
||||
"ctrl-k l": "assistant::DeployPromptLibrary"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -262,8 +261,7 @@
|
||||
"alt-r": "search::ToggleRegex",
|
||||
"alt-ctrl-f": "project_search::ToggleFilters",
|
||||
"ctrl-alt-shift-r": "search::ToggleRegex",
|
||||
"ctrl-alt-shift-x": "search::ToggleRegex",
|
||||
"ctrl-k shift-enter": "pane::TogglePinTab"
|
||||
"ctrl-alt-shift-x": "search::ToggleRegex"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
@@ -553,7 +551,6 @@
|
||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -191,8 +191,7 @@
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-m": "assistant::ToggleModelSelector",
|
||||
"cmd-k h": "assistant::DeployHistory",
|
||||
"cmd-k l": "assistant::DeployPromptLibrary",
|
||||
"cmd-n": "assistant::NewContext"
|
||||
"cmd-k l": "assistant::DeployPromptLibrary"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -299,8 +298,7 @@
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-f": "project_search::ToggleFilters",
|
||||
"alt-cmd-x": "search::ToggleRegex",
|
||||
"cmd-k shift-enter": "pane::TogglePinTab"
|
||||
"alt-cmd-x": "search::ToggleRegex"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
@@ -563,8 +561,8 @@
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
"shift-g": "vim::EndOfDocument",
|
||||
"{": "vim::StartOfParagraph",
|
||||
"}": "vim::EndOfParagraph",
|
||||
"(": "vim::SentenceBackward",
|
||||
")": "vim::SentenceForward",
|
||||
"|": "vim::GoToColumn",
|
||||
// Word motions
|
||||
"w": "vim::NextWordStart",
|
||||
@@ -214,7 +212,7 @@
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"shift-y": "vim::YankToEndOfLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
@@ -326,13 +324,6 @@
|
||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
@@ -401,9 +392,7 @@
|
||||
"context": "vim_operator == d",
|
||||
"bindings": {
|
||||
"d": "vim::CurrentLine",
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||
"o": "editor::ToggleHunkDiff", // "d o"
|
||||
"p": "editor::RevertSelectedHunks" // "d p"
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -493,7 +482,6 @@
|
||||
"v": "project_panel::OpenPermanent",
|
||||
"p": "project_panel::Open",
|
||||
"x": "project_panel::RevealInFileManager",
|
||||
"s": "project_panel::OpenWithSystem",
|
||||
"shift-g": "menu::SelectLast",
|
||||
"g g": "menu::SelectFirst",
|
||||
"-": "project_panel::SelectParent",
|
||||
|
||||
@@ -279,13 +279,6 @@
|
||||
"relative_line_numbers": false,
|
||||
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
|
||||
"search_wrap": true,
|
||||
// Search options to enable by default when opening new project and buffer searches.
|
||||
"search": {
|
||||
"whole_word": false,
|
||||
"case_sensitive": false,
|
||||
"include_ignored": false,
|
||||
"regex": false
|
||||
},
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
// This setting can take the following three values:
|
||||
//
|
||||
@@ -296,7 +289,6 @@
|
||||
// 3. Never populate the search query
|
||||
// "never"
|
||||
"seed_search_query_from_cursor": "always",
|
||||
"use_smartcase_search": false,
|
||||
// Inlay hint related settings
|
||||
"inlay_hints": {
|
||||
// Global switch to toggle hints on and off, switched off by default.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
allow-private-module-inception = true
|
||||
@@ -18,6 +18,7 @@ path = "src/anthropic.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
isahc.workspace = true
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::{pin::Pin, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::HashMap;
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use isahc::config::Configurable;
|
||||
@@ -12,11 +13,11 @@ use isahc::http::{HeaderMap, HeaderValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString};
|
||||
use thiserror::Error;
|
||||
use util::ResultExt as _;
|
||||
use util::{maybe, ResultExt as _};
|
||||
|
||||
pub use supported_countries::*;
|
||||
|
||||
pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
|
||||
pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
@@ -331,6 +332,94 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_content_from_events(
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<Event, AnthropicError>>>>,
|
||||
) -> impl Stream<Item = Result<ResponseContent, AnthropicError>> {
|
||||
struct RawToolUse {
|
||||
id: String,
|
||||
name: String,
|
||||
input_json: String,
|
||||
}
|
||||
|
||||
struct State {
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<Event, AnthropicError>>>>,
|
||||
tool_uses_by_index: HashMap<usize, RawToolUse>,
|
||||
}
|
||||
|
||||
futures::stream::unfold(
|
||||
State {
|
||||
events,
|
||||
tool_uses_by_index: HashMap::default(),
|
||||
},
|
||||
|mut state| async move {
|
||||
while let Some(event) = state.events.next().await {
|
||||
match event {
|
||||
Ok(event) => match event {
|
||||
Event::ContentBlockStart {
|
||||
index,
|
||||
content_block,
|
||||
} => match content_block {
|
||||
ResponseContent::Text { text } => {
|
||||
return Some((Some(Ok(ResponseContent::Text { text })), state));
|
||||
}
|
||||
ResponseContent::ToolUse { id, name, .. } => {
|
||||
state.tool_uses_by_index.insert(
|
||||
index,
|
||||
RawToolUse {
|
||||
id,
|
||||
name,
|
||||
input_json: String::new(),
|
||||
},
|
||||
);
|
||||
|
||||
return Some((None, state));
|
||||
}
|
||||
},
|
||||
Event::ContentBlockDelta { index, delta } => match delta {
|
||||
ContentDelta::TextDelta { text } => {
|
||||
return Some((Some(Ok(ResponseContent::Text { text })), state));
|
||||
}
|
||||
ContentDelta::InputJsonDelta { partial_json } => {
|
||||
if let Some(tool_use) = state.tool_uses_by_index.get_mut(&index) {
|
||||
tool_use.input_json.push_str(&partial_json);
|
||||
return Some((None, state));
|
||||
}
|
||||
}
|
||||
},
|
||||
Event::ContentBlockStop { index } => {
|
||||
if let Some(tool_use) = state.tool_uses_by_index.remove(&index) {
|
||||
return Some((
|
||||
Some(maybe!({
|
||||
Ok(ResponseContent::ToolUse {
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: serde_json::Value::from_str(
|
||||
&tool_use.input_json,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?,
|
||||
})
|
||||
})),
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
Event::Error { error } => {
|
||||
return Some((Some(Err(AnthropicError::ApiError(error))), state));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => {
|
||||
return Some((Some(Err(err)), state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
},
|
||||
)
|
||||
.filter_map(|event| async move { event })
|
||||
}
|
||||
|
||||
pub async fn extract_tool_args_from_events(
|
||||
tool_name: String,
|
||||
mut events: Pin<Box<dyn Send + Stream<Item = Result<Event>>>>,
|
||||
@@ -339,12 +428,14 @@ pub async fn extract_tool_args_from_events(
|
||||
while let Some(event) = events.next().await {
|
||||
if let Event::ContentBlockStart {
|
||||
index,
|
||||
content_block: ResponseContent::ToolUse { name, .. },
|
||||
content_block,
|
||||
} = event?
|
||||
{
|
||||
if name == tool_name {
|
||||
tool_use_index = Some(index);
|
||||
break;
|
||||
if let ResponseContent::ToolUse { name, .. } = content_block {
|
||||
if name == tool_name {
|
||||
tool_use_index = Some(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,14 +512,6 @@ pub enum RequestContent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
cache_control: Option<CacheControl>,
|
||||
},
|
||||
#[serde(rename = "tool_result")]
|
||||
ToolResult {
|
||||
tool_use_id: String,
|
||||
is_error: bool,
|
||||
content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
cache_control: Option<CacheControl>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -608,6 +691,9 @@ impl ApiError {
|
||||
}
|
||||
|
||||
pub fn is_rate_limit_error(&self) -> bool {
|
||||
matches!(self.error_type.as_str(), "rate_limit_error")
|
||||
match self.error_type.as_str() {
|
||||
"rate_limit_error" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
63
crates/assistant/README.zmd
Normal file
63
crates/assistant/README.zmd
Normal file
@@ -0,0 +1,63 @@
|
||||
> Hello, I'm Nathan Sobo.
|
||||
I'm currently your sole creator and the leader of Zed Industries.
|
||||
Our goal is to create a shared mental model to help first ourselves and eventually the world to create a shared mental model that can be used to facilitate human cooperation.
|
||||
|
||||
< Hello, Nathan! Welcome back to Zed. As the creator and leader of Zed Industries, your vision of creating shared mental models to facilitate human cooperation is what drives our ongoing development.
|
||||
|
||||
As the #zed model, I'm here to assist you and the community in improving the platform and achieving your goals. Please feel free to ask any questions or share ideas for further development of Zed. Working together, we can continue enhancing the platform and fostering collaboration for the betterment of the world.
|
||||
|
||||
> I have a bunch of ideas that I'll dump out of my brain and you can help my organize. Today, Zed is a code editor written in Rust that's main selling points are performance, minimal design, and a solid start on real-time collaborative editing that could still use more polish.
|
||||
|
||||
We've decided that our next milestone will be open-sourcing Zed on the Zed platform.
|
||||
|
||||
< Nathan, it's exciting to hear that you have more ideas to enhance Zed! Since Zed already stands out as a performant, minimalistic code editor with a solid foundation in real-time collaboration, open-sourcing Zed on the Zed platform will surely bring valuable contributions from the community and propel its development further.
|
||||
|
||||
Let's organize your ideas and create a plan for your next milestone. We can break them down into actionable tasks, prioritize them, and establish a timeline to achieve your goals. Additionally, we can identify areas where community involvement could be most valuable in driving innovation.
|
||||
|
||||
Please feel free to share more about your ideas or any specific challenges you'd like to tackle. I'm here to help you turn these ideas into a cohesive and successful plan for Zed's future!
|
||||
|
||||
@nathan> #zed, can you describe the conversation UI as you currently understand it.
|
||||
|
||||
< Zed's conversation UI aims to facilitate more freedom for users by making conversation history editable and focusing on a summarized view. Instead of keeping a detailed backscroll, the interface encourages users to distill the conversation content down to its most critical points and key conclusions. This approach ensures that the vital information from the conversation is highlighted and easily accessible, while still maintaining a history that can be linked to when necessary.
|
||||
|
||||
Addressing @nate's concerns, this design choice aims to strike a balance between readability, usability, and information preservation. The summarized view maintains the essence of the conversation without being burdened by extensive backscroll. Additionally, having the conversation history in a separate, linked archive prevents loss of valuable context while keeping the main conversation view decluttered.
|
||||
|
||||
@nathan> What details around the conversation view are worth exploring?
|
||||
|
||||
< @nathan, there are several aspects of the conversation view that are worth exploring and refining:
|
||||
|
||||
1. Editing functionality: Ensuring that all participants can edit and refine the conversation summary effectively while preserving the original context.
|
||||
2. Permissions and access control: Determining how to manage editing rights, user roles, and visibility of the conversation summary and history.
|
||||
3. Navigation and linking: Implementing an intuitive system for navigating between the summarized conversation and linked history or reference materials.
|
||||
4. Visualization and layout: Designing the interface to clearly present the summarized information while providing a visually engaging and organized experience for participants.
|
||||
5. Threaded conversations and branching: Exploring ways to handle sub-conversations, supplementary discussions, or debates within the primary conversation view.
|
||||
|
||||
Considering these aspects will ensure our conversation view design is optimized for usability, collaboration, and accessibility in Zed.
|
||||
|
||||
@nathan> @nate, which of these would you like to explore?
|
||||
|
||||
@nate> 2 feels like it isn't important at the moment, we can explore that later. Let's start with 4, which I think will lead us to discussion 3 and 5.
|
||||
|
||||
#zed share your thoughts on the points we need to consider to design a layout and visualization for a conversation view between you (#zed) and multiple people, or between multiple people and multiple bots (you and other bots).
|
||||
|
||||
@nathan> Agreed. I'm interested in threading I think more than anything. Or 4 yeah. I think we need to scope the threading conversation. Also, asking #zed to propose the solution... not sure it will be that effective but it's worth a try...
|
||||
|
||||
#zed describe a hypothetical conversation UI that meets the description.
|
||||
|
||||
< @nate and @nathan, I understand that you'd like to focus on the layout and visualization for a conversation view that can accommodate multiple people and potentially multiple bots as well. Here's a proposal for a conversation UI that meets the requirements:
|
||||
|
||||
1. Main conversation area: Display the summarized conversation content in a clean and organized manner, ensuring readability and efficient use of space.
|
||||
|
||||
2. Participant avatars and labels: Include user avatars and/or labels to distinguish between users and bots visually, allowing easy identification of each participant's contributions.
|
||||
|
||||
3. Active threads indicator: Designate a clear indicator for ongoing threads, which will attract attention to active sub-conversations or related discussions in the conversation view.
|
||||
|
||||
4. Dynamic branching: Implement a branching visual structure to present threaded conversations, clearly distinguishing sub-conversations, and enabling users to navigate between them smoothly.
|
||||
|
||||
5. Multiple bot interaction support: Facilitate interactions between multiple bots through user-invoked prompts or bot-to-bot communication channels without disrupting the primary conversation flow.
|
||||
|
||||
6. Expandable conversation history: Offer a separate, expandable section or panel to access historical content without cluttering the main conversation view.
|
||||
|
||||
7. Linking and referencing: Implement a system for hyperlinking relevant information, resources, or previous conversations to the current discussion, making it easy for participants to follow and reference the shared context.
|
||||
|
||||
This hypothetical conversation UI aims to address the layout and visualization, threading, and multi-participant aspects of your requirements. Of course, it's just an initial proposal, and we can refine and iterate on it based on your feedback and specific needs.
|
||||
@@ -27,8 +27,8 @@ use context_servers::ContextServerRegistry;
|
||||
pub use context_store::*;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::Context as _;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use gpui::{impl_actions, Context as _};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
@@ -45,7 +45,6 @@ use slash_command::{
|
||||
file_command, now_command, project_command, prompt_command, search_command, symbols_command,
|
||||
tab_command, terminal_command, workflow_command,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
pub(crate) use streaming_diff::*;
|
||||
use util::ResultExt;
|
||||
@@ -71,14 +70,6 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize)]
|
||||
pub enum InsertDraggedFiles {
|
||||
ProjectPaths(Vec<PathBuf>),
|
||||
ExternalFiles(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
impl_actions!(assistant, [InsertDraggedFiles]);
|
||||
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
|
||||
@@ -6,28 +6,27 @@ use crate::{
|
||||
slash_command::{
|
||||
default_command::DefaultSlashCommand,
|
||||
docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
|
||||
file_command::{self, codeblock_fence_for_path},
|
||||
file_command::codeblock_fence_for_path,
|
||||
SlashCommandCompletionProvider, SlashCommandRegistry,
|
||||
},
|
||||
slash_command_picker,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, CacheStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
|
||||
ContextStoreEvent, CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId,
|
||||
InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId, MessageMetadata,
|
||||
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand,
|
||||
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split,
|
||||
ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
|
||||
InlineAssistant, InsertIntoEditor, Message, MessageId, MessageMetadata, MessageStatus,
|
||||
ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand, PendingSlashCommandStatus,
|
||||
QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split, ToggleFocus,
|
||||
ToggleModelSelector, WorkflowStepResolution,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||
use assistant_tool::ToolRegistry;
|
||||
use client::{proto, Client, Status};
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use editor::{
|
||||
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||
display_map::{
|
||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CreaseMetadata,
|
||||
CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
|
||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CustomBlockId, FoldId,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
|
||||
Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
|
||||
@@ -37,10 +36,10 @@ use fs::Fs;
|
||||
use gpui::{
|
||||
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
|
||||
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
|
||||
Context as _, Empty, Entity, EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusableView,
|
||||
FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render,
|
||||
RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||
Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
|
||||
Context as _, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, FontWeight,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render, RenderImage,
|
||||
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
||||
UpdateGlobal, View, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
@@ -52,18 +51,12 @@ use language_model::{
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{Project, ProjectLspAdapterDelegate, Worktree};
|
||||
use project::{Project, ProjectLspAdapterDelegate};
|
||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp,
|
||||
collections::hash_map,
|
||||
ops::{ControlFlow, Range},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
borrow::Cow, cmp, collections::hash_map, fmt::Write, ops::Range, path::PathBuf, sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
@@ -74,16 +67,16 @@ use ui::{
|
||||
Avatar, AvatarShape, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
|
||||
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt};
|
||||
use util::ResultExt;
|
||||
use workspace::searchable::SearchableItemHandle;
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
item::{self, FollowableItem, Item, ItemHandle},
|
||||
pane::{self, SaveIntent},
|
||||
searchable::{SearchEvent, SearchableItem},
|
||||
DraggedSelection, Pane, Save, ShowConfiguration, ToggleZoom, ToolbarItemEvent,
|
||||
ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
Pane, Save, ShowConfiguration, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation,
|
||||
ToolbarItemView, Workspace,
|
||||
};
|
||||
use workspace::{searchable::SearchableItemHandle, DraggedTab};
|
||||
use zed_actions::InlineAssist;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
@@ -102,7 +95,6 @@ pub fn init(cx: &mut AppContext) {
|
||||
.register_action(AssistantPanel::inline_assist)
|
||||
.register_action(ContextEditor::quote_selection)
|
||||
.register_action(ContextEditor::insert_selection)
|
||||
.register_action(ContextEditor::insert_dragged_files)
|
||||
.register_action(AssistantPanel::show_configuration)
|
||||
.register_action(AssistantPanel::create_new_context);
|
||||
},
|
||||
@@ -329,7 +321,7 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
let model_summary_editor = cx.new_view(Editor::single_line);
|
||||
let model_summary_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
let context_editor_toolbar = cx.new_view(|_| {
|
||||
ContextEditorToolbarItem::new(
|
||||
workspace,
|
||||
@@ -347,62 +339,6 @@ impl AssistantPanel {
|
||||
NewContext.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let project = workspace.project().clone();
|
||||
pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| {
|
||||
let action = maybe!({
|
||||
if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
|
||||
return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
|
||||
}
|
||||
|
||||
let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
|
||||
{
|
||||
if &tab.pane == cx.view() {
|
||||
return None;
|
||||
}
|
||||
let item = tab.pane.read(cx).item_for_index(tab.ix);
|
||||
Some(
|
||||
item.and_then(|item| item.project_path(cx))
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
|
||||
{
|
||||
Some(
|
||||
selection
|
||||
.items()
|
||||
.filter_map(|item| {
|
||||
project.read(cx).path_for_entry(item.entry_id, cx)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}?;
|
||||
|
||||
let paths = project_paths
|
||||
.into_iter()
|
||||
.filter_map(|project_path| {
|
||||
let worktree = project
|
||||
.read(cx)
|
||||
.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
|
||||
let mut full_path = PathBuf::from(worktree.read(cx).root_name());
|
||||
full_path.push(&project_path.path);
|
||||
Some(full_path)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(InsertDraggedFiles::ProjectPaths(paths))
|
||||
});
|
||||
|
||||
if let Some(action) = action {
|
||||
cx.dispatch_action(action.boxed_clone());
|
||||
}
|
||||
|
||||
ControlFlow::Break(())
|
||||
});
|
||||
|
||||
pane.set_can_split(false, cx);
|
||||
pane.set_can_navigate(true, cx);
|
||||
pane.display_nav_history_buttons(None);
|
||||
@@ -418,16 +354,8 @@ impl AssistantPanel {
|
||||
cx.dispatch_action(DeployHistory.boxed_clone())
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Open History",
|
||||
&DeployHistory,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in("Open History", &DeployHistory, &focus_handle, cx)
|
||||
})
|
||||
.selected(
|
||||
pane.active_item()
|
||||
@@ -443,14 +371,7 @@ impl AssistantPanel {
|
||||
cx.dispatch_action(NewContext.boxed_clone())
|
||||
}),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Context",
|
||||
&NewContext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
.tooltip(|cx| Tooltip::for_action("New Context", &NewContext, cx)),
|
||||
)
|
||||
.child(
|
||||
PopoverMenu::new("assistant-panel-popover-menu")
|
||||
@@ -939,16 +860,9 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
let did_create_context = panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.new_context(cx)?;
|
||||
|
||||
Some(())
|
||||
})
|
||||
.is_some();
|
||||
if did_create_context {
|
||||
ContextEditor::quote_selection(workspace, &Default::default(), cx);
|
||||
}
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.new_context(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1104,7 +1018,7 @@ impl AssistantPanel {
|
||||
pane.activate_item(configuration_item_ix, true, true, cx);
|
||||
});
|
||||
} else {
|
||||
let configuration = cx.new_view(ConfigurationView::new);
|
||||
let configuration = cx.new_view(|cx| ConfigurationView::new(cx));
|
||||
self.configuration_subscription = Some(cx.subscribe(
|
||||
&configuration,
|
||||
|this, _, event: &ConfigurationViewEvent, cx| match event {
|
||||
@@ -1511,12 +1425,6 @@ pub struct ContextEditor {
|
||||
show_accept_terms: bool,
|
||||
pub(crate) slash_menu_handle:
|
||||
PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>,
|
||||
// dragged_file_worktrees is used to keep references to worktrees that were added
|
||||
// when the user drag/dropped an external file onto the context editor. Since
|
||||
// the worktree is not part of the project panel, it would be dropped as soon as
|
||||
// the file is opened. In order to keep the worktree alive for the duration of the
|
||||
// context editor, we keep a reference here.
|
||||
dragged_file_worktrees: Vec<Model<Worktree>>,
|
||||
}
|
||||
|
||||
const DEFAULT_TAB_TITLE: &str = "New Context";
|
||||
@@ -1581,7 +1489,6 @@ impl ContextEditor {
|
||||
error_message: None,
|
||||
show_accept_terms: false,
|
||||
slash_menu_handle: Default::default(),
|
||||
dragged_file_worktrees: Vec::new(),
|
||||
};
|
||||
this.update_message_headers(cx);
|
||||
this.update_image_blocks(cx);
|
||||
@@ -2184,68 +2091,6 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextEvent::UsePendingTools => {
|
||||
let pending_tool_uses = self
|
||||
.context
|
||||
.read(cx)
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.filter(|tool_use| tool_use.status.is_idle())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for tool_use in pending_tool_uses {
|
||||
let tool_registry = ToolRegistry::global(cx);
|
||||
if let Some(tool) = tool_registry.tool(&tool_use.name) {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_tool_output(tool_use.id.clone(), task, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
output_range,
|
||||
} => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
|
||||
let excerpt_id = *excerpt_id;
|
||||
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.view().downgrade(),
|
||||
IconName::PocketKnife,
|
||||
format!("Tool Result: {tool_use_id}").into(),
|
||||
),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
};
|
||||
let render_trailer =
|
||||
move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, output_range.start)
|
||||
.unwrap();
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, output_range.end)
|
||||
.unwrap();
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
|
||||
let crease = Crease::new(
|
||||
start..end,
|
||||
placeholder,
|
||||
fold_toggle("tool-use"),
|
||||
render_trailer,
|
||||
);
|
||||
|
||||
editor.insert_creases([crease], cx);
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
});
|
||||
}
|
||||
ContextEvent::Operation(_) => {}
|
||||
ContextEvent::ShowAssistError(error_message) => {
|
||||
self.error_message = Some(error_message.clone());
|
||||
@@ -2498,26 +2343,20 @@ impl ContextEditor {
|
||||
.unwrap();
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
creases.push(
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.view().downgrade(),
|
||||
section.icon,
|
||||
section.label.clone(),
|
||||
),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any_element(),
|
||||
)
|
||||
.with_metadata(CreaseMetadata {
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
}),
|
||||
);
|
||||
creases.push(Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.view().downgrade(),
|
||||
section.icon,
|
||||
section.label.clone(),
|
||||
),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any_element(),
|
||||
));
|
||||
}
|
||||
|
||||
editor.insert_creases(creases, cx);
|
||||
@@ -3104,80 +2943,6 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_dragged_files(
|
||||
workspace: &mut Workspace,
|
||||
action: &InsertDraggedFiles,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = workspace.project().clone();
|
||||
|
||||
let paths = match action {
|
||||
InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])),
|
||||
InsertDraggedFiles::ExternalFiles(paths) => {
|
||||
let tasks = paths
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn(move |_, cx| async move {
|
||||
let mut paths = vec![];
|
||||
let mut worktrees = vec![];
|
||||
|
||||
let opened_paths = futures::future::join_all(tasks).await;
|
||||
for (worktree, project_path) in opened_paths.into_iter().flatten() {
|
||||
let Ok(worktree_root_name) =
|
||||
worktree.read_with(&cx, |worktree, _| worktree.root_name().to_string())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut full_path = PathBuf::from(worktree_root_name.clone());
|
||||
full_path.push(&project_path.path);
|
||||
paths.push(full_path);
|
||||
worktrees.push(worktree);
|
||||
}
|
||||
|
||||
(paths, worktrees)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let (paths, dragged_file_worktrees) = paths.await;
|
||||
let cmd_name = file_command::FileSlashCommand.name();
|
||||
|
||||
context_editor_view
|
||||
.update(&mut cx, |context_editor, cx| {
|
||||
let file_argument = paths
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.insert("\n", cx);
|
||||
editor.insert(&format!("/{} {}", cmd_name, file_argument), cx);
|
||||
});
|
||||
|
||||
context_editor.confirm_command(&ConfirmCommand, cx);
|
||||
|
||||
context_editor
|
||||
.dragged_file_worktrees
|
||||
.extend(dragged_file_worktrees);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn quote_selection(
|
||||
workspace: &mut Workspace,
|
||||
_: &QuoteSelection,
|
||||
@@ -3193,93 +2958,87 @@ impl ContextEditor {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut creases = vec![];
|
||||
editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in selections {
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
..editor::ToOffset::to_offset(&selection.end, &buffer);
|
||||
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.code_fence_block_name())
|
||||
let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
|
||||
let editor = editor.read(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
..editor::ToOffset::to_offset(&selection.end, &buffer);
|
||||
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.code_fence_block_name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("");
|
||||
|
||||
let filename = buffer
|
||||
.file_at(selection.start)
|
||||
.map(|file| file.full_path(cx));
|
||||
|
||||
let text = if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
let start_symbols = buffer
|
||||
.symbols_containing(selection.start, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
let end_symbols = buffer
|
||||
.symbols_containing(selection.end, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
|
||||
let outline_text =
|
||||
if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
|
||||
Some(
|
||||
start_symbols
|
||||
.into_iter()
|
||||
.zip(end_symbols)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("");
|
||||
let filename = buffer
|
||||
.file_at(selection.start)
|
||||
.map(|file| file.full_path(cx));
|
||||
let text = if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
let start_symbols = buffer
|
||||
.symbols_containing(selection.start, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
let end_symbols = buffer
|
||||
.symbols_containing(selection.end, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
|
||||
let outline_text = if let Some((start_symbols, end_symbols)) =
|
||||
start_symbols.zip(end_symbols)
|
||||
{
|
||||
Some(
|
||||
start_symbols
|
||||
.into_iter()
|
||||
.zip(end_symbols)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let line_comment_prefix = start_language
|
||||
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
|
||||
|
||||
let line_comment_prefix = start_language
|
||||
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
|
||||
let fence = codeblock_fence_for_path(
|
||||
filename.as_deref(),
|
||||
Some(selection.start.row..selection.end.row),
|
||||
);
|
||||
|
||||
let fence = codeblock_fence_for_path(
|
||||
filename.as_deref(),
|
||||
Some(selection.start.row..selection.end.row),
|
||||
);
|
||||
|
||||
if let Some((line_comment_prefix, outline_text)) =
|
||||
line_comment_prefix.zip(outline_text)
|
||||
{
|
||||
let breadcrumb =
|
||||
format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
|
||||
format!("{fence}{breadcrumb}{selected_text}\n```")
|
||||
} else {
|
||||
format!("{fence}{selected_text}\n```")
|
||||
}
|
||||
};
|
||||
let crease_title = if let Some(path) = filename {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
if start_line == end_line {
|
||||
format!("{}, Line {}", path.display(), start_line)
|
||||
} else {
|
||||
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
|
||||
}
|
||||
} else {
|
||||
"Quoted selection".to_string()
|
||||
};
|
||||
creases.push((text, crease_title));
|
||||
if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text)
|
||||
{
|
||||
let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
|
||||
format!("{fence}{breadcrumb}{selected_text}\n```")
|
||||
} else {
|
||||
format!("{fence}{selected_text}\n```")
|
||||
}
|
||||
});
|
||||
if creases.is_empty() {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let crease_title = if let Some(path) = filename {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
if start_line == end_line {
|
||||
format!("{}, Line {}", path.display(), start_line)
|
||||
} else {
|
||||
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
|
||||
}
|
||||
} else {
|
||||
"Quoted selection".to_string()
|
||||
};
|
||||
|
||||
// Activate the panel
|
||||
if !panel.focus_handle(cx).contains_focused(cx) {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
@@ -3296,40 +3055,39 @@ impl ContextEditor {
|
||||
context.update(cx, |context, cx| {
|
||||
context.editor.update(cx, |editor, cx| {
|
||||
editor.insert("\n", cx);
|
||||
for (text, crease_title) in creases {
|
||||
let point = editor.selections.newest::<Point>(cx).head();
|
||||
let start_row = MultiBufferRow(point.row);
|
||||
|
||||
editor.insert(&text, cx);
|
||||
let point = editor.selections.newest::<Point>(cx).head();
|
||||
let start_row = MultiBufferRow(point.row);
|
||||
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_before = snapshot.anchor_after(point);
|
||||
let anchor_after = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.bias_left(&snapshot);
|
||||
editor.insert(&text, cx);
|
||||
|
||||
editor.insert("\n", cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_before = snapshot.anchor_after(point);
|
||||
let anchor_after = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.bias_left(&snapshot);
|
||||
|
||||
let fold_placeholder = quote_selection_fold_placeholder(
|
||||
crease_title,
|
||||
cx.view().downgrade(),
|
||||
);
|
||||
let crease = Crease::new(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
);
|
||||
editor.insert_creases(vec![crease], cx);
|
||||
editor.fold_at(
|
||||
&FoldAt {
|
||||
buffer_row: start_row,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.insert("\n", cx);
|
||||
|
||||
let fold_placeholder = quote_selection_fold_placeholder(
|
||||
crease_title,
|
||||
cx.view().downgrade(),
|
||||
);
|
||||
let crease = Crease::new(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
);
|
||||
editor.insert_creases(vec![crease], cx);
|
||||
editor.fold_at(
|
||||
&FoldAt {
|
||||
buffer_row: start_row,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
};
|
||||
@@ -3338,113 +3096,39 @@ 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);
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
copied_text,
|
||||
metadata,
|
||||
));
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
this.insert("", cx);
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
copied_text,
|
||||
metadata,
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
if range_relative_to_selection.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SelectedCreaseMetadata {
|
||||
range_relative_to_selection,
|
||||
crease: metadata.clone(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
|
||||
let editor = self.editor.read(cx);
|
||||
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 {
|
||||
break;
|
||||
} else if message.offset_range.end >= selection.range().start {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||
..cmp::min(message.offset_range.end, selection.range().end);
|
||||
if !range.is_empty() {
|
||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||
text.push_str(chunk);
|
||||
if editor.selections.count() == 1 {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let mut copied_text = String::new();
|
||||
let mut spanned_messages = 0;
|
||||
for message in context.messages(cx) {
|
||||
if message.offset_range.start >= selection.range().end {
|
||||
break;
|
||||
} else if message.offset_range.end >= selection.range().start {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||
..cmp::min(message.offset_range.end, selection.range().end);
|
||||
if !range.is_empty() {
|
||||
spanned_messages += 1;
|
||||
write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
|
||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||
copied_text.push_str(chunk);
|
||||
}
|
||||
copied_text.push('\n');
|
||||
}
|
||||
text.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if spanned_messages > 1 {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(copied_text));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
(text, CopyMetadata { creases })
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
||||
cx.stop_propagation();
|
||||
|
||||
fn paste(&mut self, _: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
||||
let images = if let Some(item) = cx.read_from_clipboard() {
|
||||
item.into_entries()
|
||||
.filter_map(|entry| {
|
||||
@@ -3459,62 +3143,9 @@ impl ContextEditor {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let metadata = if let Some(item) = cx.read_from_clipboard() {
|
||||
item.entries().first().and_then(|entry| {
|
||||
if let ClipboardEntry::String(text) = entry {
|
||||
text.metadata_json::<CopyMetadata>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if images.is_empty() {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let paste_position = editor.selections.newest::<usize>(cx).head();
|
||||
editor.paste(action, cx);
|
||||
|
||||
if let Some(metadata) = metadata {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let mut buffer_rows_to_fold = BTreeSet::new();
|
||||
let weak_editor = cx.view().downgrade();
|
||||
editor.insert_creases(
|
||||
metadata.creases.into_iter().map(|metadata| {
|
||||
let start = buffer.anchor_after(
|
||||
paste_position + metadata.range_relative_to_selection.start,
|
||||
);
|
||||
let end = buffer.anchor_before(
|
||||
paste_position + metadata.range_relative_to_selection.end,
|
||||
);
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
render: render_fold_icon_button(
|
||||
weak_editor.clone(),
|
||||
metadata.crease.icon,
|
||||
metadata.crease.label.clone(),
|
||||
),
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
)
|
||||
.with_metadata(metadata.crease.clone())
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
// If we didn't find any valid image data to paste, propagate to let normal pasting happen.
|
||||
cx.propagate();
|
||||
} else {
|
||||
let mut image_positions = Vec::new();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
@@ -4184,17 +3815,6 @@ fn render_fold_icon_button(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CopyMetadata {
|
||||
creases: Vec<SelectedCreaseMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct SelectedCreaseMetadata {
|
||||
range_relative_to_selection: Range<usize>,
|
||||
crease: CreaseMetadata,
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<SearchEvent> for ContextEditor {}
|
||||
|
||||
@@ -4220,7 +3840,6 @@ impl Render for ContextEditor {
|
||||
.capture_action(cx.listener(ContextEditor::cancel))
|
||||
.capture_action(cx.listener(ContextEditor::save))
|
||||
.capture_action(cx.listener(ContextEditor::copy))
|
||||
.capture_action(cx.listener(ContextEditor::cut))
|
||||
.capture_action(cx.listener(ContextEditor::paste))
|
||||
.capture_action(cx.listener(ContextEditor::cycle_message_role))
|
||||
.capture_action(cx.listener(ContextEditor::confirm_command))
|
||||
@@ -4381,7 +4000,8 @@ impl Item for ContextEditor {
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, Item::deactivated)
|
||||
self.editor
|
||||
.update(cx, |editor, cx| Item::deactivated(editor, cx))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,12 +160,10 @@ impl AssistantSettingsContent {
|
||||
.filter_map(|model| match model {
|
||||
OpenAiModel::Custom {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
} => Some(open_ai::AvailableModel {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
}),
|
||||
|
||||
@@ -29,7 +29,7 @@ use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, P
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, MessageContent, Role, StopReason,
|
||||
LanguageModelRequestTool, MessageContent, Role,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::{context_images_dir, contexts_dir};
|
||||
@@ -306,11 +306,6 @@ pub enum ContextEvent {
|
||||
run_commands_in_output: bool,
|
||||
expand_result: bool,
|
||||
},
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
tool_use_id: Arc<str>,
|
||||
output_range: Range<language::Anchor>,
|
||||
},
|
||||
Operation(ContextOperation),
|
||||
}
|
||||
|
||||
@@ -421,7 +416,6 @@ impl Message {
|
||||
|
||||
range_start = *image_offset;
|
||||
}
|
||||
|
||||
if range_start != self.offset_range.end {
|
||||
if let Some(text) =
|
||||
Self::collect_text_content(buffer, range_start..self.offset_range.end)
|
||||
@@ -498,7 +492,7 @@ pub struct Context {
|
||||
edits_since_last_parse: language::Subscription,
|
||||
finished_slash_commands: HashSet<SlashCommandId>,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pending_tool_uses_by_id: HashMap<Arc<str>, PendingToolUse>,
|
||||
pending_tool_uses_by_id: HashMap<String, PendingToolUse>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
images: HashMap<u64, (Arc<RenderImage>, Shared<Task<Option<LanguageModelImage>>>)>,
|
||||
image_anchors: Vec<ImageAnchor>,
|
||||
@@ -700,7 +694,7 @@ impl Context {
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
||||
let id = saved_context.id.clone().unwrap_or_else(|| ContextId::new());
|
||||
let mut this = Self::new(
|
||||
id,
|
||||
ReplicaId::default(),
|
||||
@@ -1018,7 +1012,7 @@ impl Context {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn get_tool_use_by_id(&self, id: &Arc<str>) -> Option<&PendingToolUse> {
|
||||
pub fn get_tool_use_by_id(&self, id: &String) -> Option<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.get(id)
|
||||
}
|
||||
|
||||
@@ -1925,60 +1919,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
tool_use_id: Arc<str>,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|this, mut cx| {
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
async move {
|
||||
let output = output.await;
|
||||
this.update(&mut cx, |this, cx| match output {
|
||||
Ok(mut output) => {
|
||||
const NEWLINE: char = '\n';
|
||||
|
||||
if !output.ends_with(NEWLINE) {
|
||||
output.push(NEWLINE);
|
||||
}
|
||||
|
||||
let anchor_range = this.buffer.update(cx, |buffer, cx| {
|
||||
let insert_start = buffer.len().to_offset(buffer);
|
||||
let insert_end = insert_start;
|
||||
|
||||
let start = insert_start;
|
||||
let end = start + output.len() - NEWLINE.len_utf8();
|
||||
|
||||
buffer.edit([(insert_start..insert_end, output)], None, cx);
|
||||
|
||||
let output_range = buffer.anchor_after(start)..buffer.anchor_after(end);
|
||||
|
||||
output_range
|
||||
});
|
||||
|
||||
cx.emit(ContextEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
output_range: anchor_range,
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string());
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: insert_output_task.shared(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.count_remaining_tokens(cx);
|
||||
}
|
||||
@@ -2050,7 +1990,7 @@ impl Context {
|
||||
.message_anchors
|
||||
.iter()
|
||||
.position(|message| message.id == assistant_message_id)?;
|
||||
let event_to_emit = this.buffer.update(cx, |buffer, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let message_old_end_offset = this.message_anchors[message_ix + 1..]
|
||||
.iter()
|
||||
.find(|message| message.start.is_valid(buffer))
|
||||
@@ -2059,13 +1999,6 @@ impl Context {
|
||||
});
|
||||
|
||||
match event {
|
||||
LanguageModelCompletionEvent::Stop(reason) => match reason {
|
||||
StopReason::ToolUse => {
|
||||
return Some(ContextEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
},
|
||||
LanguageModelCompletionEvent::Text(chunk) => {
|
||||
buffer.edit(
|
||||
[(
|
||||
@@ -2103,11 +2036,10 @@ impl Context {
|
||||
let source_range = buffer.anchor_after(start_ix)
|
||||
..buffer.anchor_after(end_ix);
|
||||
|
||||
let tool_use_id: Arc<str> = tool_use.id.into();
|
||||
this.pending_tool_uses_by_id.insert(
|
||||
tool_use_id.clone(),
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
id: tool_use_id,
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
@@ -2116,14 +2048,9 @@ impl Context {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
cx.emit(ContextEvent::StreamedCompletion);
|
||||
if let Some(event) = event_to_emit {
|
||||
cx.emit(event);
|
||||
}
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
@@ -2889,7 +2816,7 @@ impl FeatureFlag for ToolUseFeatureFlag {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: Arc<str>,
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
@@ -2903,12 +2830,6 @@ pub enum PendingToolUseStatus {
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl PendingToolUseStatus {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Idle)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedMessage {
|
||||
pub id: MessageId,
|
||||
|
||||
@@ -390,7 +390,7 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(ContextOperation::from_proto)
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
@@ -527,7 +527,7 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(ContextOperation::from_proto)
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1921,7 +1921,7 @@ impl PromptEditor {
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: settings.ui_font_size.into(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: relative(1.3),
|
||||
..Default::default()
|
||||
@@ -3073,7 +3073,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range,
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3145,7 +3145,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3220,7 +3220,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3294,7 +3294,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -123,7 +123,7 @@ impl PromptBuilder {
|
||||
if params.fs.is_dir(parent_dir).await {
|
||||
let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
|
||||
while let Some(changed_paths) = changes.next().await {
|
||||
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
||||
let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display());
|
||||
if let Ok(target) = params.fs.read_link(&templates_dir).await {
|
||||
log_message.push_str(" -> ");
|
||||
@@ -162,18 +162,18 @@ impl PromptBuilder {
|
||||
let mut combined_changes = futures::stream::select(changes, parent_changes);
|
||||
|
||||
while let Some(changed_paths) = combined_changes.next().await {
|
||||
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
||||
if !params.fs.is_dir(&templates_dir).await {
|
||||
log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates.");
|
||||
Self::register_built_in_templates(&mut handlebars.lock()).log_err();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for event in changed_paths {
|
||||
if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") {
|
||||
log::info!("Reloading prompt template override: {}", event.path.display());
|
||||
if let Some(content) = params.fs.load(&event.path).await.log_err() {
|
||||
let file_name = event.path.file_stem().unwrap().to_string_lossy();
|
||||
for changed_path in changed_paths {
|
||||
if changed_path.starts_with(&templates_dir) && changed_path.extension().map_or(false, |ext| ext == "hbs") {
|
||||
log::info!("Reloading prompt template override: {}", changed_path.display());
|
||||
if let Some(content) = params.fs.load(&changed_path).await.log_err() {
|
||||
let file_name = changed_path.file_stem().unwrap().to_string_lossy();
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,10 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
self.prompt.arguments.as_ref().map_or(false, |args| {
|
||||
args.iter().any(|arg| arg.required == Some(true))
|
||||
})
|
||||
self.prompt
|
||||
.arguments
|
||||
.as_ref()
|
||||
.map_or(false, |args| !args.is_empty())
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
@@ -178,8 +179,6 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
|
||||
let mut map = HashMap::default();
|
||||
map.insert(args[0].name.clone(), arguments.join(" "));
|
||||
Ok(map)
|
||||
} else if arguments.is_empty() && args[0].required == Some(false) {
|
||||
Ok(HashMap::default())
|
||||
} else {
|
||||
Err(anyhow!("Prompt expects argument but none given"))
|
||||
}
|
||||
@@ -200,7 +199,7 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
|
||||
pub fn acceptable_prompt(prompt: &PromptInfo) -> bool {
|
||||
match &prompt.arguments {
|
||||
None => true,
|
||||
Some(args) if args.len() <= 1 => true,
|
||||
Some(args) if args.len() == 1 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,11 @@ impl SlashCommand for FileSlashCommand {
|
||||
Some(ArgumentCompletion {
|
||||
label,
|
||||
new_text: text,
|
||||
after_completion: AfterCompletion::Compose,
|
||||
after_completion: if path_match.is_dir {
|
||||
AfterCompletion::Compose
|
||||
} else {
|
||||
AfterCompletion::Run
|
||||
},
|
||||
replace_previous_arguments: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -253,7 +253,7 @@ fn tab_items_for_queries(
|
||||
.fold(HashMap::default(), |mut candidates, (id, path_string)| {
|
||||
candidates
|
||||
.entry(path_string)
|
||||
.or_insert_with(Vec::new)
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(id);
|
||||
candidates
|
||||
});
|
||||
|
||||
@@ -465,8 +465,7 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let buttons = match status {
|
||||
let buttons = match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
@@ -517,8 +516,7 @@ impl Render for PromptEditor {
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
|
||||
|
||||
let has_error = matches!(status, CodegenStatus::Error(_));
|
||||
if has_error || self.edited_since_done {
|
||||
if self.edited_since_done {
|
||||
vec![
|
||||
cancel,
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
@@ -990,7 +988,7 @@ impl TerminalTransaction {
|
||||
|
||||
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
|
||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||
let input = Self::sanitize_input(hunk);
|
||||
let input = hunk.replace(CARRIAGE_RETURN, " ");
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(input));
|
||||
}
|
||||
@@ -1005,10 +1003,6 @@ impl TerminalTransaction {
|
||||
terminal.input(CARRIAGE_RETURN.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
fn sanitize_input(input: String) -> String {
|
||||
input.replace(['\r', '\n'], "")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Codegen {
|
||||
|
||||
@@ -66,9 +66,4 @@ impl ToolRegistry {
|
||||
pub fn tools(&self) -> Vec<Arc<dyn Tool>> {
|
||||
self.state.read().tools.values().cloned().collect()
|
||||
}
|
||||
|
||||
/// Returns the [`Tool`] with the given name.
|
||||
pub fn tool(&self, name: &str) -> Option<Arc<dyn Tool>> {
|
||||
self.state.read().tools.get(name).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl SoundRegistry {
|
||||
let bytes = self
|
||||
.assets
|
||||
.load(&path)?
|
||||
.map(Ok)
|
||||
.map(|asset| Ok(asset))
|
||||
.unwrap_or_else(|| Err(anyhow::anyhow!("No such asset available")))?
|
||||
.into_owned();
|
||||
let cursor = Cursor::new(bytes);
|
||||
|
||||
@@ -33,7 +33,6 @@ impl Sound {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Audio {
|
||||
_output_stream: Option<OutputStream>,
|
||||
output_handle: Option<OutputStreamHandle>,
|
||||
@@ -46,7 +45,10 @@ impl Global for GlobalAudio {}
|
||||
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
Self {
|
||||
_output_stream: None,
|
||||
output_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
|
||||
|
||||
@@ -95,7 +95,7 @@ struct MacOsUnmounter {
|
||||
impl Drop for MacOsUnmounter {
|
||||
fn drop(&mut self) {
|
||||
let unmount_output = std::process::Command::new("hdiutil")
|
||||
.args(["detach", "-force"])
|
||||
.args(&["detach", "-force"])
|
||||
.arg(&self.mount_path)
|
||||
.output();
|
||||
|
||||
@@ -211,7 +211,7 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(message) = env::var("ZED_UPDATE_EXPLANATION") {
|
||||
if let Some(message) = env::var("ZED_UPDATE_EXPLANATION").ok() {
|
||||
drop(cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
"Zed was installed via a package manager.",
|
||||
@@ -254,7 +254,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
let url = &auto_updater
|
||||
.http_client
|
||||
.build_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
cx.open_url(url);
|
||||
cx.open_url(&url);
|
||||
}
|
||||
|
||||
None
|
||||
@@ -722,7 +722,7 @@ async fn install_release_linux(
|
||||
}
|
||||
|
||||
let output = Command::new("rsync")
|
||||
.args(["-av", "--delete"])
|
||||
.args(&["-av", "--delete"])
|
||||
.arg(&from)
|
||||
.arg(&to)
|
||||
.output()
|
||||
@@ -754,10 +754,10 @@ async fn install_release_macos(
|
||||
|
||||
mounted_app_path.push("/");
|
||||
let output = Command::new("hdiutil")
|
||||
.args(["attach", "-nobrowse"])
|
||||
.args(&["attach", "-nobrowse"])
|
||||
.arg(&downloaded_dmg)
|
||||
.arg("-mountroot")
|
||||
.arg(temp_dir.path())
|
||||
.arg(&temp_dir.path())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
@@ -773,7 +773,7 @@ async fn install_release_macos(
|
||||
};
|
||||
|
||||
let output = Command::new("rsync")
|
||||
.args(["-av", "--delete"])
|
||||
.args(&["-av", "--delete"])
|
||||
.arg(&mounted_app_path)
|
||||
.arg(&running_app_path)
|
||||
.output()
|
||||
|
||||
@@ -18,12 +18,6 @@ pub struct Breadcrumbs {
|
||||
subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl Default for Breadcrumbs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Breadcrumbs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -529,13 +529,14 @@ mod test {
|
||||
let (a, b) = cx.update(|cx| {
|
||||
(
|
||||
one_at_a_time.spawn(cx, |_| async {
|
||||
panic!("");
|
||||
assert!(false);
|
||||
Ok(2)
|
||||
}),
|
||||
one_at_a_time.spawn(cx, |_| async { Ok(3) }),
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(a.await.unwrap(), None::<u32>);
|
||||
assert_eq!(a.await.unwrap(), None);
|
||||
assert_eq!(b.await.unwrap(), Some(3));
|
||||
|
||||
let promise = cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(4) }));
|
||||
|
||||
@@ -259,13 +259,13 @@ impl Room {
|
||||
None
|
||||
};
|
||||
|
||||
let did_join = room
|
||||
match room
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = true;
|
||||
room.call(called_user_id, initial_project_id, cx)
|
||||
})?
|
||||
.await;
|
||||
match did_join {
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(room),
|
||||
Err(error) => Err(error.context("room creation failed")),
|
||||
}
|
||||
@@ -493,7 +493,7 @@ impl Room {
|
||||
// we leave the room and return an error.
|
||||
if let Some(this) = this.upgrade() {
|
||||
log::info!("reconnection failed, leaving room");
|
||||
this.update(&mut cx, |this, cx| this.leave(cx))?.await?;
|
||||
let _ = this.update(&mut cx, |this, cx| this.leave(cx))?.await?;
|
||||
}
|
||||
Err(anyhow!(
|
||||
"can't reconnect to room: client failed to re-establish connection"
|
||||
@@ -933,7 +933,7 @@ impl Room {
|
||||
let list = this
|
||||
.follows_by_leader_id_project_id
|
||||
.entry((leader, project_id))
|
||||
.or_default();
|
||||
.or_insert(Vec::new());
|
||||
if !list.contains(&follower) {
|
||||
list.push(follower);
|
||||
}
|
||||
@@ -942,7 +942,7 @@ impl Room {
|
||||
this.pending_room_update.take();
|
||||
if this.should_leave() {
|
||||
log::info!("room is empty, leaving");
|
||||
this.leave(cx).detach();
|
||||
let _ = this.leave(cx).detach();
|
||||
}
|
||||
|
||||
this.user_store.update(cx, |user_store, cx| {
|
||||
@@ -1017,11 +1017,19 @@ impl Room {
|
||||
.collect::<Vec<u64>>();
|
||||
speaker_ids.sort_unstable();
|
||||
for (sid, participant) in &mut self.remote_participants {
|
||||
participant.speaking = speaker_ids.binary_search(sid).is_ok();
|
||||
if let Ok(_) = speaker_ids.binary_search(sid) {
|
||||
participant.speaking = true;
|
||||
} else {
|
||||
participant.speaking = false;
|
||||
}
|
||||
}
|
||||
if let Some(id) = self.client.user_id() {
|
||||
if let Some(room) = &mut self.live_kit {
|
||||
room.speaking = speaker_ids.binary_search(&id).is_ok();
|
||||
if let Ok(_) = speaker_ids.binary_search(&id) {
|
||||
room.speaking = true;
|
||||
} else {
|
||||
room.speaking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,10 +177,13 @@ impl ChannelBuffer {
|
||||
match event {
|
||||
language::Event::Operation(operation) => {
|
||||
if *ZED_ALWAYS_ACTIVE {
|
||||
if let language::Operation::UpdateSelections { selections, .. } = operation {
|
||||
if selections.is_empty() {
|
||||
return;
|
||||
match operation {
|
||||
language::Operation::UpdateSelections { selections, .. } => {
|
||||
if selections.is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let operation = language::proto::serialize_operation(operation);
|
||||
|
||||
@@ -61,9 +61,9 @@ pub enum ChannelMessageId {
|
||||
Pending(usize),
|
||||
}
|
||||
|
||||
impl From<ChannelMessageId> for Option<u64> {
|
||||
fn from(val: ChannelMessageId) -> Self {
|
||||
match val {
|
||||
impl Into<Option<u64>> for ChannelMessageId {
|
||||
fn into(self) -> Option<u64> {
|
||||
match self {
|
||||
ChannelMessageId::Saved(id) => Some(id),
|
||||
ChannelMessageId::Pending(_) => None,
|
||||
}
|
||||
|
||||
@@ -249,14 +249,15 @@ impl ChannelStore {
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self) {
|
||||
if !self.did_subscribe
|
||||
&& self
|
||||
if !self.did_subscribe {
|
||||
if self
|
||||
.client
|
||||
.send(proto::SubscribeToChannels {})
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
self.did_subscribe = true;
|
||||
{
|
||||
self.did_subscribe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +423,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.or_insert_with(|| Default::default())
|
||||
.acknowledge_message_id(message_id);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -435,7 +436,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.or_insert_with(|| Default::default())
|
||||
.update_latest_message_id(message_id);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -449,7 +450,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.or_insert_with(|| Default::default())
|
||||
.acknowledge_notes_version(epoch, version);
|
||||
cx.notify()
|
||||
}
|
||||
@@ -463,7 +464,7 @@ impl ChannelStore {
|
||||
) {
|
||||
self.channel_states
|
||||
.entry(channel_id)
|
||||
.or_default()
|
||||
.or_insert_with(|| Default::default())
|
||||
.update_latest_notes_version(epoch, version);
|
||||
cx.notify()
|
||||
}
|
||||
@@ -923,7 +924,7 @@ impl ChannelStore {
|
||||
if let Some(role) = ChannelRole::from_i32(membership.role) {
|
||||
this.channel_states
|
||||
.entry(ChannelId(membership.channel_id))
|
||||
.or_default()
|
||||
.or_insert_with(|| ChannelState::default())
|
||||
.set_role(role)
|
||||
}
|
||||
}
|
||||
@@ -1093,7 +1094,11 @@ impl ChannelStore {
|
||||
id: ChannelId(channel.id),
|
||||
visibility: channel.visibility(),
|
||||
name: channel.name.into(),
|
||||
parent_path: channel.parent_path.into_iter().map(ChannelId).collect(),
|
||||
parent_path: channel
|
||||
.parent_path
|
||||
.into_iter()
|
||||
.map(|cid| ChannelId(cid))
|
||||
.collect(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
@@ -1108,11 +1113,14 @@ impl ChannelStore {
|
||||
|
||||
if channels_changed {
|
||||
if !payload.delete_channels.is_empty() {
|
||||
let delete_channels: Vec<ChannelId> =
|
||||
payload.delete_channels.into_iter().map(ChannelId).collect();
|
||||
let delete_channels: Vec<ChannelId> = payload
|
||||
.delete_channels
|
||||
.into_iter()
|
||||
.map(|cid| ChannelId(cid))
|
||||
.collect();
|
||||
self.channel_index.delete_channels(&delete_channels);
|
||||
self.channel_participants
|
||||
.retain(|channel_id, _| !delete_channels.contains(channel_id));
|
||||
.retain(|channel_id, _| !delete_channels.contains(&channel_id));
|
||||
|
||||
for channel_id in &delete_channels {
|
||||
let channel_id = *channel_id;
|
||||
|
||||
@@ -117,7 +117,7 @@ impl Settings for ClientSettings {
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
let mut result = sources.json_merge::<Self>()?;
|
||||
if let Some(server_url) = &*ZED_SERVER_URL {
|
||||
result.server_url.clone_from(server_url)
|
||||
result.server_url.clone_from(&server_url)
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1141,7 +1141,7 @@ impl Client {
|
||||
request_headers.insert("x-zed-app-version", HeaderValue::from_str(&app_version)?);
|
||||
request_headers.insert(
|
||||
"x-zed-release-channel",
|
||||
HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
|
||||
HeaderValue::from_str(&release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
|
||||
);
|
||||
|
||||
match url_scheme {
|
||||
@@ -1344,14 +1344,16 @@ impl Client {
|
||||
);
|
||||
}
|
||||
|
||||
serde_json::from_slice::<GithubUser>(body.as_slice()).map_err(|err| {
|
||||
let user = serde_json::from_slice::<GithubUser>(body.as_slice()).map_err(|err| {
|
||||
log::error!("Error deserializing: {:?}", err);
|
||||
log::error!(
|
||||
"GitHub API response text: {:?}",
|
||||
String::from_utf8_lossy(body.as_slice())
|
||||
);
|
||||
anyhow!("error deserializing GitHub user")
|
||||
})?
|
||||
})?;
|
||||
|
||||
user
|
||||
};
|
||||
|
||||
let query_params = [
|
||||
@@ -1406,7 +1408,7 @@ impl Client {
|
||||
|
||||
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncAppContext) {
|
||||
self.state.write().credentials = None;
|
||||
self.disconnect(cx);
|
||||
self.disconnect(&cx);
|
||||
|
||||
if self.has_credentials(cx).await {
|
||||
self.credentials_provider
|
||||
|
||||
@@ -35,8 +35,12 @@ pub(crate) async fn connect_socks_proxy_stream(
|
||||
}
|
||||
|
||||
fn parse_socks_proxy(proxy: Option<&Uri>) -> Option<((String, u16), SocksVersion)> {
|
||||
let proxy_uri = proxy?;
|
||||
let scheme = proxy_uri.scheme_str()?;
|
||||
let Some(proxy_uri) = proxy else {
|
||||
return None;
|
||||
};
|
||||
let Some(scheme) = proxy_uri.scheme_str() else {
|
||||
return None;
|
||||
};
|
||||
let socks_version = if scheme.starts_with("socks4") {
|
||||
// socks4
|
||||
SocksVersion::V4
|
||||
|
||||
@@ -670,24 +670,6 @@ impl Telemetry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_json_checksum(json: &impl AsRef<[u8]>) -> Option<String> {
|
||||
let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(checksum_seed);
|
||||
summer.update(json);
|
||||
summer.update(checksum_seed);
|
||||
let mut checksum = String::new();
|
||||
for byte in summer.finalize().as_slice() {
|
||||
use std::fmt::Write;
|
||||
write!(&mut checksum, "{:02x}", byte).unwrap();
|
||||
}
|
||||
|
||||
Some(checksum)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -846,3 +828,21 @@ mod tests {
|
||||
&& telemetry.state.lock().first_event_date_time.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_json_checksum(json: &impl AsRef<[u8]>) -> Option<String> {
|
||||
let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(checksum_seed);
|
||||
summer.update(&json);
|
||||
summer.update(checksum_seed);
|
||||
let mut checksum = String::new();
|
||||
for byte in summer.finalize().as_slice() {
|
||||
use std::fmt::Write;
|
||||
write!(&mut checksum, "{:02x}", byte).unwrap();
|
||||
}
|
||||
|
||||
Some(checksum)
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ impl UserStore {
|
||||
cx.update(|cx| {
|
||||
if let Some(info) = info {
|
||||
let disable_staff = std::env::var("ZED_DISABLE_STAFF")
|
||||
.map_or(false, |v| !v.is_empty() && v != "0");
|
||||
.map_or(false, |v| v != "" && v != "0");
|
||||
let staff = info.staff && !disable_staff;
|
||||
cx.update_flags(staff, info.flags);
|
||||
client.telemetry.set_authenticated_user_info(
|
||||
|
||||
@@ -137,7 +137,7 @@ async fn get_authenticated_user(
|
||||
)
|
||||
.await?;
|
||||
let metrics_id = app.db.get_user_metrics_id(user.id).await?;
|
||||
Ok(Json(AuthenticatedUserResponse { user, metrics_id }))
|
||||
return Ok(Json(AuthenticatedUserResponse { user, metrics_id }));
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
@@ -600,7 +600,7 @@ async fn find_or_create_billing_customer(
|
||||
// there's nothing more we need to do.
|
||||
if let Some(billing_customer) = app
|
||||
.db
|
||||
.get_billing_customer_by_stripe_customer_id(customer_id)
|
||||
.get_billing_customer_by_stripe_customer_id(&customer_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(Some(billing_customer));
|
||||
@@ -609,7 +609,7 @@ async fn find_or_create_billing_customer(
|
||||
// If all we have is a customer ID, resolve it to a full customer record by
|
||||
// hitting the Stripe API.
|
||||
let customer = match customer_or_id {
|
||||
Expandable::Id(id) => Customer::retrieve(stripe_client, &id, &[]).await?,
|
||||
Expandable::Id(id) => Customer::retrieve(&stripe_client, &id, &[]).await?,
|
||||
Expandable::Object(customer) => *customer,
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct CheckIsContributorParams {
|
||||
}
|
||||
|
||||
impl CheckIsContributorParams {
|
||||
fn into_contributor_selector(self) -> Result<ContributorSelector> {
|
||||
fn as_contributor_selector(self) -> Result<ContributorSelector> {
|
||||
if let Some(github_user_id) = self.github_user_id {
|
||||
return Ok(ContributorSelector::GitHubUserId { github_user_id });
|
||||
}
|
||||
@@ -54,7 +54,7 @@ async fn check_is_contributor(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<CheckIsContributorParams>,
|
||||
) -> Result<Json<CheckIsContributorResponse>> {
|
||||
let params = params.into_contributor_selector()?;
|
||||
let params = params.as_contributor_selector()?;
|
||||
|
||||
if RenovateBot::is_renovate_bot(¶ms) {
|
||||
return Ok(Json(CheckIsContributorResponse {
|
||||
|
||||
@@ -397,7 +397,7 @@ pub async fn post_events(
|
||||
match &wrapper.event {
|
||||
Event::Editor(event) => to_upload.editor_events.push(EditorEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
country_code.clone(),
|
||||
@@ -410,7 +410,7 @@ pub async fn post_events(
|
||||
.inline_completion_events
|
||||
.push(InlineCompletionEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
country_code.clone(),
|
||||
@@ -419,7 +419,7 @@ pub async fn post_events(
|
||||
}
|
||||
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
@@ -429,7 +429,7 @@ pub async fn post_events(
|
||||
.assistant_events
|
||||
.push(AssistantEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
@@ -437,42 +437,42 @@ pub async fn post_events(
|
||||
}
|
||||
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
@@ -486,7 +486,7 @@ pub async fn post_events(
|
||||
.extension_events
|
||||
.push(ExtensionEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
metadata,
|
||||
first_event_at,
|
||||
@@ -495,7 +495,7 @@ pub async fn post_events(
|
||||
}
|
||||
Event::Repl(event) => to_upload.repl_events.push(ReplEventRow::from_event(
|
||||
event.clone(),
|
||||
wrapper,
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
@@ -1326,11 +1326,13 @@ impl ActionEventRow {
|
||||
}
|
||||
|
||||
pub fn calculate_json_checksum(app: Arc<AppState>, json: &impl AsRef<[u8]>) -> Option<Vec<u8>> {
|
||||
let checksum_seed = app.config.zed_client_checksum_seed.as_ref()?;
|
||||
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(checksum_seed);
|
||||
summer.update(json);
|
||||
summer.update(&json);
|
||||
summer.update(checksum_seed);
|
||||
Some(summer.finalize().into_iter().collect())
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ async fn get_extensions(
|
||||
let extension_id = filter.to_lowercase();
|
||||
let mut exact_match = None;
|
||||
extensions.retain(|extension| {
|
||||
if extension.id.as_ref() == extension_id {
|
||||
if extension.id.as_ref() == &extension_id {
|
||||
exact_match = Some(extension.clone());
|
||||
false
|
||||
} else {
|
||||
@@ -319,14 +319,14 @@ async fn fetch_extensions_from_blob_store(
|
||||
if let Some(extension) = fetch_extension_manifest(
|
||||
blob_store_client,
|
||||
blob_store_bucket,
|
||||
extension_id,
|
||||
published_version,
|
||||
&extension_id,
|
||||
&published_version,
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
new_versions
|
||||
.entry(extension_id)
|
||||
.entry(&extension_id)
|
||||
.or_default()
|
||||
.push(extension);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
|
||||
impersonator_id: None,
|
||||
})
|
||||
} else {
|
||||
verify_access_token(access_token, user_id, &state.db).await
|
||||
verify_access_token(&access_token, user_id, &state.db).await
|
||||
};
|
||||
|
||||
if let Ok(validate_result) = validate_result {
|
||||
@@ -202,7 +202,7 @@ pub async fn verify_access_token(
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let token: AccessTokenJson = serde_json::from_str(token)?;
|
||||
let token: AccessTokenJson = serde_json::from_str(&token)?;
|
||||
|
||||
let db_token = db.get_access_token(token.id).await?;
|
||||
let token_user_id = db_token.impersonated_user_id.unwrap_or(db_token.user_id);
|
||||
@@ -249,7 +249,7 @@ pub async fn verify_dev_server_token(
|
||||
db: &Arc<Database>,
|
||||
) -> anyhow::Result<dev_server::Model> {
|
||||
let (id, token) = split_dev_server_token(dev_server_token)?;
|
||||
let token_hash = hash_access_token(token);
|
||||
let token_hash = hash_access_token(&token);
|
||||
let server = db.get_dev_server(id).await?;
|
||||
|
||||
if server
|
||||
@@ -301,16 +301,18 @@ mod test {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token = create_access_token(db, user.user_id, None).await.unwrap();
|
||||
let token = create_access_token(&db, user.user_id, None).await.unwrap();
|
||||
assert!(matches!(
|
||||
verify_access_token(&token, user.user_id, db).await.unwrap(),
|
||||
verify_access_token(&token, user.user_id, &db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
let old_token = create_previous_access_token(user.user_id, None, db)
|
||||
let old_token = create_previous_access_token(user.user_id, None, &db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -331,7 +333,7 @@ mod test {
|
||||
assert!(hash.starts_with("$scrypt$"));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&old_token, user.user_id, db)
|
||||
verify_access_token(&old_token, user.user_id, &db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
@@ -353,7 +355,7 @@ mod test {
|
||||
assert!(hash.starts_with("$sha256$"));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&old_token, user.user_id, db)
|
||||
verify_access_token(&old_token, user.user_id, &db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
@@ -363,7 +365,9 @@ mod test {
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&token, user.user_id, db).await.unwrap(),
|
||||
verify_access_token(&token, user.user_id, &db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
|
||||
@@ -139,12 +139,14 @@ impl Database {
|
||||
let (tx, result) = self.with_weak_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(result) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(result),
|
||||
Err(error) => Err(error),
|
||||
Ok(()) => return Ok(result),
|
||||
Err(error) => {
|
||||
return Err(error);
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
Err(error)
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -215,7 +217,7 @@ impl Database {
|
||||
F: Send + Fn(TransactionHandle) -> Fut,
|
||||
Fut: Send + Future<Output = Result<T>>,
|
||||
{
|
||||
let room_id = Database::room_id_for_project(self, project_id).await?;
|
||||
let room_id = Database::room_id_for_project(&self, project_id).await?;
|
||||
let body = async {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
|
||||
@@ -218,9 +218,9 @@ impl From<proto::ChannelRole> for ChannelRole {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelRole> for proto::ChannelRole {
|
||||
fn from(val: ChannelRole) -> Self {
|
||||
match val {
|
||||
impl Into<proto::ChannelRole> for ChannelRole {
|
||||
fn into(self) -> proto::ChannelRole {
|
||||
match self {
|
||||
ChannelRole::Admin => proto::ChannelRole::Admin,
|
||||
ChannelRole::Member => proto::ChannelRole::Member,
|
||||
ChannelRole::Talker => proto::ChannelRole::Talker,
|
||||
@@ -230,9 +230,9 @@ impl From<ChannelRole> for proto::ChannelRole {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelRole> for i32 {
|
||||
fn from(val: ChannelRole) -> Self {
|
||||
let proto: proto::ChannelRole = val.into();
|
||||
impl Into<i32> for ChannelRole {
|
||||
fn into(self) -> i32 {
|
||||
let proto: proto::ChannelRole = self.into();
|
||||
proto.into()
|
||||
}
|
||||
}
|
||||
@@ -259,18 +259,18 @@ impl From<proto::ChannelVisibility> for ChannelVisibility {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelVisibility> for proto::ChannelVisibility {
|
||||
fn from(val: ChannelVisibility) -> Self {
|
||||
match val {
|
||||
impl Into<proto::ChannelVisibility> for ChannelVisibility {
|
||||
fn into(self) -> proto::ChannelVisibility {
|
||||
match self {
|
||||
ChannelVisibility::Public => proto::ChannelVisibility::Public,
|
||||
ChannelVisibility::Members => proto::ChannelVisibility::Members,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelVisibility> for i32 {
|
||||
fn from(val: ChannelVisibility) -> Self {
|
||||
let proto: proto::ChannelVisibility = val.into();
|
||||
impl Into<i32> for ChannelVisibility {
|
||||
fn into(self) -> i32 {
|
||||
let proto: proto::ChannelVisibility = self.into();
|
||||
proto.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ impl Database {
|
||||
drop(rows);
|
||||
|
||||
if collaborators.is_empty() {
|
||||
self.snapshot_channel_buffer(channel_id, tx).await?;
|
||||
self.snapshot_channel_buffer(channel_id, &tx).await?;
|
||||
}
|
||||
|
||||
Ok(LeftChannelBuffer {
|
||||
@@ -872,7 +872,7 @@ fn operation_from_storage(
|
||||
})
|
||||
}
|
||||
|
||||
fn version_to_storage(version: &[proto::VectorClockEntry]) -> Vec<storage::VectorClockEntry> {
|
||||
fn version_to_storage(version: &Vec<proto::VectorClockEntry>) -> Vec<storage::VectorClockEntry> {
|
||||
version
|
||||
.iter()
|
||||
.map(|entry| storage::VectorClockEntry {
|
||||
@@ -882,7 +882,7 @@ fn version_to_storage(version: &[proto::VectorClockEntry]) -> Vec<storage::Vecto
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn version_from_storage(version: &[storage::VectorClockEntry]) -> Vec<proto::VectorClockEntry> {
|
||||
fn version_from_storage(version: &Vec<storage::VectorClockEntry>) -> Vec<proto::VectorClockEntry> {
|
||||
version
|
||||
.iter()
|
||||
.map(|entry| proto::VectorClockEntry {
|
||||
|
||||
@@ -188,16 +188,17 @@ impl Database {
|
||||
.anyhow())?;
|
||||
}
|
||||
}
|
||||
} else if visibility == ChannelVisibility::Members
|
||||
&& self
|
||||
} else if visibility == ChannelVisibility::Members {
|
||||
if self
|
||||
.get_channel_descendants_excluding_self([&channel], &tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.any(|channel| channel.visibility == ChannelVisibility::Public)
|
||||
{
|
||||
Err(ErrorCode::BadPublicNesting
|
||||
.with_tag("direction", "children")
|
||||
.anyhow())?;
|
||||
{
|
||||
Err(ErrorCode::BadPublicNesting
|
||||
.with_tag("direction", "children")
|
||||
.anyhow())?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut model = channel.into_active_model();
|
||||
@@ -307,7 +308,7 @@ impl Database {
|
||||
|
||||
fn sanitize_channel_name(name: &str) -> Result<&str> {
|
||||
let new_name = name.trim().trim_start_matches('#');
|
||||
if new_name.is_empty() {
|
||||
if new_name == "" {
|
||||
Err(anyhow!("channel name can't be blank"))?;
|
||||
}
|
||||
Ok(new_name)
|
||||
@@ -984,7 +985,7 @@ impl Database {
|
||||
.all(&*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Channel::from_model)
|
||||
.map(|c| Channel::from_model(c))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok((root_id, channels))
|
||||
|
||||
@@ -146,11 +146,11 @@ impl Database {
|
||||
pub async fn update_dev_server_project(
|
||||
&self,
|
||||
id: DevServerProjectId,
|
||||
paths: &[String],
|
||||
paths: &Vec<String>,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let paths = paths.to_owned();
|
||||
let paths = paths.clone();
|
||||
let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
|
||||
.find_also_related(dev_server::Entity)
|
||||
.one(&*tx)
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::*;
|
||||
impl Database {
|
||||
pub async fn get_hosted_projects(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
channel_ids: &Vec<ChannelId>,
|
||||
roles: &HashMap<ChannelId, ChannelRole>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::HostedProject>> {
|
||||
|
||||
@@ -86,13 +86,14 @@ impl Database {
|
||||
avoid_duplicates: bool,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Option<(UserId, proto::Notification)>> {
|
||||
if avoid_duplicates
|
||||
&& self
|
||||
if avoid_duplicates {
|
||||
if self
|
||||
.find_notification(recipient_id, ¬ification, tx)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Ok(None);
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let proto = notification.to_proto();
|
||||
|
||||
@@ -459,7 +459,7 @@ impl Database {
|
||||
.await?;
|
||||
}
|
||||
|
||||
let (channel, room) = self.get_channel_room(room_id, tx).await?;
|
||||
let (channel, room) = self.get_channel_room(room_id, &tx).await?;
|
||||
let channel = channel.ok_or_else(|| anyhow!("no channel for room"))?;
|
||||
Ok(JoinRoom {
|
||||
room,
|
||||
@@ -766,13 +766,13 @@ impl Database {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Some(RejoinedProject {
|
||||
return Ok(Some(RejoinedProject {
|
||||
id: project_id,
|
||||
old_connection_id,
|
||||
collaborators,
|
||||
worktrees,
|
||||
language_servers,
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
pub async fn leave_room(
|
||||
@@ -1108,14 +1108,15 @@ impl Database {
|
||||
.count(tx)
|
||||
.await?
|
||||
> 0;
|
||||
if requires_zed_cla
|
||||
&& contributor::Entity::find()
|
||||
if requires_zed_cla {
|
||||
if contributor::Entity::find()
|
||||
.filter(contributor::Column::UserId.eq(user_id))
|
||||
.one(tx)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
Err(anyhow!("user has not signed the Zed CLA"))?;
|
||||
{
|
||||
Err(anyhow!("user has not signed the Zed CLA"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -48,7 +48,7 @@ impl Model {
|
||||
id: self.id.to_proto(),
|
||||
project_id: project.map(|p| p.id.to_proto()),
|
||||
dev_server_id: self.dev_server_id.to_proto(),
|
||||
path: self.paths().first().cloned().unwrap_or_default(),
|
||||
path: self.paths().get(0).cloned().unwrap_or_default(),
|
||||
paths: self.paths().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,19 @@ async fn test_already_processed_stripe_event(db: &Arc<Database>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
assert_eq!(
|
||||
db.already_processed_stripe_event(&processed_event_id)
|
||||
.await
|
||||
.unwrap(),
|
||||
true,
|
||||
"Expected {processed_event_id} to already be processed"
|
||||
);
|
||||
|
||||
assert!(
|
||||
!db.already_processed_stripe_event(&unprocessed_event_id)
|
||||
assert_eq!(
|
||||
db.already_processed_stripe_event(&unprocessed_event_id)
|
||||
.await
|
||||
.unwrap(),
|
||||
false,
|
||||
"Expected {unprocessed_event_id} to be unprocessed"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -304,7 +304,10 @@ impl AppState {
|
||||
db: db.clone(),
|
||||
live_kit_client,
|
||||
blob_store_client: build_blob_store_client(&config).await.log_err(),
|
||||
stripe_client: build_stripe_client(&config).await.map(Arc::new).log_err(),
|
||||
stripe_client: build_stripe_client(&config)
|
||||
.await
|
||||
.map(|client| Arc::new(client))
|
||||
.log_err(),
|
||||
rate_limiter: Arc::new(RateLimiter::new(db)),
|
||||
executor,
|
||||
clickhouse_client: config
|
||||
|
||||
@@ -141,7 +141,7 @@ async fn validate_api_token<B>(mut req: Request<B>, next: Next<B>) -> impl IntoR
|
||||
})?;
|
||||
|
||||
let state = req.extensions().get::<Arc<LlmState>>().unwrap();
|
||||
match LlmTokenClaims::validate(token, &state.config) {
|
||||
match LlmTokenClaims::validate(&token, &state.config) {
|
||||
Ok(claims) => {
|
||||
if state.db.is_access_token_revoked(&claims.jti).await? {
|
||||
return Err(Error::http(
|
||||
@@ -154,7 +154,7 @@ async fn validate_api_token<B>(mut req: Request<B>, next: Next<B>) -> impl IntoR
|
||||
.record("user_id", claims.user_id)
|
||||
.record("login", claims.github_user_login.clone())
|
||||
.record("authn.jti", &claims.jti)
|
||||
.record("is_staff", claims.is_staff);
|
||||
.record("is_staff", &claims.is_staff);
|
||||
|
||||
req.extensions_mut().insert(claims);
|
||||
Ok::<_, Error>(next.run(req).await.into_response())
|
||||
@@ -247,7 +247,7 @@ async fn perform_completion(
|
||||
};
|
||||
|
||||
let mut request: anthropic::Request =
|
||||
serde_json::from_str(params.provider_request.get())?;
|
||||
serde_json::from_str(¶ms.provider_request.get())?;
|
||||
|
||||
// Override the model on the request with the latest version of the model that is
|
||||
// known to the server.
|
||||
@@ -348,7 +348,7 @@ async fn perform_completion(
|
||||
&state.http_client,
|
||||
open_ai::OPEN_AI_API_URL,
|
||||
api_key,
|
||||
serde_json::from_str(params.provider_request.get())?,
|
||||
serde_json::from_str(¶ms.provider_request.get())?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -379,8 +379,7 @@ async fn perform_completion(
|
||||
&state.http_client,
|
||||
google_ai::API_URL,
|
||||
api_key,
|
||||
serde_json::from_str(params.provider_request.get())?,
|
||||
None,
|
||||
serde_json::from_str(¶ms.provider_request.get())?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -412,9 +411,9 @@ async fn perform_completion(
|
||||
.context("no Qwen2-7B URL configured on the server")?;
|
||||
let chunks = open_ai::stream_completion(
|
||||
&state.http_client,
|
||||
api_url,
|
||||
&api_url,
|
||||
api_key,
|
||||
serde_json::from_str(params.provider_request.get())?,
|
||||
serde_json::from_str(¶ms.provider_request.get())?,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -26,16 +26,19 @@ fn authorize_access_to_model(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if provider == LanguageModelProvider::Anthropic {
|
||||
if model == "claude-3-5-sonnet" {
|
||||
return Ok(());
|
||||
}
|
||||
match provider {
|
||||
LanguageModelProvider::Anthropic => {
|
||||
if model == "claude-3-5-sonnet" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if claims.has_llm_closed_beta_feature_flag
|
||||
&& Some(model) == config.llm_closed_beta_model_name.as_deref()
|
||||
{
|
||||
return Ok(());
|
||||
if claims.has_llm_closed_beta_feature_flag
|
||||
&& Some(model) == config.llm_closed_beta_model_name.as_deref()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Err(Error::http(
|
||||
|
||||
@@ -110,12 +110,14 @@ impl LlmDatabase {
|
||||
let (tx, result) = self.with_transaction(&f).await?;
|
||||
match result {
|
||||
Ok(result) => match tx.commit().await.map_err(Into::into) {
|
||||
Ok(()) => Ok(result),
|
||||
Err(error) => Err(error),
|
||||
Ok(()) => return Ok(result),
|
||||
Err(error) => {
|
||||
return Err(error);
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tx.rollback().await?;
|
||||
Err(error)
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,7 +82,7 @@ impl LlmDatabase {
|
||||
pub async fn insert_models(&mut self, models: &[ModelParams]) -> Result<()> {
|
||||
let all_provider_ids = &self.provider_ids;
|
||||
self.transaction(|tx| async move {
|
||||
model::Entity::insert_many(models.iter().map(|model_params| {
|
||||
model::Entity::insert_many(models.into_iter().map(|model_params| {
|
||||
let provider_id = all_provider_ids[&model_params.provider];
|
||||
model::ActiveModel {
|
||||
provider_id: ActiveValue::set(provider_id),
|
||||
|
||||
@@ -88,7 +88,7 @@ async fn main() -> Result<()> {
|
||||
.route("/healthz", get(handle_liveness_probe))
|
||||
.layer(Extension(mode));
|
||||
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", config.http_port))
|
||||
let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.http_port))
|
||||
.expect("failed to bind TCP listener");
|
||||
|
||||
let mut on_shutdown = None;
|
||||
@@ -257,7 +257,7 @@ async fn setup_app_database(config: &Config) -> Result<()> {
|
||||
db.initialize_notification_kinds().await?;
|
||||
|
||||
if config.seed_path.is_some() {
|
||||
collab::seed::seed(config, &db, false).await?;
|
||||
collab::seed::seed(&config, &db, false).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -100,7 +100,7 @@ impl RateLimiter {
|
||||
pub async fn save(&self) -> Result<()> {
|
||||
let mut buckets = Vec::new();
|
||||
self.dirty_buckets.retain(|key| {
|
||||
if let Some(bucket) = self.buckets.get(key) {
|
||||
if let Some(bucket) = self.buckets.get(&key) {
|
||||
buckets.push(crate::db::rate_buckets::Model {
|
||||
user_id: key.0,
|
||||
rate_limit_name: key.1.clone(),
|
||||
|
||||
@@ -115,16 +115,16 @@ impl Principal {
|
||||
fn update_span(&self, span: &tracing::Span) {
|
||||
match &self {
|
||||
Principal::User(user) => {
|
||||
span.record("user_id", user.id.0);
|
||||
span.record("user_id", &user.id.0);
|
||||
span.record("login", &user.github_login);
|
||||
}
|
||||
Principal::Impersonated { user, admin } => {
|
||||
span.record("user_id", user.id.0);
|
||||
span.record("user_id", &user.id.0);
|
||||
span.record("login", &user.github_login);
|
||||
span.record("impersonator", &admin.github_login);
|
||||
}
|
||||
Principal::DevServer(dev_server) => {
|
||||
span.record("dev_server_id", dev_server.id.0);
|
||||
span.record("dev_server_id", &dev_server.id.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -964,10 +964,14 @@ impl Server {
|
||||
}
|
||||
};
|
||||
|
||||
let supermaven_client = this.app_state.config.supermaven_admin_api_key.clone().map(|supermaven_admin_api_key| Arc::new(SupermavenAdminApi::new(
|
||||
let supermaven_client = if let Some(supermaven_admin_api_key) = this.app_state.config.supermaven_admin_api_key.clone() {
|
||||
Some(Arc::new(SupermavenAdminApi::new(
|
||||
supermaven_admin_api_key.to_string(),
|
||||
http_client.clone(),
|
||||
)));
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let session = Session {
|
||||
principal: principal.clone(),
|
||||
@@ -1122,7 +1126,7 @@ impl Server {
|
||||
self.peer.send(connection_id, incoming_call)?;
|
||||
}
|
||||
|
||||
update_user_contacts(user.id, session).await?;
|
||||
update_user_contacts(user.id, &session).await?;
|
||||
}
|
||||
Principal::DevServer(dev_server) => {
|
||||
{
|
||||
@@ -1155,7 +1159,7 @@ impl Server {
|
||||
.db
|
||||
.dev_server_projects_update(dev_server.user_id)
|
||||
.await?;
|
||||
send_dev_server_projects_update(dev_server.user_id, status, session).await;
|
||||
send_dev_server_projects_update(dev_server.user_id, status, &session).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1560,17 +1564,21 @@ async fn join_room(
|
||||
|
||||
let live_kit_connection_info =
|
||||
if let Some(live_kit) = session.app_state.live_kit_client.as_ref() {
|
||||
live_kit
|
||||
if let Some(token) = live_kit
|
||||
.room_token(
|
||||
&joined_room.room.live_kit_room,
|
||||
&session.user_id().to_string(),
|
||||
)
|
||||
.trace_err()
|
||||
.map(|token| proto::LiveKitConnectionInfo {
|
||||
{
|
||||
Some(proto::LiveKitConnectionInfo {
|
||||
server_url: live_kit.url().into(),
|
||||
token,
|
||||
can_publish: true,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1855,7 +1863,7 @@ async fn call(
|
||||
initial_project_id,
|
||||
)
|
||||
.await?;
|
||||
room_updated(room, &session.peer);
|
||||
room_updated(&room, &session.peer);
|
||||
mem::take(incoming_call)
|
||||
};
|
||||
update_user_contacts(called_user_id, &session).await?;
|
||||
@@ -1998,13 +2006,13 @@ async fn share_project(
|
||||
&request.worktrees,
|
||||
request
|
||||
.dev_server_project_id
|
||||
.map(DevServerProjectId::from_proto),
|
||||
.map(|id| DevServerProjectId::from_proto(id)),
|
||||
)
|
||||
.await?;
|
||||
response.send(proto::ShareProjectResponse {
|
||||
project_id: project_id.to_proto(),
|
||||
})?;
|
||||
room_updated(room, &session.peer);
|
||||
room_updated(&room, &session.peer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2261,9 +2269,9 @@ async fn leave_project(request: proto::LeaveProject, session: UserSession) -> Re
|
||||
"leave project"
|
||||
);
|
||||
|
||||
project_left(project, &session);
|
||||
project_left(&project, &session);
|
||||
if let Some(room) = room {
|
||||
room_updated(room, &session.peer);
|
||||
room_updated(&room, &session.peer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2745,7 +2753,7 @@ async fn shutdown_dev_server_internal(
|
||||
.await
|
||||
.dev_server_projects_update(dev_server.user_id)
|
||||
.await?;
|
||||
send_dev_server_projects_update(dev_server.user_id, status, session).await;
|
||||
send_dev_server_projects_update(dev_server.user_id, status, &session).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2787,7 +2795,7 @@ async fn update_project(
|
||||
},
|
||||
);
|
||||
if let Some(room) = room {
|
||||
room_updated(room, &session.peer);
|
||||
room_updated(&room, &session.peer);
|
||||
}
|
||||
response.send(proto::Ack {})?;
|
||||
|
||||
@@ -3554,7 +3562,7 @@ async fn create_channel(
|
||||
) -> Result<()> {
|
||||
let db = session.db().await;
|
||||
|
||||
let parent_id = request.parent_id.map(ChannelId::from_proto);
|
||||
let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id));
|
||||
let (channel, membership) = db
|
||||
.create_channel(&request.name, parent_id, session.user_id())
|
||||
.await?;
|
||||
@@ -4276,7 +4284,10 @@ async fn send_channel_message(
|
||||
&request.mentions,
|
||||
timestamp,
|
||||
nonce.clone().into(),
|
||||
request.reply_to_message_id.map(MessageId::from_proto),
|
||||
match request.reply_to_message_id {
|
||||
Some(reply_to_message_id) => Some(MessageId::from_proto(reply_to_message_id)),
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -4529,7 +4540,6 @@ async fn count_language_model_tokens(
|
||||
google_ai::API_URL,
|
||||
api_key,
|
||||
serde_json::from_str(&request.request)?,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
@@ -4837,7 +4847,9 @@ async fn get_notifications(
|
||||
.get_notifications(
|
||||
session.user_id(),
|
||||
NOTIFICATION_COUNT_PER_PAGE,
|
||||
request.before_id.map(db::NotificationId::from_proto),
|
||||
request
|
||||
.before_id
|
||||
.map(|id| db::NotificationId::from_proto(id)),
|
||||
)
|
||||
.await?;
|
||||
response.send(proto::GetNotificationsResponse {
|
||||
@@ -5091,7 +5103,7 @@ fn build_initial_contacts_update(
|
||||
for contact in contacts {
|
||||
match contact {
|
||||
db::Contact::Accepted { user_id, busy } => {
|
||||
update.contacts.push(contact_for_user(user_id, busy, pool));
|
||||
update.contacts.push(contact_for_user(user_id, busy, &pool));
|
||||
}
|
||||
db::Contact::Outgoing { user_id } => update.outgoing_requests.push(user_id.to_proto()),
|
||||
db::Contact::Incoming { user_id } => {
|
||||
@@ -5148,8 +5160,7 @@ fn channel_updated(
|
||||
None,
|
||||
pool.channel_connection_ids(channel.root_id())
|
||||
.filter_map(|(channel_id, role)| {
|
||||
role.can_see_channel(channel.visibility)
|
||||
.then_some(channel_id)
|
||||
role.can_see_channel(channel.visibility).then(|| channel_id)
|
||||
}),
|
||||
|peer_id| {
|
||||
peer.send(
|
||||
@@ -5227,7 +5238,7 @@ async fn lost_dev_server_connection(session: &DevServerSession) -> Result<()> {
|
||||
|
||||
for project_id in project_ids {
|
||||
// not unshare re-checks the connection ids match, so we get away with no transaction
|
||||
unshare_project_internal(project_id, session.connection_id, None, session).await?;
|
||||
unshare_project_internal(project_id, session.connection_id, None, &session).await?;
|
||||
}
|
||||
|
||||
let user_id = session.dev_server().user_id;
|
||||
@@ -5299,7 +5310,7 @@ async fn leave_room_for_session(session: &UserSession, connection_id: Connection
|
||||
}
|
||||
|
||||
for contact_user_id in contacts_to_update {
|
||||
update_user_contacts(contact_user_id, session).await?;
|
||||
update_user_contacts(contact_user_id, &session).await?;
|
||||
}
|
||||
|
||||
if let Some(live_kit) = session.app_state.live_kit_client.as_ref() {
|
||||
|
||||
@@ -236,7 +236,7 @@ impl ConnectionPool {
|
||||
}
|
||||
PrincipalId::DevServerId(dev_server_id) => {
|
||||
assert_eq!(
|
||||
self.connected_dev_servers.get(dev_server_id).unwrap(),
|
||||
self.connected_dev_servers.get(&dev_server_id).unwrap(),
|
||||
connection_id
|
||||
);
|
||||
}
|
||||
@@ -300,9 +300,9 @@ impl ChannelPool {
|
||||
}
|
||||
|
||||
pub fn remove_user(&mut self, user_id: &UserId) {
|
||||
if let Some(channels) = self.by_user.remove(user_id) {
|
||||
if let Some(channels) = self.by_user.remove(&user_id) {
|
||||
for channel_id in channels.keys() {
|
||||
self.unsubscribe(user_id, channel_id)
|
||||
self.unsubscribe(user_id, &channel_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ async fn test_core_channel_buffers(
|
||||
|
||||
// Client B sees that client A is gone from the channel buffer.
|
||||
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_collaborators(buffer.collaborators(), &[client_b.user_id()]);
|
||||
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
|
||||
});
|
||||
|
||||
// Client A rejoins the channel buffer
|
||||
@@ -99,7 +99,7 @@ async fn test_core_channel_buffers(
|
||||
// Sanity test, make sure we saw A rejoining
|
||||
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_collaborators(
|
||||
buffer.collaborators(),
|
||||
&buffer.collaborators(),
|
||||
&[client_a.user_id(), client_b.user_id()],
|
||||
);
|
||||
});
|
||||
@@ -111,7 +111,7 @@ async fn test_core_channel_buffers(
|
||||
|
||||
// Client B observes A disconnect
|
||||
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_collaborators(buffer.collaborators(), &[client_b.user_id()]);
|
||||
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
|
||||
});
|
||||
|
||||
// TODO:
|
||||
@@ -687,7 +687,7 @@ fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Op
|
||||
user_ids.sort();
|
||||
assert_eq!(
|
||||
user_ids,
|
||||
ids.iter().map(|id| id.unwrap()).collect::<Vec<_>>()
|
||||
ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -356,7 +356,7 @@ async fn test_channel_message_changes(
|
||||
let project_b = client_b.build_empty_local_project(cx_b);
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
|
||||
let chat_panel_b = workspace_b.update(cx_b, ChatPanel::new);
|
||||
let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx));
|
||||
chat_panel_b
|
||||
.update(cx_b, |chat_panel, cx| {
|
||||
chat_panel.set_active(true, cx);
|
||||
|
||||
@@ -96,7 +96,7 @@ async fn test_dev_server(cx: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppC
|
||||
|
||||
let content = dev_server
|
||||
.fs()
|
||||
.load(Path::new("/remote/1.txt"))
|
||||
.load(&Path::new("/remote/1.txt"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(content, "wow!remote\nremote\nremote\n");
|
||||
@@ -599,7 +599,7 @@ async fn test_save_as_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::Tes
|
||||
let title = remote_workspace
|
||||
.update(&mut cx, |ws, cx| {
|
||||
let active_item = ws.active_item(cx).unwrap();
|
||||
active_item.tab_description(0, cx).unwrap()
|
||||
active_item.tab_description(0, &cx).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -607,7 +607,7 @@ async fn test_save_as_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::Tes
|
||||
|
||||
let path = Path::new("/remote/2.txt");
|
||||
assert_eq!(
|
||||
dev_server.fs().load(path).await.unwrap(),
|
||||
dev_server.fs().load(&path).await.unwrap(),
|
||||
"remote\nremote\nremote"
|
||||
);
|
||||
}
|
||||
@@ -632,12 +632,12 @@ async fn test_new_file_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::Te
|
||||
|
||||
let title = remote_workspace
|
||||
.update(&mut cx, |ws, cx| {
|
||||
ws.active_item(cx).unwrap().tab_description(0, cx).unwrap()
|
||||
ws.active_item(cx).unwrap().tab_description(0, &cx).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(title, "2.txt");
|
||||
|
||||
let path = Path::new("/remote/2.txt");
|
||||
assert_eq!(dev_server.fs().load(path).await.unwrap(), "new!");
|
||||
assert_eq!(dev_server.fs().load(&path).await.unwrap(), "new!");
|
||||
}
|
||||
|
||||
@@ -2109,7 +2109,7 @@ struct Row10;"#};
|
||||
editor_cx_a.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
|
||||
assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
|
||||
assert_eq!(
|
||||
all_hunks,
|
||||
@@ -2146,7 +2146,7 @@ struct Row10;"#};
|
||||
editor_cx_b.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
|
||||
assert_eq!(
|
||||
expanded_hunks_background_highlights(editor, cx),
|
||||
vec![DisplayRow(1)..=DisplayRow(2), DisplayRow(8)..=DisplayRow(8)],
|
||||
@@ -2194,7 +2194,7 @@ struct Row10;"#};
|
||||
editor_cx_a.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
|
||||
assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
|
||||
assert_eq!(
|
||||
all_hunks,
|
||||
@@ -2209,7 +2209,7 @@ struct Row10;"#};
|
||||
editor_cx_b.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let all_hunks = editor_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
|
||||
let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
|
||||
assert_eq!(
|
||||
expanded_hunks_background_highlights(editor, cx),
|
||||
vec![DisplayRow(5)..=DisplayRow(5)]
|
||||
|
||||
@@ -506,7 +506,7 @@ async fn test_basic_following(
|
||||
|
||||
// Client B activates an item that doesn't implement following,
|
||||
// so the previously-opened screen-sharing item gets activated.
|
||||
let unfollowable_item = cx_b.new_view(TestItem::new);
|
||||
let unfollowable_item = cx_b.new_view(|cx| TestItem::new(cx));
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
|
||||
|
||||
@@ -538,7 +538,10 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
|
||||
|
||||
// Leave the room
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.hang_up(cx))
|
||||
.update(cx_a, |call, cx| {
|
||||
let hang_up = call.hang_up(cx);
|
||||
hang_up
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -571,7 +574,10 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
|
||||
|
||||
// Leave the room
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.hang_up(cx))
|
||||
.update(cx_a, |call, cx| {
|
||||
let hang_up = call.hang_up(cx);
|
||||
hang_up
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -2572,7 +2578,7 @@ async fn test_git_diff_base_change(
|
||||
);
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
);
|
||||
@@ -2596,7 +2602,7 @@ async fn test_git_diff_base_change(
|
||||
);
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
);
|
||||
@@ -2620,7 +2626,7 @@ async fn test_git_diff_base_change(
|
||||
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
);
|
||||
@@ -2635,7 +2641,7 @@ async fn test_git_diff_base_change(
|
||||
);
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
);
|
||||
@@ -2678,7 +2684,7 @@ async fn test_git_diff_base_change(
|
||||
);
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
);
|
||||
@@ -2702,7 +2708,7 @@ async fn test_git_diff_base_change(
|
||||
);
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
);
|
||||
@@ -2735,7 +2741,7 @@ async fn test_git_diff_base_change(
|
||||
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
);
|
||||
@@ -2750,7 +2756,7 @@ async fn test_git_diff_base_change(
|
||||
);
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
buffer,
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
);
|
||||
@@ -2878,8 +2884,8 @@ async fn test_git_status_sync(
|
||||
client_a.fs().set_status_for_repo_via_git_operation(
|
||||
Path::new("/dir/.git"),
|
||||
&[
|
||||
(Path::new(A_TXT), GitFileStatus::Added),
|
||||
(Path::new(B_TXT), GitFileStatus::Added),
|
||||
(&Path::new(A_TXT), GitFileStatus::Added),
|
||||
(&Path::new(B_TXT), GitFileStatus::Added),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -2926,8 +2932,8 @@ async fn test_git_status_sync(
|
||||
client_a.fs().set_status_for_repo_via_working_copy_change(
|
||||
Path::new("/dir/.git"),
|
||||
&[
|
||||
(Path::new(A_TXT), GitFileStatus::Modified),
|
||||
(Path::new(B_TXT), GitFileStatus::Modified),
|
||||
(&Path::new(A_TXT), GitFileStatus::Modified),
|
||||
(&Path::new(B_TXT), GitFileStatus::Modified),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -3324,7 +3330,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
|
||||
@@ -3343,7 +3349,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), r#"{}"#.to_string()),
|
||||
@@ -3372,7 +3378,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||
@@ -3404,7 +3410,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
|
||||
)
|
||||
@@ -6330,7 +6336,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
|
||||
// Close permanent tab
|
||||
pane.update(cx, |pane, cx| {
|
||||
let id = pane.items().next().unwrap().item_id();
|
||||
let id = pane.items().nth(0).unwrap().item_id();
|
||||
pane.close_item_by_id(id, workspace::SaveIntent::Skip, cx)
|
||||
})
|
||||
.await
|
||||
@@ -6341,7 +6347,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().next().unwrap().item_id())
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
@@ -6360,7 +6366,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().next().unwrap().item_id())
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
@@ -6389,7 +6395,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(get_path(pane, 0, cx), path_1.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().next().unwrap().item_id())
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
@@ -6427,7 +6433,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
assert_eq!(get_path(pane, 0, cx), path_2.clone());
|
||||
assert_eq!(
|
||||
pane.preview_item_id(),
|
||||
Some(pane.items().next().unwrap().item_id())
|
||||
Some(pane.items().nth(0).unwrap().item_id())
|
||||
);
|
||||
|
||||
assert!(pane.can_navigate_backward());
|
||||
|
||||
@@ -282,7 +282,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
let mut paths = client.fs().paths(false);
|
||||
paths.remove(0);
|
||||
let new_root_path = if paths.is_empty() || rng.gen() {
|
||||
Path::new("/").join(plan.next_root_dir_name())
|
||||
Path::new("/").join(&plan.next_root_dir_name())
|
||||
} else {
|
||||
paths.choose(rng).unwrap().clone()
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use gpui::{Context as _, TestAppContext};
|
||||
use language::language_settings::all_language_settings;
|
||||
use remote::SshSession;
|
||||
use remote_server::HeadlessProject;
|
||||
use serde_json::json;
|
||||
@@ -30,9 +29,6 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
"/code",
|
||||
json!({
|
||||
"project1": {
|
||||
".zed": {
|
||||
"settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
|
||||
},
|
||||
"README.md": "# project 1",
|
||||
"src": {
|
||||
"lib.rs": "fn one() -> usize { 1 }"
|
||||
@@ -72,8 +68,6 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
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"),
|
||||
@@ -94,18 +88,6 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
buffer.edit([(ix..ix + 1, "100")], None, cx);
|
||||
});
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
cx_b.read(|cx| {
|
||||
let file = buffer_b.read(cx).file();
|
||||
assert_eq!(
|
||||
all_language_settings(file, cx)
|
||||
.language(Some("Rust"))
|
||||
.language_servers,
|
||||
["override-rust-analyzer".into()]
|
||||
)
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| project.save_buffer(buffer_b, cx))
|
||||
.await
|
||||
|
||||
@@ -263,7 +263,8 @@ impl TestServer {
|
||||
})
|
||||
});
|
||||
|
||||
let git_hosting_provider_registry = cx.update(GitHostingProviderRegistry::default_global);
|
||||
let git_hosting_provider_registry =
|
||||
cx.update(|cx| GitHostingProviderRegistry::default_global(cx));
|
||||
git_hosting_provider_registry
|
||||
.register_hosting_provider(Arc::new(git_hosting_providers::Github));
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ impl ChannelView {
|
||||
{
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(&map)])
|
||||
})
|
||||
});
|
||||
return;
|
||||
@@ -460,7 +460,8 @@ impl Item for ChannelView {
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, Item::deactivated)
|
||||
self.editor
|
||||
.update(cx, |editor, cx| Item::deactivated(editor, cx))
|
||||
}
|
||||
|
||||
fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -560,7 +560,7 @@ impl ChatPanel {
|
||||
},
|
||||
)
|
||||
.child(
|
||||
self.render_popover_buttons(cx, message_id, can_delete_message, can_edit_message)
|
||||
self.render_popover_buttons(&cx, message_id, can_delete_message, can_edit_message)
|
||||
.mt_neg_2p5(),
|
||||
)
|
||||
}
|
||||
@@ -705,7 +705,7 @@ impl ChatPanel {
|
||||
menu.entry(
|
||||
"Copy message text",
|
||||
None,
|
||||
cx.handler_for(this, move |this, cx| {
|
||||
cx.handler_for(&this, move |this, cx| {
|
||||
if let Some(message) = this.active_chat().and_then(|active_chat| {
|
||||
active_chat.read(cx).find_loaded_message(message_id)
|
||||
}) {
|
||||
@@ -718,7 +718,7 @@ impl ChatPanel {
|
||||
menu.entry(
|
||||
"Delete message",
|
||||
None,
|
||||
cx.handler_for(this, move |this, cx| this.remove_message(message_id, cx)),
|
||||
cx.handler_for(&this, move |this, cx| this.remove_message(message_id, cx)),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -802,11 +802,13 @@ impl ChatPanel {
|
||||
{
|
||||
task.detach();
|
||||
}
|
||||
} else if let Some(task) = chat
|
||||
.update(cx, |chat, cx| chat.send_message(message, cx))
|
||||
.log_err()
|
||||
{
|
||||
task.detach();
|
||||
} else {
|
||||
if let Some(task) = chat
|
||||
.update(cx, |chat, cx| chat.send_message(message, cx))
|
||||
.log_err()
|
||||
{
|
||||
task.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -852,7 +854,7 @@ impl ChatPanel {
|
||||
let scroll_to_message_id = this.update(&mut cx, |this, cx| {
|
||||
this.set_active_chat(chat.clone(), cx);
|
||||
|
||||
scroll_to_message_id.or(this.last_acknowledged_message_id)
|
||||
scroll_to_message_id.or_else(|| this.last_acknowledged_message_id)
|
||||
})?;
|
||||
|
||||
if let Some(message_id) = scroll_to_message_id {
|
||||
|
||||
@@ -293,8 +293,8 @@ impl MessageEditor {
|
||||
completion_fn: impl Fn(&StringMatch) -> (String, CodeLabel),
|
||||
) -> Vec<Completion> {
|
||||
let matches = fuzzy::match_strings(
|
||||
candidates,
|
||||
query,
|
||||
&candidates,
|
||||
&query,
|
||||
true,
|
||||
10,
|
||||
&Default::default(),
|
||||
@@ -346,7 +346,7 @@ impl MessageEditor {
|
||||
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let query = buffer.update(cx, |buffer, _| {
|
||||
let Some(query) = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == '@' {
|
||||
@@ -358,7 +358,9 @@ impl MessageEditor {
|
||||
query.push(ch);
|
||||
}
|
||||
None
|
||||
})?;
|
||||
}) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let start_offset = end_offset - query.len();
|
||||
let start_anchor = buffer.read(cx).anchor_before(start_offset);
|
||||
@@ -412,7 +414,7 @@ impl MessageEditor {
|
||||
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let query = buffer.update(cx, |buffer, _| {
|
||||
let Some(query) = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == ':' {
|
||||
@@ -448,7 +450,9 @@ impl MessageEditor {
|
||||
query.push(ch);
|
||||
}
|
||||
None
|
||||
})?;
|
||||
}) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let start_offset = end_offset - query.len() - 1;
|
||||
let start_anchor = buffer.read(cx).anchor_before(start_offset);
|
||||
|
||||
@@ -219,7 +219,7 @@ impl CollabPanel {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let channel_name_editor = cx.new_view(Editor::single_line);
|
||||
let channel_name_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
|
||||
cx.subscribe(&channel_name_editor, |this: &mut Self, _, event, cx| {
|
||||
if let editor::EditorEvent::Blurred = event {
|
||||
@@ -328,7 +328,7 @@ impl CollabPanel {
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
panel.collapsed_channels = serialized_panel
|
||||
.collapsed_channels
|
||||
.unwrap_or_else(Vec::new)
|
||||
.unwrap_or_else(|| Vec::new())
|
||||
.iter()
|
||||
.map(|cid| ChannelId(*cid))
|
||||
.collect();
|
||||
@@ -955,7 +955,7 @@ impl CollabPanel {
|
||||
}
|
||||
|
||||
fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if self.channel_editing_state.take().is_some() {
|
||||
if let Some(_) = self.channel_editing_state.take() {
|
||||
self.channel_name_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("", cx);
|
||||
});
|
||||
@@ -1850,7 +1850,8 @@ impl CollabPanel {
|
||||
if let Some(contact) = self.selected_contact() {
|
||||
self.deploy_contact_context_menu(bounds.center(), contact, cx);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn selected_channel(&self) -> Option<&Arc<Channel>> {
|
||||
@@ -2141,7 +2142,7 @@ impl CollabPanel {
|
||||
} => self
|
||||
.render_participant_project(
|
||||
*project_id,
|
||||
worktree_root_names,
|
||||
&worktree_root_names,
|
||||
*host_user_id,
|
||||
*is_last,
|
||||
is_selected,
|
||||
@@ -2400,7 +2401,7 @@ impl CollabPanel {
|
||||
) -> impl IntoElement {
|
||||
let github_login = SharedString::from(user.github_login.clone());
|
||||
let user_id = user.id;
|
||||
let is_response_pending = self.user_store.read(cx).is_contact_request_pending(user);
|
||||
let is_response_pending = self.user_store.read(cx).is_contact_request_pending(&user);
|
||||
let color = if is_response_pending {
|
||||
Color::Muted
|
||||
} else {
|
||||
@@ -2456,7 +2457,7 @@ impl CollabPanel {
|
||||
let response_is_pending = self
|
||||
.channel_store
|
||||
.read(cx)
|
||||
.has_pending_channel_invite_response(channel);
|
||||
.has_pending_channel_invite_response(&channel);
|
||||
let color = if response_is_pending {
|
||||
Color::Muted
|
||||
} else {
|
||||
@@ -2831,7 +2832,7 @@ impl Panel for CollabPanel {
|
||||
fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
|
||||
CollaborationPanelSettings::get_global(cx)
|
||||
.button
|
||||
.then_some(ui::IconName::Collab)
|
||||
.then(|| ui::IconName::Collab)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
|
||||
@@ -309,7 +309,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
let members = search_members.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.delegate.has_all_members =
|
||||
query.is_empty() && members.len() < 100;
|
||||
query == "" && members.len() < 100;
|
||||
picker.delegate.matching_member_indices =
|
||||
(0..members.len()).collect();
|
||||
picker.delegate.members = members;
|
||||
|
||||
@@ -154,7 +154,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
.selected(selected)
|
||||
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
||||
.child(Label::new(user.github_login.clone()))
|
||||
.end_slot::<Icon>(icon_path.map(Icon::from_path)),
|
||||
.end_slot::<Icon>(icon_path.map(|icon_path| Icon::from_path(icon_path))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
chat_panel::init(cx);
|
||||
collab_panel::init(cx);
|
||||
notification_panel::init(cx);
|
||||
notifications::init(app_state, cx);
|
||||
notifications::init(&app_state, cx);
|
||||
title_bar::init(cx);
|
||||
vcs_menu::init(cx);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ impl NotificationPanel {
|
||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let mut status = client.status();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
while (status.next().await).is_some() {
|
||||
while let Some(_) = status.next().await {
|
||||
if this
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.notify();
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
room::Event::RemoteProjectUnshared { project_id }
|
||||
| room::Event::RemoteProjectJoined { project_id }
|
||||
| room::Event::RemoteProjectInvitationDiscarded { project_id } => {
|
||||
if let Some(windows) = notification_windows.remove(project_id) {
|
||||
if let Some(windows) = notification_windows.remove(&project_id) {
|
||||
for window in windows {
|
||||
window
|
||||
.update(cx, |_, cx| {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow;
|
||||
use gpui::Pixels;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
@@ -273,7 +273,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
let mut commands = self.all_commands.clone();
|
||||
let hit_counts = cx.global::<HitCounts>().clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
let query = trim_consecutive_whitespaces(query.as_str());
|
||||
let query = trim_consecutive_whitespaces(&query.as_str());
|
||||
async move {
|
||||
commands.sort_by_key(|action| {
|
||||
(
|
||||
@@ -303,7 +303,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
fuzzy::match_strings(
|
||||
let ret = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
true,
|
||||
@@ -311,7 +311,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
&Default::default(),
|
||||
executor,
|
||||
)
|
||||
.await
|
||||
.await;
|
||||
ret
|
||||
};
|
||||
|
||||
tx.send((commands, matches)).await.log_err();
|
||||
|
||||
@@ -118,7 +118,9 @@ impl CommandPaletteInterceptor {
|
||||
|
||||
/// Intercepts the given query from the command palette.
|
||||
pub fn intercept(&self, query: &str, cx: &AppContext) -> Option<CommandInterceptResult> {
|
||||
let handler = self.0.as_ref()?;
|
||||
let Some(handler) = self.0.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
(handler)(query, cx)
|
||||
}
|
||||
|
||||
@@ -235,13 +235,13 @@ impl Client {
|
||||
let content = buffer.trim();
|
||||
|
||||
if !content.is_empty() {
|
||||
if let Ok(response) = serde_json::from_str::<AnyResponse>(content) {
|
||||
if let Ok(response) = serde_json::from_str::<AnyResponse>(&content) {
|
||||
if let Some(handlers) = response_handlers.lock().as_mut() {
|
||||
if let Some(handler) = handlers.remove(&response.id) {
|
||||
handler(Ok(content.to_string()));
|
||||
}
|
||||
}
|
||||
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(content) {
|
||||
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(&content) {
|
||||
let mut notification_handlers = notification_handlers.lock();
|
||||
if let Some(handler) =
|
||||
notification_handlers.get_mut(notification.method.as_str())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use gpui::{actions, AppContext, Context, ViewContext};
|
||||
use log;
|
||||
use manager::ContextServerManager;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -26,7 +27,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
fn restart_servers(_workspace: &mut Workspace, _action: &Restart, cx: &mut ViewContext<Workspace>) {
|
||||
let model = ContextServerManager::global(cx);
|
||||
let model = ContextServerManager::global(&cx);
|
||||
cx.update_model(&model, |manager, cx| {
|
||||
for server in manager.servers() {
|
||||
manager
|
||||
|
||||
@@ -39,7 +39,6 @@ pub struct ServerConfig {
|
||||
pub id: String,
|
||||
pub executable: String,
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Settings for ContextServerSettings {
|
||||
@@ -71,13 +70,13 @@ impl ContextServer {
|
||||
}
|
||||
|
||||
async fn start(&self, cx: &AsyncAppContext) -> anyhow::Result<()> {
|
||||
log::info!("starting context server {}", self.config.id,);
|
||||
log::info!("starting context server {}", self.config.id);
|
||||
let client = Client::new(
|
||||
client::ContextServerId(self.config.id.clone()),
|
||||
client::ModelContextServerBinary {
|
||||
executable: Path::new(&self.config.executable).to_path_buf(),
|
||||
args: self.config.args.clone(),
|
||||
env: self.config.env.clone(),
|
||||
env: None,
|
||||
},
|
||||
cx.clone(),
|
||||
)?;
|
||||
@@ -125,12 +124,6 @@ pub enum Event {
|
||||
impl Global for ContextServerManager {}
|
||||
impl EventEmitter<Event> for ContextServerManager {}
|
||||
|
||||
impl Default for ContextServerManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextServerManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -422,86 +422,90 @@ impl Copilot {
|
||||
(this, fake_server)
|
||||
}
|
||||
|
||||
async fn start_language_server(
|
||||
fn start_language_server(
|
||||
new_server_id: LanguageServerId,
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
this: WeakModel<Self>,
|
||||
mut cx: AsyncAppContext,
|
||||
) {
|
||||
let start_language_server = async {
|
||||
let server_path = get_copilot_lsp(http).await?;
|
||||
let node_path = node_runtime.binary_path().await?;
|
||||
let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
||||
let binary = LanguageServerBinary {
|
||||
path: node_path,
|
||||
arguments,
|
||||
// TODO: We could set HTTP_PROXY etc here and fix the copilot issue.
|
||||
env: None,
|
||||
) -> impl Future<Output = ()> {
|
||||
async move {
|
||||
let start_language_server = async {
|
||||
let server_path = get_copilot_lsp(http).await?;
|
||||
let node_path = node_runtime.binary_path().await?;
|
||||
let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
||||
let binary = LanguageServerBinary {
|
||||
path: node_path,
|
||||
arguments,
|
||||
// TODO: We could set HTTP_PROXY etc here and fix the copilot issue.
|
||||
env: None,
|
||||
};
|
||||
|
||||
let root_path = if cfg!(target_os = "windows") {
|
||||
Path::new("C:/")
|
||||
} else {
|
||||
Path::new("/")
|
||||
};
|
||||
|
||||
let server = LanguageServer::new(
|
||||
Arc::new(Mutex::new(None)),
|
||||
new_server_id,
|
||||
binary,
|
||||
root_path,
|
||||
None,
|
||||
cx.clone(),
|
||||
)?;
|
||||
|
||||
server
|
||||
.on_notification::<StatusNotification, _>(
|
||||
|_, _| { /* Silence the notification */ },
|
||||
)
|
||||
.detach();
|
||||
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
|
||||
|
||||
let status = server
|
||||
.request::<request::CheckStatus>(request::CheckStatusParams {
|
||||
local_checks_only: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
server
|
||||
.request::<request::SetEditorInfo>(request::SetEditorInfoParams {
|
||||
editor_info: request::EditorInfo {
|
||||
name: "zed".into(),
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
},
|
||||
editor_plugin_info: request::EditorPluginInfo {
|
||||
name: "zed-copilot".into(),
|
||||
version: "0.0.1".into(),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
anyhow::Ok((server, status))
|
||||
};
|
||||
|
||||
let root_path = if cfg!(target_os = "windows") {
|
||||
Path::new("C:/")
|
||||
} else {
|
||||
Path::new("/")
|
||||
};
|
||||
|
||||
let server = LanguageServer::new(
|
||||
Arc::new(Mutex::new(None)),
|
||||
new_server_id,
|
||||
binary,
|
||||
root_path,
|
||||
None,
|
||||
cx.clone(),
|
||||
)?;
|
||||
|
||||
server
|
||||
.on_notification::<StatusNotification, _>(|_, _| { /* Silence the notification */ })
|
||||
.detach();
|
||||
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
|
||||
|
||||
let status = server
|
||||
.request::<request::CheckStatus>(request::CheckStatusParams {
|
||||
local_checks_only: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
server
|
||||
.request::<request::SetEditorInfo>(request::SetEditorInfoParams {
|
||||
editor_info: request::EditorInfo {
|
||||
name: "zed".into(),
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
},
|
||||
editor_plugin_info: request::EditorPluginInfo {
|
||||
name: "zed-copilot".into(),
|
||||
version: "0.0.1".into(),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
anyhow::Ok((server, status))
|
||||
};
|
||||
|
||||
let server = start_language_server.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.notify();
|
||||
match server {
|
||||
Ok((server, status)) => {
|
||||
this.server = CopilotServer::Running(RunningCopilotServer {
|
||||
lsp: server,
|
||||
sign_in_status: SignInStatus::SignedOut,
|
||||
registered_buffers: Default::default(),
|
||||
});
|
||||
cx.emit(Event::CopilotLanguageServerStarted);
|
||||
this.update_sign_in_status(status, cx);
|
||||
let server = start_language_server.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.notify();
|
||||
match server {
|
||||
Ok((server, status)) => {
|
||||
this.server = CopilotServer::Running(RunningCopilotServer {
|
||||
lsp: server,
|
||||
sign_in_status: SignInStatus::SignedOut,
|
||||
registered_buffers: Default::default(),
|
||||
});
|
||||
cx.emit(Event::CopilotLanguageServerStarted);
|
||||
this.update_sign_in_status(status, cx);
|
||||
}
|
||||
Err(error) => {
|
||||
this.server = CopilotServer::Error(error.to_string().into());
|
||||
cx.notify()
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
this.server = CopilotServer::Error(error.to_string().into());
|
||||
cx.notify()
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
@@ -1001,7 +1005,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
|
||||
let url = &release
|
||||
.assets
|
||||
.first()
|
||||
.get(0)
|
||||
.context("Github release for copilot contained no assets")?
|
||||
.browser_download_url;
|
||||
|
||||
@@ -1250,8 +1254,8 @@ mod tests {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn worktree_id(&self, _: &AppContext) -> settings::WorktreeId {
|
||||
settings::WorktreeId::from_usize(0)
|
||||
fn worktree_id(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn is_private(&self) -> bool {
|
||||
|
||||
@@ -15,8 +15,9 @@ use settings::watch_config_file;
|
||||
use strum::EnumIter;
|
||||
use ui::Context;
|
||||
|
||||
pub const COPILOT_CHAT_COMPLETION_URL: &str = "https://api.githubcopilot.com/chat/completions";
|
||||
pub const COPILOT_CHAT_AUTH_URL: &str = "https://api.github.com/copilot_internal/v2/token";
|
||||
pub const COPILOT_CHAT_COMPLETION_URL: &'static str =
|
||||
"https://api.githubcopilot.com/chat/completions";
|
||||
pub const COPILOT_CHAT_AUTH_URL: &'static str = "https://api.github.com/copilot_internal/v2/token";
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
|
||||
@@ -145,7 +145,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
};
|
||||
}
|
||||
Direction::Next => {
|
||||
if self.completions.is_empty() {
|
||||
if self.completions.len() == 0 {
|
||||
self.active_completion_index = 0
|
||||
} else {
|
||||
self.active_completion_index =
|
||||
@@ -221,13 +221,15 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
if should_report_inline_completion_event && self.active_completion().is_some() {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
telemetry.report_inline_completion_event(
|
||||
Self::name().to_string(),
|
||||
false,
|
||||
self.file_extension.clone(),
|
||||
);
|
||||
if should_report_inline_completion_event {
|
||||
if self.active_completion().is_some() {
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
telemetry.report_inline_completion_event(
|
||||
Self::name().to_string(),
|
||||
false,
|
||||
self.file_extension.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1146,7 +1148,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
|
||||
cx.update(|cx| {
|
||||
_ = cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
@@ -167,7 +167,7 @@ impl Render for CopilotCodeVerification {
|
||||
let prompt = match &self.status {
|
||||
Status::SigningIn {
|
||||
prompt: Some(prompt),
|
||||
} => Self::render_prompting_modal(self.connect_clicked, prompt, cx).into_any_element(),
|
||||
} => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
|
||||
Status::Unauthorized => {
|
||||
self.connect_clicked = false;
|
||||
Self::render_unauthorized_modal(cx).into_any_element()
|
||||
|
||||
@@ -18,7 +18,7 @@ use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
use sqlez_macros::sql;
|
||||
use std::env;
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::LazyLock;
|
||||
use util::{maybe, ResultExt};
|
||||
@@ -79,7 +79,7 @@ pub async fn open_db<M: Migrator + 'static>(
|
||||
open_fallback_db().await
|
||||
}
|
||||
|
||||
async fn open_main_db<M: Migrator>(db_path: &Path) -> Option<ThreadSafeConnection<M>> {
|
||||
async fn open_main_db<M: Migrator>(db_path: &PathBuf) -> Option<ThreadSafeConnection<M>> {
|
||||
log::info!("Opening main db");
|
||||
ThreadSafeConnection::<M>::builder(db_path.to_string_lossy().as_ref(), true)
|
||||
.with_db_initialization_query(DB_INITIALIZE_QUERY)
|
||||
|
||||
@@ -28,7 +28,7 @@ impl From<proto::DevServerProject> for DevServerProject {
|
||||
fn from(project: proto::DevServerProject) -> Self {
|
||||
Self {
|
||||
id: DevServerProjectId(project.id),
|
||||
project_id: project.project_id.map(ProjectId),
|
||||
project_id: project.project_id.map(|id| ProjectId(id)),
|
||||
paths: project.paths.into_iter().map(|path| path.into()).collect(),
|
||||
dev_server_id: DevServerId(project.dev_server_id),
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ impl ProjectDiagnosticsEditor {
|
||||
.unwrap();
|
||||
|
||||
prev_excerpt_id = excerpt_id;
|
||||
first_excerpt_id.get_or_insert(prev_excerpt_id);
|
||||
first_excerpt_id.get_or_insert_with(|| prev_excerpt_id);
|
||||
group_state.excerpts.push(excerpt_id);
|
||||
let header_position = (excerpt_id, language::Anchor::MIN);
|
||||
|
||||
@@ -491,7 +491,7 @@ impl ProjectDiagnosticsEditor {
|
||||
blocks_to_remove.extend(group_state.blocks.iter().copied());
|
||||
} else if let Some((_, group_state)) = to_keep {
|
||||
prev_excerpt_id = *group_state.excerpts.last().unwrap();
|
||||
first_excerpt_id.get_or_insert(prev_excerpt_id);
|
||||
first_excerpt_id.get_or_insert_with(|| prev_excerpt_id);
|
||||
path_state.diagnostic_groups.push(group_state);
|
||||
}
|
||||
}
|
||||
@@ -776,7 +776,7 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
const DIAGNOSTIC_HEADER: &str = "diagnostic header";
|
||||
const DIAGNOSTIC_HEADER: &'static str = "diagnostic header";
|
||||
|
||||
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user