Compare commits
26 Commits
vim-norm
...
git-panel-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8566ed816 | ||
|
|
556fe6c14f | ||
|
|
72cb786e5b | ||
|
|
73dceb5cf2 | ||
|
|
9fad819749 | ||
|
|
312534ffe7 | ||
|
|
1d176f1a6b | ||
|
|
e301b7bbf5 | ||
|
|
a047c02bcc | ||
|
|
c82a27e4f9 | ||
|
|
d44a1a4474 | ||
|
|
6852bf5488 | ||
|
|
ea678027e0 | ||
|
|
232e0883f8 | ||
|
|
8a6c6174f9 | ||
|
|
5146520df8 | ||
|
|
93007d42a6 | ||
|
|
4438c4c1c3 | ||
|
|
2a152ddcdc | ||
|
|
c62b10792c | ||
|
|
ae50babfb4 | ||
|
|
c25079adb6 | ||
|
|
197591f557 | ||
|
|
1ced12fbaf | ||
|
|
c2971819b4 | ||
|
|
5a170ad359 |
@@ -1,12 +0,0 @@
|
||||
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
|
||||
# This file is placed in ./../.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with ./../.cargo/config.toml
|
||||
# with preference for settings from Zeds config.toml.
|
||||
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
|
||||
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
|
||||
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
|
||||
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
|
||||
# would be incovenient.
|
||||
# The reason for not using the RUSTFLAGS environment variable is that doing so would override all the settings in the config.toml file, even if the contents of the latter are completely nonsensical. See: https://github.com/rust-lang/cargo/issues/5376
|
||||
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
|
||||
[target.'cfg(all())']
|
||||
rustflags = ["-D", "warnings"]
|
||||
@@ -19,10 +19,6 @@ rustflags = ["-C", "link-args=-Objc -all_load"]
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "link-args=-Objc -all_load"]
|
||||
|
||||
# This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = [
|
||||
"--cfg",
|
||||
"windows_slim_errors", # This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes
|
||||
"-C",
|
||||
"target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows
|
||||
]
|
||||
rustflags = ["--cfg", "windows_slim_errors"]
|
||||
|
||||
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -7,7 +7,7 @@ runs:
|
||||
- name: Install Rust
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
cargo install cargo-nextest --locked
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
|
||||
113
.github/workflows/ci.yml
vendored
113
.github/workflows/ci.yml
vendored
@@ -7,10 +7,10 @@ on:
|
||||
- "v[0-9]+.[0-9]+.x"
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
@@ -21,8 +21,34 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
jobs:
|
||||
check_docs_only:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docs_only: ${{ steps.check_changes.outputs.docs_only }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for non-docs changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "merge_group" ]; then
|
||||
# When we're running in a merge queue, never assume that the changes
|
||||
# are docs-only, as there could be other PRs in the group that
|
||||
# contain non-docs changes.
|
||||
echo "Running in the merge queue"
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
elif git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
|
||||
echo "Detected non-docs changes"
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Docs-only change"
|
||||
echo "docs_only=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
migration_checks:
|
||||
name: Check Postgres and Protobuf migrations, mergability
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
@@ -77,7 +103,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
# To support writing comments that they will certainly be revisited.
|
||||
- name: Check for todo! and FIXME comments
|
||||
- name: Check for todo! and FIXME comments
|
||||
run: script/check-todos
|
||||
|
||||
- name: Run style checks
|
||||
@@ -95,41 +121,43 @@ jobs:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
|
||||
- name: cargo clippy
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Check unused dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: bnjbvr/cargo-machete@main
|
||||
|
||||
- name: Check licenses
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: |
|
||||
script/check-licenses
|
||||
script/generate-licenses /tmp/zed_licenses_output
|
||||
|
||||
- name: Check for new vulnerable dependencies
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
|
||||
with:
|
||||
license-check: false
|
||||
|
||||
- name: Run tests
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build collab
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: cargo build -p collab
|
||||
|
||||
- name: Build other binaries and features
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: |
|
||||
cargo build --workspace --bins --all-features
|
||||
cargo check -p gpui --features "macos-blade"
|
||||
@@ -137,17 +165,13 @@ jobs:
|
||||
cargo build -p remote_server
|
||||
script/check-rust-livekit-macos
|
||||
|
||||
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: rm -rf ./../.cargo
|
||||
|
||||
linux_tests:
|
||||
timeout-minutes: 60
|
||||
name: (Linux) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -158,44 +182,37 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/linux
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
|
||||
- name: cargo clippy
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Run tests
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Build other binaries and features
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: |
|
||||
cargo build -p zed
|
||||
cargo check -p workspace
|
||||
|
||||
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
|
||||
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
|
||||
# to clean up the config file, I’ve included the cleanup code here as a precaution.
|
||||
# While it’s not strictly necessary at this moment, I believe it’s better to err on the side of caution.
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: rm -rf ./../.cargo
|
||||
|
||||
build_remote_server:
|
||||
timeout-minutes: 60
|
||||
name: (Linux) Build Remote Server
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
@@ -206,32 +223,27 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
|
||||
- name: Install Clang & Mold
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: ./script/remote-server && ./script/install-mold 2.34.0
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
|
||||
- name: Build Remote Server
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: cargo build -p remote_server
|
||||
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: rm -rf ./../.cargo
|
||||
|
||||
# todo(windows): Actually run the tests
|
||||
windows_tests:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: hosted-windows-1
|
||||
needs: check_docs_only
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
@@ -242,30 +254,23 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
|
||||
- name: cargo clippy
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||
run: cargo xtask clippy
|
||||
|
||||
- name: Build Zed
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
run: cargo build
|
||||
|
||||
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: Remove-Item -Path "./../.cargo" -Recurse -Force
|
||||
|
||||
bundle-mac:
|
||||
timeout-minutes: 120
|
||||
timeout-minutes: 60
|
||||
name: Create a macOS bundle
|
||||
runs-on:
|
||||
- self-hosted
|
||||
@@ -327,14 +332,14 @@ jobs:
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -354,9 +359,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
bundle-linux-x86_x64:
|
||||
bundle-linux:
|
||||
timeout-minutes: 60
|
||||
name: Linux x86_x64 release bundle
|
||||
name: Create a Linux bundle
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2004
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
@@ -385,7 +390,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -404,7 +409,7 @@ jobs:
|
||||
|
||||
bundle-linux-aarch64: # this runs on ubuntu22.04
|
||||
timeout-minutes: 60
|
||||
name: Linux arm64 release bundle
|
||||
name: Create arm64 Linux bundle
|
||||
runs-on:
|
||||
- buildjet-16vcpu-ubuntu-2204-arm
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
@@ -433,7 +438,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
@@ -453,7 +458,7 @@ jobs:
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
|
||||
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64]
|
||||
needs: [bundle-mac, bundle-linux, bundle-linux-aarch64]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
|
||||
2
.github/workflows/deploy_collab.yml
vendored
2
.github/workflows/deploy_collab.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
- name: Install cargo nextest
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
cargo install cargo-nextest --locked
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Limit target directory size
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
check_formatting:
|
||||
|
||||
17
.mailmap
17
.mailmap
@@ -9,8 +9,6 @@
|
||||
# Keep these entries sorted alphabetically.
|
||||
# In Zed: `editor: sort lines case insensitive`
|
||||
|
||||
Agus Zubiaga <agus@zed.dev>
|
||||
Agus Zubiaga <agus@zed.dev> <hi@aguz.me>
|
||||
Alex Viscreanu <alexviscreanu@gmail.com>
|
||||
Alex Viscreanu <alexviscreanu@gmail.com> <alexandru.viscreanu@kiwi.com>
|
||||
Alexander Mankuta <alex@pointless.one>
|
||||
@@ -26,7 +24,6 @@ Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com>
|
||||
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
|
||||
Boris Cherny <boris@anthropic.com>
|
||||
Boris Cherny <boris@anthropic.com> <boris@performancejs.com>
|
||||
Brian Tan <brian.tan88@gmail.com>
|
||||
Chris Hayes <chris+git@hayes.software>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
||||
@@ -35,16 +32,11 @@ Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||
Dairon Medina <dairon.medina@gmail.com>
|
||||
Danilo Leal <danilo@zed.dev>
|
||||
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
|
||||
Edwin Aronsson <75266237+4teapo@users.noreply.github.com>
|
||||
Evren Sen <nervenes@icloud.com>
|
||||
Evren Sen <nervenes@icloud.com> <146845123+evrensen467@users.noreply.github.com>
|
||||
Evren Sen <nervenes@icloud.com> <146845123+evrsen@users.noreply.github.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
||||
Finn Evers <dev@bahn.sh>
|
||||
Finn Evers <dev@bahn.sh> <75036051+MrSubidubi@users.noreply.github.com>
|
||||
Finn Evers <dev@bahn.sh> <finn.evers@outlook.de>
|
||||
Gowtham K <73059450+dovakin0007@users.noreply.github.com>
|
||||
Greg Morenz <greg-morenz@droid.cafe>
|
||||
Greg Morenz <greg-morenz@droid.cafe> <morenzg@gmail.com>
|
||||
Ihnat Aŭtuška <autushka.ihnat@gmail.com>
|
||||
@@ -62,14 +54,11 @@ Kirill Bulatov <kirill@zed.dev>
|
||||
Kirill Bulatov <kirill@zed.dev> <mail4score@gmail.com>
|
||||
Kyle Caverly <kylebcaverly@gmail.com>
|
||||
Kyle Caverly <kylebcaverly@gmail.com> <kyle@zed.dev>
|
||||
Lilith Iris <itslirissama@gmail.com>
|
||||
Lilith Iris <itslirissama@gmail.com> <83819417+Irilith@users.noreply.github.com>
|
||||
LoganDark <contact@logandark.mozmail.com>
|
||||
LoganDark <contact@logandark.mozmail.com> <git@logandark.mozmail.com>
|
||||
LoganDark <contact@logandark.mozmail.com> <github@logandark.mozmail.com>
|
||||
Marshall Bowers <git@maxdeviant.com>
|
||||
Marshall Bowers <git@maxdeviant.com> <elliott.codes@gmail.com>
|
||||
Marshall Bowers <git@maxdeviant.com> <marshall@zed.dev>
|
||||
Marshall Bowers <elliott.codes@gmail.com>
|
||||
Marshall Bowers <elliott.codes@gmail.com> <marshall@zed.dev>
|
||||
Matt Fellenz <matt@felle.nz>
|
||||
Matt Fellenz <matt@felle.nz> <matt+github@felle.nz>
|
||||
Max Brunsfeld <maxbrunsfeld@gmail.com>
|
||||
@@ -123,7 +112,5 @@ Uladzislau Kaminski <i@uladkaminski.com>
|
||||
Uladzislau Kaminski <i@uladkaminski.com> <uladzislau_kaminski@epam.com>
|
||||
Vitaly Slobodin <vitaliy.slobodin@gmail.com>
|
||||
Vitaly Slobodin <vitaliy.slobodin@gmail.com> <vitaly_slobodin@fastmail.com>
|
||||
Will Bradley <williambbradley@gmail.com>
|
||||
Will Bradley <williambbradley@gmail.com> <will@zed.dev>
|
||||
WindSoilder <WindSoilder@outlook.com>
|
||||
张小白 <364772080@qq.com>
|
||||
|
||||
955
Cargo.lock
generated
955
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
48
Cargo.toml
48
Cargo.toml
@@ -2,15 +2,11 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/zed_predict_tos",
|
||||
"crates/anthropic",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/assistant2",
|
||||
"crates/assistant_context_editor",
|
||||
"crates/assistant_settings",
|
||||
"crates/assistant_slash_command",
|
||||
"crates/assistant_slash_commands",
|
||||
"crates/assistant_tool",
|
||||
"crates/assistant_tools",
|
||||
"crates/audio",
|
||||
@@ -44,7 +40,6 @@ members = [
|
||||
"crates/feedback",
|
||||
"crates/file_finder",
|
||||
"crates/file_icons",
|
||||
"crates/fireworks",
|
||||
"crates/fs",
|
||||
"crates/fsevent",
|
||||
"crates/fuzzy",
|
||||
@@ -73,7 +68,6 @@ members = [
|
||||
"crates/livekit_client",
|
||||
"crates/livekit_client_macos",
|
||||
"crates/livekit_server",
|
||||
"crates/lmstudio",
|
||||
"crates/lsp",
|
||||
"crates/markdown",
|
||||
"crates/markdown_preview",
|
||||
@@ -92,7 +86,6 @@ members = [
|
||||
"crates/project",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/prompt_library",
|
||||
"crates/proto",
|
||||
"crates/recent_projects",
|
||||
"crates/refineable",
|
||||
@@ -118,7 +111,6 @@ members = [
|
||||
"crates/sqlez_macros",
|
||||
"crates/story",
|
||||
"crates/storybook",
|
||||
"crates/streaming_diff",
|
||||
"crates/sum_tree",
|
||||
"crates/supermaven",
|
||||
"crates/supermaven_api",
|
||||
@@ -188,10 +180,6 @@ members = [
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
|
||||
[workspace.package]
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
#
|
||||
@@ -200,15 +188,11 @@ edition = "2021"
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
ai = { path = "crates/ai" }
|
||||
zed_predict_tos = { path = "crates/zed_predict_tos" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
assistant2 = { path = "crates/assistant2" }
|
||||
assistant_context_editor = { path = "crates/assistant_context_editor" }
|
||||
assistant_settings = { path = "crates/assistant_settings" }
|
||||
assistant_slash_command = { path = "crates/assistant_slash_command" }
|
||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||
assistant_tool = { path = "crates/assistant_tool" }
|
||||
assistant_tools = { path = "crates/assistant_tools" }
|
||||
audio = { path = "crates/audio" }
|
||||
@@ -238,7 +222,6 @@ feature_flags = { path = "crates/feature_flags" }
|
||||
feedback = { path = "crates/feedback" }
|
||||
file_finder = { path = "crates/file_finder" }
|
||||
file_icons = { path = "crates/file_icons" }
|
||||
fireworks = { path = "crates/fireworks" }
|
||||
fs = { path = "crates/fs" }
|
||||
fsevent = { path = "crates/fsevent" }
|
||||
fuzzy = { path = "crates/fuzzy" }
|
||||
@@ -270,7 +253,6 @@ languages = { path = "crates/languages" }
|
||||
livekit_client = { path = "crates/livekit_client" }
|
||||
livekit_client_macos = { path = "crates/livekit_client_macos" }
|
||||
livekit_server = { path = "crates/livekit_server" }
|
||||
lmstudio = { path = "crates/lmstudio" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
@@ -291,7 +273,6 @@ prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
prompt_library = { path = "crates/prompt_library" }
|
||||
proto = { path = "crates/proto" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
refineable = { path = "crates/refineable" }
|
||||
@@ -316,7 +297,6 @@ sqlez = { path = "crates/sqlez" }
|
||||
sqlez_macros = { path = "crates/sqlez_macros" }
|
||||
story = { path = "crates/story" }
|
||||
storybook = { path = "crates/storybook" }
|
||||
streaming_diff = { path = "crates/streaming_diff" }
|
||||
sum_tree = { path = "crates/sum_tree" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
@@ -354,8 +334,7 @@ zeta = { path = "crates/zeta" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
# TODO(#18342): Update to version 0.25 from crates.io when it is released.
|
||||
alacritty_terminal = { git = "https://github.com/alacritty/alacritty.git", rev = "5e78d20c709cb1ab8d44ca7a8702cc26d779227c" }
|
||||
alacritty_terminal = "0.24"
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
@@ -363,7 +342,7 @@ ashpd = { version = "0.10", default-features = false, features = ["async-std"]}
|
||||
async-compat = "0.2.1"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = "0.1"
|
||||
async-fs = "2.1"
|
||||
async-fs = "1.6"
|
||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.5.0"
|
||||
@@ -372,7 +351,7 @@ async-tungstenite = "0.28"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.8.0"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||
@@ -412,21 +391,20 @@ ignore = "0.4.22"
|
||||
image = "0.25.1"
|
||||
indexmap = { version = "2.7.0", features = ["serde"] }
|
||||
indoc = "2"
|
||||
itertools = "0.14.0"
|
||||
itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.6.0" }
|
||||
jupyter-websocket-client = { version = "0.9.0" }
|
||||
jupyter-protocol = { version = "0.5.0" }
|
||||
jupyter-websocket-client = { version = "0.8.0" }
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
|
||||
log = { version = "0.4.25", features = ["kv_unstable_serde", "serde"] }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nbformat = { version = "0.10.0" }
|
||||
nbformat = { version = "0.9.0" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.20"
|
||||
ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
@@ -458,7 +436,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.25.0", default-features = false, features = [
|
||||
runtimelib = { version = "0.24.0", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
@@ -483,14 +461,13 @@ signal-hook = "0.3.17"
|
||||
similar = "1.3"
|
||||
simplelog = "0.12.2"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "2.0"
|
||||
smol = "1.2"
|
||||
sqlformat = "0.2"
|
||||
strsim = "0.11"
|
||||
strum = { version = "0.26.0", features = ["derive"] }
|
||||
subtle = "2.5.0"
|
||||
sys-locale = "0.3.1"
|
||||
sysinfo = "0.31.0"
|
||||
take-until = "0.2.0"
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.6.0"
|
||||
@@ -509,11 +486,11 @@ tree-sitter = { version = "0.23", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.23"
|
||||
tree-sitter-c = "0.23"
|
||||
tree-sitter-cpp = "0.23"
|
||||
tree-sitter-css = "0.23.2"
|
||||
tree-sitter-css = "0.23"
|
||||
tree-sitter-elixir = "0.3"
|
||||
tree-sitter-embedded-template = "0.23.0"
|
||||
tree-sitter-go = "0.23"
|
||||
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
|
||||
tree-sitter-go-mod = { git = "https://github.com/zed-industries/tree-sitter-go-mod", rev = "a9aea5e358cde4d0f8ff20b7bc4fa311e359c7ca", package = "tree-sitter-gomod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
|
||||
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
|
||||
tree-sitter-diff = "0.1.0"
|
||||
@@ -635,7 +612,6 @@ image_viewer = { codegen-units = 1 }
|
||||
inline_completion_button = { codegen-units = 1 }
|
||||
install_cli = { codegen-units = 1 }
|
||||
journal = { codegen-units = 1 }
|
||||
lmstudio = { codegen-units = 1 }
|
||||
menu = { codegen-units = 1 }
|
||||
notifications = { codegen-units = 1 }
|
||||
ollama = { codegen-units = 1 }
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Artboard</title>
|
||||
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Rectangle" stroke="black" stroke-width="1.26" x="1.22" y="1.22" width="13.56" height="13.56" rx="2.66"></rect>
|
||||
<g id="Group-7" transform="translate(2.44, 3.03)" fill="black">
|
||||
<g id="Group" transform="translate(0.37, 0)">
|
||||
<rect id="Rectangle" opacity="0.487118676" x="1.9" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
<rect id="Rectangle" opacity="0.845098586" x="0" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
</g>
|
||||
<g id="Group-2" transform="translate(2.88, 1.7)">
|
||||
<rect id="Rectangle" opacity="0.487118676" x="1.9" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
<rect id="Rectangle" opacity="0.845098586" x="0" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
</g>
|
||||
<g id="Group-3" transform="translate(1.53, 3.38)">
|
||||
<rect id="Rectangle" opacity="0.487118676" x="1.92" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
<rect id="Rectangle" opacity="0.845098586" x="0" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
</g>
|
||||
<g id="Group-4" transform="translate(0, 5.09)">
|
||||
<rect id="Rectangle" opacity="0.487118676" x="1.9" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
<rect id="Rectangle" opacity="0.845098586" x="0" y="0" width="6.28" height="1.43" rx="0.71"></rect>
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(1.64, 6.77)">
|
||||
<rect id="Rectangle" opacity="0.487118676" x="1.94" y="0" width="5.46" height="1.43" rx="0.71"></rect>
|
||||
<rect id="Rectangle" opacity="0.845098586" x="0" y="0" width="5.46" height="1.43" rx="0.71"></rect>
|
||||
</g>
|
||||
<g id="Group-6" transform="translate(4.24, 8.47)">
|
||||
<rect id="Rectangle" opacity="0.487118676" x="2.11" y="0" width="4.56" height="1.43" rx="0.71"></rect>
|
||||
<rect id="Rectangle" opacity="0.845098586" x="0" y="0" width="4.56" height="1.43" rx="0.71"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,4 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.4286 9H10.5714C9.70355 9 9 9.70355 9 10.5714V18.4286C9 19.2964 9.70355 20 10.5714 20H18.4286C19.2964 20 20 19.2964 20 18.4286V10.5714C20 9.70355 19.2964 9 18.4286 9Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.57143 15C4.70714 15 4 14.2929 4 13.4286V5.57143C4 4.70714 4.70714 4 5.57143 4H13.4286C14.2929 4 15 4.70714 15 5.57143" stroke="black" stroke-width="2" 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-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 338 B |
@@ -210,5 +210,208 @@
|
||||
"zsh_profile": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zshrc": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
"bun": {
|
||||
"icon": "icons/file_icons/bun.svg"
|
||||
},
|
||||
"c": {
|
||||
"icon": "icons/file_icons/c.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"coffeescript": {
|
||||
"icon": "icons/file_icons/coffeescript.svg"
|
||||
},
|
||||
"collapsed_chevron": {
|
||||
"icon": "icons/file_icons/chevron_right.svg"
|
||||
},
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"cpp": {
|
||||
"icon": "icons/file_icons/cpp.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"dart": {
|
||||
"icon": "icons/file_icons/dart.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"diff": {
|
||||
"icon": "icons/file_icons/diff.svg"
|
||||
},
|
||||
"docker": {
|
||||
"icon": "icons/file_icons/docker.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"elixir": {
|
||||
"icon": "icons/file_icons/elixir.svg"
|
||||
},
|
||||
"elm": {
|
||||
"icon": "icons/file_icons/elm.svg"
|
||||
},
|
||||
"erlang": {
|
||||
"icon": "icons/file_icons/erlang.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_chevron": {
|
||||
"icon": "icons/file_icons/chevron_down.svg"
|
||||
},
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"font": {
|
||||
"icon": "icons/file_icons/font.svg"
|
||||
},
|
||||
"fsharp": {
|
||||
"icon": "icons/file_icons/fsharp.svg"
|
||||
},
|
||||
"gleam": {
|
||||
"icon": "icons/file_icons/gleam.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"graphql": {
|
||||
"icon": "icons/file_icons/graphql.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"hcl": {
|
||||
"icon": "icons/file_icons/hcl.svg"
|
||||
},
|
||||
"heroku": {
|
||||
"icon": "icons/file_icons/heroku.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"java": {
|
||||
"icon": "icons/file_icons/java.svg"
|
||||
},
|
||||
"javascript": {
|
||||
"icon": "icons/file_icons/javascript.svg"
|
||||
},
|
||||
"julia": {
|
||||
"icon": "icons/file_icons/julia.svg"
|
||||
},
|
||||
"kotlin": {
|
||||
"icon": "icons/file_icons/kotlin.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"lua": {
|
||||
"icon": "icons/file_icons/lua.svg"
|
||||
},
|
||||
"metal": {
|
||||
"icon": "icons/file_icons/metal.svg"
|
||||
},
|
||||
"nim": {
|
||||
"icon": "icons/file_icons/nim.svg"
|
||||
},
|
||||
"nix": {
|
||||
"icon": "icons/file_icons/nix.svg"
|
||||
},
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
"php": {
|
||||
"icon": "icons/file_icons/php.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"prisma": {
|
||||
"icon": "icons/file_icons/prisma.svg"
|
||||
},
|
||||
"python": {
|
||||
"icon": "icons/file_icons/python.svg"
|
||||
},
|
||||
"r": {
|
||||
"icon": "icons/file_icons/r.svg"
|
||||
},
|
||||
"react": {
|
||||
"icon": "icons/file_icons/react.svg"
|
||||
},
|
||||
"roc": {
|
||||
"icon": "icons/file_icons/roc.svg"
|
||||
},
|
||||
"ruby": {
|
||||
"icon": "icons/file_icons/ruby.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"sass": {
|
||||
"icon": "icons/file_icons/sass.svg"
|
||||
},
|
||||
"scala": {
|
||||
"icon": "icons/file_icons/scala.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"swift": {
|
||||
"icon": "icons/file_icons/swift.svg"
|
||||
},
|
||||
"tcl": {
|
||||
"icon": "icons/file_icons/tcl.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"terraform": {
|
||||
"icon": "icons/file_icons/terraform.svg"
|
||||
},
|
||||
"toml": {
|
||||
"icon": "icons/file_icons/toml.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"v": {
|
||||
"icon": "icons/file_icons/v.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
},
|
||||
"vue": {
|
||||
"icon": "icons/file_icons/vue.svg"
|
||||
},
|
||||
"zig": {
|
||||
"icon": "icons/file_icons/zig.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<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-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg>
|
||||
|
Before Width: | Height: | Size: 345 B |
@@ -2,27 +2,26 @@
|
||||
// Standard Linux bindings
|
||||
{
|
||||
"bindings": {
|
||||
"home": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"end": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectLast",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"tab": "menu::SelectNext",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"shift-tab": "menu::SelectPrev",
|
||||
"home": "menu::SelectFirst",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"tab": "menu::SelectNext",
|
||||
"end": "menu::SelectLast",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"alt-shift-enter": "menu::Restart",
|
||||
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
|
||||
"ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
|
||||
"ctrl-shift-w": "workspace::CloseWindow",
|
||||
"shift-escape": "workspace::ToggleZoom",
|
||||
"open": "workspace::Open",
|
||||
"ctrl-o": "workspace::Open",
|
||||
"ctrl-=": "zed::IncreaseBufferFontSize",
|
||||
"ctrl-+": "zed::IncreaseBufferFontSize",
|
||||
@@ -30,9 +29,7 @@
|
||||
"ctrl-0": "zed::ResetBufferFontSize",
|
||||
"ctrl-,": "zed::OpenSettings",
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-alt-z": "zeta::RateCompletions",
|
||||
"ctrl-shift-i": "inline_completion::ToggleMenu"
|
||||
"f11": "zed::ToggleFullScreen"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -53,8 +50,8 @@
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"shift-backspace": "editor::Backspace",
|
||||
"backspace": "editor::Backspace",
|
||||
"shift-backspace": "editor::Backspace",
|
||||
"delete": "editor::Delete",
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::TabPrev",
|
||||
@@ -64,19 +61,11 @@
|
||||
"ctrl-k q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"cut": "editor::Cut",
|
||||
"shift-delete": "editor::Cut",
|
||||
"ctrl-x": "editor::Cut",
|
||||
"copy": "editor::Copy",
|
||||
"ctrl-insert": "editor::Copy",
|
||||
"ctrl-c": "editor::Copy",
|
||||
"paste": "editor::Paste",
|
||||
"shift-insert": "editor::Paste",
|
||||
"ctrl-v": "editor::Paste",
|
||||
"undo": "editor::Undo",
|
||||
"ctrl-z": "editor::Undo",
|
||||
"redo": "editor::Redo",
|
||||
"ctrl-y": "editor::Redo",
|
||||
"ctrl-z": "editor::Undo",
|
||||
"ctrl-shift-z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::LineUp",
|
||||
@@ -108,11 +97,11 @@
|
||||
"ctrl-l": "editor::SelectLine",
|
||||
"ctrl-shift-i": "editor::Format",
|
||||
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
|
||||
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
||||
"ctrl-;": "editor::ToggleLineNumbers",
|
||||
@@ -125,16 +114,24 @@
|
||||
"shift-f10": "editor::OpenContextMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Separate block with same context so these display in context menus
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-x": "editor::Cut",
|
||||
"ctrl-c": "editor::Copy",
|
||||
"ctrl-v": "editor::Paste"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"shift-enter": "editor::Newline",
|
||||
"enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
"ctrl-enter": "editor::NewlineAbove",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow",
|
||||
"ctrl-k ctrl-z": "editor::ToggleSoftWrap",
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
@@ -168,7 +165,6 @@
|
||||
{
|
||||
"context": "Markdown",
|
||||
"bindings": {
|
||||
"copy": "markdown::Copy",
|
||||
"ctrl-c": "markdown::Copy"
|
||||
}
|
||||
},
|
||||
@@ -179,17 +175,15 @@
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-g": "search::SelectNextMatch",
|
||||
"ctrl-shift-g": "search::SelectPrevMatch",
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-shift-m": "assistant::ToggleModelSelector",
|
||||
"ctrl-k h": "assistant::DeployHistory",
|
||||
"ctrl-k l": "assistant::DeployPromptLibrary",
|
||||
"new": "assistant::NewContext",
|
||||
"ctrl-n": "assistant::NewContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"bindings": {
|
||||
"new": "prompt_library::NewPrompt",
|
||||
"ctrl-n": "prompt_library::NewPrompt",
|
||||
"ctrl-shift-s": "prompt_library::ToggleDefaultPrompt"
|
||||
}
|
||||
@@ -203,7 +197,6 @@
|
||||
"shift-enter": "search::SelectPrevMatch",
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"ctrl-f": "search::FocusSearch",
|
||||
"find": "search::FocusSearch",
|
||||
"ctrl-h": "search::ToggleReplace",
|
||||
"ctrl-l": "search::ToggleSelection"
|
||||
}
|
||||
@@ -226,7 +219,6 @@
|
||||
"context": "ProjectSearchBar",
|
||||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"shift-find": "search::FocusSearch",
|
||||
"ctrl-shift-f": "search::FocusSearch",
|
||||
"ctrl-shift-h": "search::ToggleReplace",
|
||||
"alt-ctrl-g": "search::ToggleRegex",
|
||||
@@ -259,48 +251,32 @@
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"alt-1": ["pane::ActivateItem", 0],
|
||||
"alt-2": ["pane::ActivateItem", 1],
|
||||
"alt-3": ["pane::ActivateItem", 2],
|
||||
"alt-4": ["pane::ActivateItem", 3],
|
||||
"alt-5": ["pane::ActivateItem", 4],
|
||||
"alt-6": ["pane::ActivateItem", 5],
|
||||
"alt-7": ["pane::ActivateItem", 6],
|
||||
"alt-8": ["pane::ActivateItem", 7],
|
||||
"alt-9": ["pane::ActivateItem", 8],
|
||||
"alt-0": "pane::ActivateLastItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"back": "pane::GoBack",
|
||||
"forward": "pane::GoForward",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
|
||||
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
|
||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"back": "pane::GoBack",
|
||||
"ctrl-alt--": "pane::GoBack",
|
||||
"ctrl-alt-_": "pane::GoForward",
|
||||
"forward": "pane::GoForward",
|
||||
"ctrl-shift-f": "pane::DeploySearch",
|
||||
"ctrl-alt-g": "search::SelectNextMatch",
|
||||
"f3": "search::SelectNextMatch",
|
||||
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
||||
"shift-f3": "search::SelectPrevMatch",
|
||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||
"shift-find": "project_search::ToggleFocus",
|
||||
"ctrl-alt-shift-h": "search::ToggleReplace",
|
||||
"ctrl-alt-shift-l": "search::ToggleSelection",
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"alt-c": "search::ToggleCaseSensitive",
|
||||
"alt-w": "search::ToggleWholeWord",
|
||||
"alt-r": "search::ToggleRegex",
|
||||
"alt-ctrl-f": "project_search::ToggleFilters",
|
||||
"alt-find": "project_search::ToggleFilters",
|
||||
"ctrl-alt-shift-r": "search::ToggleRegex",
|
||||
"ctrl-alt-shift-x": "search::ToggleRegex",
|
||||
"alt-r": "search::ToggleRegex",
|
||||
"ctrl-k shift-enter": "pane::TogglePinTab"
|
||||
}
|
||||
},
|
||||
@@ -375,25 +351,40 @@
|
||||
"ctrl-g": "go_to_line::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"alt-1": ["pane::ActivateItem", 0],
|
||||
"alt-2": ["pane::ActivateItem", 1],
|
||||
"alt-3": ["pane::ActivateItem", 2],
|
||||
"alt-4": ["pane::ActivateItem", 3],
|
||||
"alt-5": ["pane::ActivateItem", 4],
|
||||
"alt-6": ["pane::ActivateItem", 5],
|
||||
"alt-7": ["pane::ActivateItem", 6],
|
||||
"alt-8": ["pane::ActivateItem", 7],
|
||||
"alt-9": ["pane::ActivateItem", 8],
|
||||
"alt-0": "pane::ActivateLastItem",
|
||||
"ctrl-alt--": "pane::GoBack",
|
||||
"ctrl-alt-_": "pane::GoForward",
|
||||
"ctrl-shift-t": "pane::ReopenClosedItem",
|
||||
"f3": "search::SelectNextMatch",
|
||||
"shift-f3": "search::SelectPrevMatch",
|
||||
"ctrl-shift-f": "project_search::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
// Change the default action on `menu::Confirm` by setting the parameter
|
||||
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
|
||||
"alt-open": "projects::OpenRecent",
|
||||
"alt-ctrl-o": "projects::OpenRecent",
|
||||
"alt-shift-open": "projects::OpenRemote",
|
||||
"alt-ctrl-shift-o": "projects::OpenRemote",
|
||||
"alt-ctrl-shift-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"save": "workspace::Save",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"ctrl-k s": "workspace::SaveWithoutFormat",
|
||||
"shift-save": "workspace::SaveAs",
|
||||
"ctrl-shift-s": "workspace::SaveAs",
|
||||
"new": "workspace::NewFile",
|
||||
"ctrl-n": "workspace::NewFile",
|
||||
"shift-new": "workspace::NewWindow",
|
||||
"ctrl-shift-n": "workspace::NewWindow",
|
||||
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||
"alt-1": ["workspace::ActivatePane", 0],
|
||||
@@ -409,10 +400,8 @@
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-y": "workspace::CloseAllDocks",
|
||||
"shift-find": "pane::DeploySearch",
|
||||
"ctrl-shift-f": "pane::DeploySearch",
|
||||
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
"ctrl-shift-t": "pane::ReopenClosedItem",
|
||||
"ctrl-k ctrl-s": "zed::OpenKeymap",
|
||||
"ctrl-k ctrl-t": "theme_selector::Toggle",
|
||||
"ctrl-t": "project_symbols::Toggle",
|
||||
@@ -420,13 +409,12 @@
|
||||
"ctrl-tab": "tab_switcher::Toggle",
|
||||
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
||||
"ctrl-e": "file_finder::Toggle",
|
||||
"f1": "command_palette::Toggle",
|
||||
"ctrl-shift-p": "command_palette::Toggle",
|
||||
"f1": "command_palette::Toggle",
|
||||
"ctrl-shift-m": "diagnostics::Deploy",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||
"ctrl-?": "assistant::ToggleFocus",
|
||||
"alt-save": "workspace::SaveAll",
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
"ctrl-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
@@ -459,6 +447,7 @@
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-shift-k": "editor::DeleteLine",
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-shift-j": "editor::JoinLines",
|
||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||
@@ -516,10 +505,10 @@
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrev",
|
||||
"up": "editor::ContextMenuPrev",
|
||||
"ctrl-n": "editor::ContextMenuNext",
|
||||
"ctrl-p": "editor::ContextMenuPrev",
|
||||
"down": "editor::ContextMenuNext",
|
||||
"ctrl-n": "editor::ContextMenuNext",
|
||||
"pageup": "editor::ContextMenuFirst",
|
||||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
@@ -570,7 +559,6 @@
|
||||
"ctrl-enter": "assistant::Assist",
|
||||
"ctrl-shift-enter": "assistant::Edit",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"save": "workspace::Save",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"shift-enter": "assistant::Split",
|
||||
@@ -579,49 +567,11 @@
|
||||
"alt-enter": "editor::Newline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel2",
|
||||
"bindings": {
|
||||
"ctrl-n": "assistant2::NewThread",
|
||||
"new": "assistant2::NewThread",
|
||||
"ctrl-shift-h": "assistant2::OpenHistory",
|
||||
"ctrl-alt-/": "assistant2::ToggleModelSelector",
|
||||
"ctrl-shift-a": "assistant2::ToggleContextPicker",
|
||||
"ctrl-e": "assistant2::ChatMode",
|
||||
"ctrl-alt-e": "assistant2::RemoveAllContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "assistant2::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextStrip",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "assistant2::FocusUp",
|
||||
"right": "assistant2::FocusRight",
|
||||
"left": "assistant2::FocusLeft",
|
||||
"down": "assistant2::FocusDown",
|
||||
"backspace": "assistant2::RemoveFocusedContext",
|
||||
"enter": "assistant2::AcceptSuggestedContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ThreadHistory",
|
||||
"bindings": {
|
||||
"backspace": "assistant2::RemoveSelectedThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptEditor",
|
||||
"bindings": {
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist",
|
||||
"ctrl-alt-e": "assistant2::RemoveAllContext"
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -636,9 +586,7 @@
|
||||
"escape": "menu::Cancel",
|
||||
"left": "outline_panel::CollapseSelectedEntry",
|
||||
"right": "outline_panel::ExpandSelectedEntry",
|
||||
"alt-copy": "outline_panel::CopyPath",
|
||||
"ctrl-alt-c": "outline_panel::CopyPath",
|
||||
"alt-shift-copy": "outline_panel::CopyRelativePath",
|
||||
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
||||
"space": "outline_panel::Open",
|
||||
@@ -653,38 +601,36 @@
|
||||
"bindings": {
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
"new": "project_panel::NewFile",
|
||||
"ctrl-n": "project_panel::NewFile",
|
||||
"alt-new": "project_panel::NewDirectory",
|
||||
"alt-ctrl-n": "project_panel::NewDirectory",
|
||||
"cut": "project_panel::Cut",
|
||||
"ctrl-x": "project_panel::Cut",
|
||||
"copy": "project_panel::Copy",
|
||||
"ctrl-insert": "project_panel::Copy",
|
||||
"ctrl-c": "project_panel::Copy",
|
||||
"paste": "project_panel::Paste",
|
||||
"shift-insert": "project_panel::Paste",
|
||||
"ctrl-v": "project_panel::Paste",
|
||||
"alt-copy": "project_panel::CopyPath",
|
||||
"ctrl-alt-c": "project_panel::CopyPath",
|
||||
"alt-shift-copy": "project_panel::CopyRelativePath",
|
||||
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
|
||||
"enter": "project_panel::Rename",
|
||||
"f2": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"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",
|
||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-find": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Separate block with same context so these display in context menus
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"f2": "project_panel::Rename",
|
||||
"ctrl-c": "project_panel::Copy",
|
||||
"ctrl-x": "project_panel::Cut",
|
||||
"ctrl-v": "project_panel::Paste",
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"bindings": {
|
||||
@@ -751,9 +697,9 @@
|
||||
{
|
||||
"context": "TabSwitcher",
|
||||
"bindings": {
|
||||
"ctrl-shift-tab": "menu::SelectPrev",
|
||||
"ctrl-up": "menu::SelectPrev",
|
||||
"ctrl-down": "menu::SelectNext",
|
||||
"ctrl-shift-tab": "menu::SelectPrev",
|
||||
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
|
||||
}
|
||||
},
|
||||
@@ -761,17 +707,12 @@
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
"ctrl-alt-space": "terminal::ShowCharacterPalette",
|
||||
"copy": "terminal::Copy",
|
||||
"ctrl-insert": "terminal::Copy",
|
||||
"ctrl-shift-c": "terminal::Copy",
|
||||
"paste": "terminal::Paste",
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
// Overrides for conflicting keybindings
|
||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||
"ctrl-shift-a": "editor::SelectAll",
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-shift-f": "buffer_search::Deploy",
|
||||
"ctrl-shift-l": "terminal::Clear",
|
||||
"ctrl-shift-w": "pane::CloseActiveItem",
|
||||
@@ -791,5 +732,13 @@
|
||||
"shift-end": "terminal::ScrollToBottom",
|
||||
"ctrl-shift-space": "terminal::ToggleViMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Separate block with same context so these display in context menus
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
"ctrl-shift-c": "terminal::Copy",
|
||||
"ctrl-shift-v": "terminal::Paste"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
{
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"home": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"end": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectLast",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"tab": "menu::SelectNext",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"down": "menu::SelectNext",
|
||||
"shift-tab": "menu::SelectPrev",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"up": "menu::SelectPrev",
|
||||
"shift-tab": "menu::SelectPrev",
|
||||
"home": "menu::SelectFirst",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"tab": "menu::SelectNext",
|
||||
"end": "menu::SelectLast",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"cmd-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"cmd-escape": "menu::Cancel",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"alt-shift-enter": "menu::Restart",
|
||||
"cmd-shift-w": "workspace::CloseWindow",
|
||||
"shift-escape": "workspace::ToggleZoom",
|
||||
@@ -37,10 +37,7 @@
|
||||
"cmd-h": "zed::Hide",
|
||||
"alt-cmd-h": "zed::HideOthers",
|
||||
"cmd-m": "zed::Minimize",
|
||||
"fn-f": "zed::ToggleFullScreen",
|
||||
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
||||
"ctrl-shift-z": "zeta::RateCompletions",
|
||||
"ctrl-shift-i": "inline_completion::ToggleMenu"
|
||||
"ctrl-cmd-f": "zed::ToggleFullScreen"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -48,18 +45,18 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"backspace": "editor::Backspace",
|
||||
"shift-backspace": "editor::Backspace",
|
||||
"ctrl-h": "editor::Backspace",
|
||||
"backspace": "editor::Backspace",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"delete": "editor::Delete",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-t": "editor::Transpose",
|
||||
"ctrl-k": "editor::KillRingCut",
|
||||
"ctrl-y": "editor::KillRingYank",
|
||||
"cmd-k cmd-q": "editor::Rewrap",
|
||||
"cmd-k q": "editor::Rewrap",
|
||||
"cmd-k cmd-q": "editor::Rewrap",
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
@@ -70,33 +67,34 @@
|
||||
"cmd-v": "editor::Paste",
|
||||
"cmd-z": "editor::Undo",
|
||||
"cmd-shift-z": "editor::Redo",
|
||||
"ctrl-shift-z": "zeta::RateCompletions",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"cmd-pageup": "editor::PageUp",
|
||||
"ctrl-pageup": "editor::LineUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"cmd-pagedown": "editor::PageDown",
|
||||
"ctrl-pagedown": "editor::LineDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"left": "editor::MoveLeft",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-l": "editor::ScrollCursorCenter",
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
"cmd-left": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"cmd-right": "editor::MoveToEndOfLine",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"cmd-up": "editor::MoveToBeginning",
|
||||
"cmd-down": "editor::MoveToEnd",
|
||||
"shift-up": "editor::SelectUp",
|
||||
@@ -139,8 +137,8 @@
|
||||
"context": "Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"shift-enter": "editor::Newline",
|
||||
"enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
"cmd-enter": "editor::NewlineBelow",
|
||||
"cmd-shift-enter": "editor::NewlineAbove",
|
||||
"cmd-k z": "editor::ToggleSoftWrap",
|
||||
@@ -159,7 +157,7 @@
|
||||
"bindings": {
|
||||
"alt-tab": "editor::NextInlineCompletion",
|
||||
"alt-shift-tab": "editor::PreviousInlineCompletion",
|
||||
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
|
||||
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -201,7 +199,7 @@
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-shift-m": "assistant::ToggleModelSelector",
|
||||
"cmd-k h": "assistant::DeployHistory",
|
||||
"cmd-k l": "assistant::DeployPromptLibrary",
|
||||
"cmd-n": "assistant::NewContext"
|
||||
@@ -227,11 +225,9 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "assistant2::NewThread",
|
||||
"cmd-alt-p": "assistant2::NewPromptEditor",
|
||||
"cmd-shift-h": "assistant2::OpenHistory",
|
||||
"cmd-alt-/": "assistant2::ToggleModelSelector",
|
||||
"cmd-shift-m": "assistant2::ToggleModelSelector",
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-e": "assistant2::ChatMode",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext"
|
||||
}
|
||||
},
|
||||
@@ -242,24 +238,6 @@
|
||||
"enter": "assistant2::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextStrip",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"up": "assistant2::FocusUp",
|
||||
"right": "assistant2::FocusRight",
|
||||
"left": "assistant2::FocusLeft",
|
||||
"down": "assistant2::FocusDown",
|
||||
"backspace": "assistant2::RemoveFocusedContext",
|
||||
"enter": "assistant2::AcceptSuggestedContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ThreadHistory",
|
||||
"bindings": {
|
||||
"backspace": "assistant2::RemoveSelectedThread"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"use_key_equivalents": true,
|
||||
@@ -342,10 +320,10 @@
|
||||
"context": "Pane",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-cmd-left": "pane::ActivatePrevItem",
|
||||
"cmd-{": "pane::ActivatePrevItem",
|
||||
"alt-cmd-right": "pane::ActivateNextItem",
|
||||
"cmd-}": "pane::ActivateNextItem",
|
||||
"alt-cmd-left": "pane::ActivatePrevItem",
|
||||
"alt-cmd-right": "pane::ActivateNextItem",
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
@@ -375,10 +353,10 @@
|
||||
"bindings": {
|
||||
"cmd-[": "editor::Outdent",
|
||||
"cmd-]": "editor::Indent",
|
||||
"cmd-ctrl-p": "editor::AddSelectionAbove", // Insert cursor above
|
||||
"cmd-alt-up": "editor::AddSelectionAbove",
|
||||
"cmd-ctrl-n": "editor::AddSelectionBelow", // Insert cursor below
|
||||
"cmd-alt-down": "editor::AddSelectionBelow",
|
||||
"cmd-alt-up": "editor::AddSelectionAbove", // Insert cursor above
|
||||
"cmd-ctrl-p": "editor::AddSelectionAbove",
|
||||
"cmd-alt-down": "editor::AddSelectionBelow", // Insert cursor below
|
||||
"cmd-ctrl-n": "editor::AddSelectionBelow",
|
||||
"cmd-shift-k": "editor::DeleteLine",
|
||||
"alt-up": "editor::MoveLineUp",
|
||||
"alt-down": "editor::MoveLineDown",
|
||||
@@ -405,8 +383,8 @@
|
||||
"shift-f12": "editor::GoToImplementation",
|
||||
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"cmd-|": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"cmd-|": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
"alt-cmd-]": "editor::UnfoldLines",
|
||||
"cmd-k cmd-l": "editor::ToggleFold",
|
||||
@@ -423,8 +401,6 @@
|
||||
"cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
|
||||
"cmd-k cmd-0": "editor::FoldAll",
|
||||
"cmd-k cmd-j": "editor::UnfoldAll",
|
||||
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
|
||||
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"cmd-.": "editor::ToggleCodeActions",
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
@@ -459,6 +435,7 @@
|
||||
"ctrl-0": "pane::ActivateLastItem",
|
||||
"ctrl--": "pane::GoBack",
|
||||
"ctrl-shift--": "pane::GoForward",
|
||||
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||
"cmd-shift-f": "pane::DeploySearch"
|
||||
}
|
||||
},
|
||||
@@ -492,7 +469,6 @@
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
"cmd-shift-f": "pane::DeploySearch",
|
||||
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
|
||||
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||
"cmd-t": "project_symbols::Toggle",
|
||||
@@ -527,7 +503,7 @@
|
||||
"cmd-alt-r": "task::Rerun",
|
||||
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
|
||||
// also possible to spawn tasks by name:
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
// "foo-bar": ["task_name::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -640,7 +616,6 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-alt-/": "assistant2::ToggleModelSelector",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext",
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
@@ -808,9 +783,9 @@
|
||||
"context": "TabSwitcher",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-tab": "menu::SelectPrev",
|
||||
"ctrl-up": "menu::SelectPrev",
|
||||
"ctrl-down": "menu::SelectNext",
|
||||
"ctrl-shift-tab": "menu::SelectPrev",
|
||||
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
|
||||
}
|
||||
},
|
||||
@@ -841,16 +816,16 @@
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"cmd-up": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"cmd-down": "terminal::ScrollPageDown",
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
"shift-down": "terminal::ScrollLineDown",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"cmd-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom",
|
||||
"cmd-end": "terminal::ScrollToBottom",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom",
|
||||
"ctrl-shift-space": "terminal::ToggleViMode",
|
||||
"ctrl-k up": "pane::SplitUp",
|
||||
"ctrl-k down": "pane::SplitDown",
|
||||
@@ -862,8 +837,7 @@
|
||||
"context": "RateCompletionModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-enter": "zeta::ThumbsUpActiveCompletion",
|
||||
"cmd-shift-backspace": "zeta::ThumbsDownActiveCompletion",
|
||||
"cmd-enter": "zeta::ThumbsUp",
|
||||
"shift-down": "zeta::NextEdit",
|
||||
"shift-up": "zeta::PreviousEdit",
|
||||
"right": "zeta::PreviewCompletion"
|
||||
@@ -877,12 +851,5 @@
|
||||
"cmd-shift-enter": "zeta::ThumbsUpActiveCompletion",
|
||||
"cmd-shift-backspace": "zeta::ThumbsDownActiveCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ZedPredictTos",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"alt-g g": "go_to_line::Toggle", // goto-line
|
||||
"alt-g alt-g": "go_to_line::Toggle", // goto-line
|
||||
"ctrl-space": "editor::SetMark", // set-mark
|
||||
"ctrl-@": "editor::SetMark", // set-mark
|
||||
"ctrl-x ctrl-x": "editor::SwapSelectionEnds", // exchange-point-and-mark
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-f": "editor::MoveRight", // forward-char
|
||||
"ctrl-b": "editor::MoveLeft", // backward-char
|
||||
"ctrl-n": "editor::MoveDown", // next-line
|
||||
@@ -26,14 +24,11 @@
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
"ctrl-t": "editor::Transpose", // transpose-chars
|
||||
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-x ctrl-;": "editor::ToggleComments",
|
||||
"alt-.": "editor::GoToDefinition", // xref-find-definitions
|
||||
@@ -60,33 +55,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && selection_mode", // region selection
|
||||
"bindings": {
|
||||
"right": "editor::SelectRight",
|
||||
"left": "editor::SelectLeft",
|
||||
"down": "editor::SelectDown",
|
||||
"up": "editor::SelectUp",
|
||||
"alt-left": "editor::SelectToPreviousWordStart",
|
||||
"alt-right": "editor::SelectToNextWordEnd",
|
||||
"pagedown": "editor::SelectPageDown",
|
||||
"pageup": "editor::SelectPageUp",
|
||||
"ctrl-f": "editor::SelectRight",
|
||||
"ctrl-b": "editor::SelectLeft",
|
||||
"ctrl-n": "editor::SelectDown",
|
||||
"ctrl-p": "editor::SelectUp",
|
||||
"home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"alt-f": "editor::SelectToNextWordEnd",
|
||||
"alt-b": "editor::SelectToPreviousSubwordStart",
|
||||
"alt-<": "editor::SelectToBeginning",
|
||||
"alt->": "editor::SelectToEnd",
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"context": "Workspace && !Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
|
||||
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
|
||||
@@ -103,18 +72,6 @@
|
||||
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using emacs in the Zed terminal.
|
||||
// Unbind so Zed ignores these keys and lets emacs handle them.
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
|
||||
"ctrl-x ctrl-f": null, // find-file
|
||||
"ctrl-x ctrl-s": null, // save-buffer
|
||||
"ctrl-x ctrl-w": null, // write-file
|
||||
"ctrl-x s": null // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > Editor",
|
||||
"bindings": {
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"alt-g g": "go_to_line::Toggle", // goto-line
|
||||
"alt-g alt-g": "go_to_line::Toggle", // goto-line
|
||||
"ctrl-space": "editor::SetMark", // set-mark
|
||||
"ctrl-@": "editor::SetMark", // set-mark
|
||||
"ctrl-x ctrl-x": "editor::SwapSelectionEnds", // exchange-point-and-mark
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-f": "editor::MoveRight", // forward-char
|
||||
"ctrl-b": "editor::MoveLeft", // backward-char
|
||||
"ctrl-n": "editor::MoveDown", // next-line
|
||||
@@ -26,14 +24,11 @@
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
"ctrl-t": "editor::Transpose", // transpose-chars
|
||||
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-x ctrl-;": "editor::ToggleComments",
|
||||
"alt-.": "editor::GoToDefinition", // xref-find-definitions
|
||||
@@ -59,32 +54,6 @@
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && selection_mode", // region selection
|
||||
"bindings": {
|
||||
"right": "editor::SelectRight",
|
||||
"left": "editor::SelectLeft",
|
||||
"down": "editor::SelectDown",
|
||||
"up": "editor::SelectUp",
|
||||
"alt-left": "editor::SelectToPreviousWordStart",
|
||||
"alt-right": "editor::SelectToNextWordEnd",
|
||||
"pagedown": "editor::SelectPageDown",
|
||||
"pageup": "editor::SelectPageUp",
|
||||
"ctrl-f": "editor::SelectRight",
|
||||
"ctrl-b": "editor::SelectLeft",
|
||||
"ctrl-n": "editor::SelectDown",
|
||||
"ctrl-p": "editor::SelectUp",
|
||||
"home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"alt-f": "editor::SelectToNextWordEnd",
|
||||
"alt-b": "editor::SelectToPreviousSubwordStart",
|
||||
"alt-<": "editor::SelectToBeginning",
|
||||
"alt->": "editor::SelectToEnd",
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
|
||||
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"cmd-up": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-down": "editor::SelectSmallerSyntaxNode",
|
||||
"shift-alt-up": "editor::MoveLineUp",
|
||||
"shift-alt-down": "editor::MoveLineDown",
|
||||
"cmd-alt-l": "editor::Format",
|
||||
|
||||
@@ -2,27 +2,21 @@
|
||||
// Standard macOS bindings
|
||||
{
|
||||
"bindings": {
|
||||
"home": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"end": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectLast",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"tab": "menu::SelectNext",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"down": "menu::SelectNext",
|
||||
"shift-tab": "menu::SelectPrev",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"up": "menu::SelectPrev",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"cmd-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"cmd-q": "storybook::Quit",
|
||||
"backspace": "editor::Backspace",
|
||||
"delete": "editor::Delete",
|
||||
|
||||
@@ -4,25 +4,25 @@
|
||||
"bindings": {
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"left": "vim::Left",
|
||||
"h": "vim::Left",
|
||||
"left": "vim::Left",
|
||||
"backspace": "vim::Backspace",
|
||||
"j": "vim::Down",
|
||||
"down": "vim::Down",
|
||||
"ctrl-j": "vim::Down",
|
||||
"j": "vim::Down",
|
||||
"enter": "vim::NextLineStart",
|
||||
"ctrl-m": "vim::NextLineStart",
|
||||
"+": "vim::NextLineStart",
|
||||
"enter": "vim::NextLineStart",
|
||||
"-": "vim::PreviousLineStart",
|
||||
"shift-tab": "vim::Tab",
|
||||
"tab": "vim::Tab",
|
||||
"up": "vim::Up",
|
||||
"shift-tab": "vim::Tab",
|
||||
"k": "vim::Up",
|
||||
"right": "vim::Right",
|
||||
"up": "vim::Up",
|
||||
"l": "vim::Right",
|
||||
"right": "vim::Right",
|
||||
"space": "vim::Space",
|
||||
"end": "vim::EndOfLine",
|
||||
"$": "vim::EndOfLine",
|
||||
"end": "vim::EndOfLine",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"_": "vim::StartOfLineDownward",
|
||||
"g _": "vim::EndOfLineDownward",
|
||||
@@ -110,7 +110,7 @@
|
||||
"g y": "editor::GoToTypeDefinition",
|
||||
"g shift-i": "editor::GoToImplementation",
|
||||
"g x": "editor::OpenUrl",
|
||||
"g f": "editor::OpenSelectedFilename",
|
||||
"g f": "editor::OpenFile",
|
||||
"g n": "vim::SelectNextMatch",
|
||||
"g shift-n": "vim::SelectPreviousMatch",
|
||||
"g l": "vim::SelectNext",
|
||||
@@ -188,8 +188,8 @@
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
"bindings": {
|
||||
"ctrl-[": "editor::Cancel",
|
||||
"escape": "editor::Cancel",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
":": "command_palette::Toggle",
|
||||
".": "vim::Repeat",
|
||||
"c": ["vim::PushOperator", "Change"],
|
||||
@@ -221,13 +221,12 @@
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"=": ["vim::PushOperator", "AutoIndent"],
|
||||
"!": ["vim::PushOperator", "ShellCommand"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"g w": ["vim::PushOperator", "Rewrap"],
|
||||
"g q": ["vim::PushOperator", "Rewrap"],
|
||||
"g w": ["vim::PushOperator", "Rewrap"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
@@ -254,8 +253,8 @@
|
||||
":": "vim::VisualCommand",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
"shift-u": "vim::ConvertToUpperCase",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
"shift-d": "vim::VisualDeleteLine",
|
||||
@@ -264,10 +263,10 @@
|
||||
"shift-y": "vim::VisualYankLine",
|
||||
"p": "vim::Paste",
|
||||
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
|
||||
"c": "vim::Substitute",
|
||||
"s": "vim::Substitute",
|
||||
"shift-r": "vim::SubstituteLine",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"shift-r": "vim::SubstituteLine",
|
||||
"c": "vim::Substitute",
|
||||
"~": "vim::ChangeCase",
|
||||
"*": ["vim::MoveToNext", { "partialWord": true }],
|
||||
"#": ["vim::MoveToPrev", { "partialWord": true }],
|
||||
@@ -283,12 +282,11 @@
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"!": "vim::ShellCommand",
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"g c": "vim::ToggleComments",
|
||||
@@ -302,9 +300,9 @@
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-x": null,
|
||||
"ctrl-x ctrl-o": "editor::ShowCompletions",
|
||||
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
|
||||
@@ -352,9 +350,9 @@
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore",
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
|
||||
@@ -371,9 +369,9 @@
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
|
||||
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
|
||||
@@ -382,9 +380,9 @@
|
||||
{
|
||||
"context": "vim_mode == operator",
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"escape": "vim::ClearOperators",
|
||||
"g c": "vim::Comment"
|
||||
}
|
||||
},
|
||||
@@ -393,9 +391,6 @@
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
// Subword TextObject
|
||||
// "w": "vim::Subword",
|
||||
// "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"p": "vim::Paragraph",
|
||||
@@ -500,12 +495,6 @@
|
||||
"=": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == sh",
|
||||
"bindings": {
|
||||
"!": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"bindings": {
|
||||
@@ -571,14 +560,14 @@
|
||||
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
@@ -603,19 +592,19 @@
|
||||
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w ctrl-v": "pane::SplitVertical",
|
||||
"ctrl-w v": "pane::SplitVertical",
|
||||
"ctrl-w ctrl-v": "pane::SplitVertical",
|
||||
"ctrl-w s": "pane::SplitHorizontal",
|
||||
"ctrl-w shift-s": "pane::SplitHorizontal",
|
||||
"ctrl-w ctrl-s": "pane::SplitHorizontal",
|
||||
"ctrl-w s": "pane::SplitHorizontal",
|
||||
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
||||
"ctrl-w c": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
||||
"ctrl-w q": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal"
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,15 +13,15 @@ You must describe the change using the following XML structure:
|
||||
- <description> (optional) - An arbitrarily-long comment that describes the purpose
|
||||
of this edit.
|
||||
- <old_text> (optional) - An excerpt from the file's current contents that uniquely
|
||||
identifies a range within the file where the edit should occur. Required for all operations
|
||||
except `create`.
|
||||
identifies a range within the file where the edit should occur. If this tag is not
|
||||
specified, then the entire file will be used as the range.
|
||||
- <new_text> (required) - The new text to insert into the file.
|
||||
- <operation> (required) - The type of change that should occur at the given range
|
||||
of the file. Must be one of the following values:
|
||||
- `update`: Replaces the entire range with the new text.
|
||||
- `insert_before`: Inserts the new text before the range.
|
||||
- `insert_after`: Inserts new text after the range.
|
||||
- `create`: Creates or overwrites a file with the given path and the new text.
|
||||
- `create`: Creates a new file with the given path and the new text.
|
||||
- `delete`: Deletes the specified range from the file.
|
||||
|
||||
<guidelines>
|
||||
|
||||
@@ -372,8 +372,6 @@
|
||||
"default_width": 240,
|
||||
// Where to dock the project panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Spacing between worktree entries in the project panel. Can be 'comfortable' or 'standard'.
|
||||
"entry_spacing": "comfortable",
|
||||
// Whether to show file icons in the project panel.
|
||||
"file_icons": true,
|
||||
// Whether to show folder icons or chevrons for directories in the project panel.
|
||||
@@ -504,15 +502,9 @@
|
||||
"dock": "left",
|
||||
// Default width of the git panel.
|
||||
"default_width": 360,
|
||||
// Style of the git status indicator in the panel.
|
||||
//
|
||||
// Default: icon
|
||||
"status_style": "icon",
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the git panel.
|
||||
//
|
||||
// Default: inherits editor scrollbar settings
|
||||
"show": null
|
||||
"show": "auto"
|
||||
}
|
||||
},
|
||||
"message_editor": {
|
||||
@@ -908,23 +900,6 @@
|
||||
// The shell running in the terminal needs to be configured to emit the title.
|
||||
// Example: `echo -e "\e]2;New Title\007";`
|
||||
"breadcrumbs": true
|
||||
},
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the terminal.
|
||||
/// This setting can take four values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
"show": null
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
@@ -1001,17 +976,11 @@
|
||||
},
|
||||
"C": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false,
|
||||
"prettier": {
|
||||
"allowed": false
|
||||
}
|
||||
"use_on_type_format": false
|
||||
},
|
||||
"C++": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false,
|
||||
"prettier": {
|
||||
"allowed": false
|
||||
}
|
||||
"use_on_type_format": false
|
||||
},
|
||||
"CSS": {
|
||||
"prettier": {
|
||||
@@ -1163,9 +1132,6 @@
|
||||
"openai": {
|
||||
"version": "1",
|
||||
"api_url": "https://api.openai.com/v1"
|
||||
},
|
||||
"lmstudio": {
|
||||
"api_url": "http://localhost:1234/api/v0"
|
||||
}
|
||||
},
|
||||
// Zed's Prettier integration settings.
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#21242bff",
|
||||
"editor.active_line.background": "#21242bbf",
|
||||
"editor.highlighted_line.background": "#21242bff",
|
||||
"editor.line_number": "#565960",
|
||||
"editor.active_line_number": "#f8f8f9",
|
||||
"editor.hover_line_number": "#cbcdd0",
|
||||
"editor.line_number": "#f7f7f859",
|
||||
"editor.active_line_number": "#f7f7f8ff",
|
||||
"editor.invisible": "#64646dff",
|
||||
"editor.wrap_guide": "#f7f7f80d",
|
||||
"editor.active_wrap_guide": "#f7f7f81a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#221f26ff",
|
||||
"editor.active_line.background": "#221f26bf",
|
||||
"editor.highlighted_line.background": "#221f26ff",
|
||||
"editor.line_number": "#656369",
|
||||
"editor.active_line_number": "#d8d8da",
|
||||
"editor.hover_line_number": "#b7b5ba",
|
||||
"editor.line_number": "#efecf459",
|
||||
"editor.active_line_number": "#efecf4ff",
|
||||
"editor.invisible": "#726c7aff",
|
||||
"editor.wrap_guide": "#efecf40d",
|
||||
"editor.active_wrap_guide": "#efecf41a",
|
||||
@@ -445,9 +444,8 @@
|
||||
"editor.subheader.background": "#e6e3ebff",
|
||||
"editor.active_line.background": "#e6e3ebbf",
|
||||
"editor.highlighted_line.background": "#e6e3ebff",
|
||||
"editor.line_number": "#a4a2a8",
|
||||
"editor.active_line_number": "#323135",
|
||||
"editor.hover_line_number": "#58565c",
|
||||
"editor.line_number": "#19171c59",
|
||||
"editor.active_line_number": "#19171cff",
|
||||
"editor.invisible": "#726c7aff",
|
||||
"editor.wrap_guide": "#19171c0d",
|
||||
"editor.active_wrap_guide": "#19171c1a",
|
||||
@@ -831,9 +829,8 @@
|
||||
"editor.subheader.background": "#262622ff",
|
||||
"editor.active_line.background": "#262622bf",
|
||||
"editor.highlighted_line.background": "#262622ff",
|
||||
"editor.line_number": "#6d6c66",
|
||||
"editor.active_line_number": "#dadad7",
|
||||
"editor.hover_line_number": "#bab9b5",
|
||||
"editor.line_number": "#fefbec59",
|
||||
"editor.active_line_number": "#fefbecff",
|
||||
"editor.invisible": "#8b8773ff",
|
||||
"editor.wrap_guide": "#fefbec0d",
|
||||
"editor.active_wrap_guide": "#fefbec1a",
|
||||
@@ -1217,9 +1214,8 @@
|
||||
"editor.subheader.background": "#eeebd7ff",
|
||||
"editor.active_line.background": "#eeebd7bf",
|
||||
"editor.highlighted_line.background": "#eeebd7ff",
|
||||
"editor.line_number": "#b1afa5",
|
||||
"editor.active_line_number": "#292824",
|
||||
"editor.hover_line_number": "#44433b",
|
||||
"editor.line_number": "#20201d59",
|
||||
"editor.active_line_number": "#20201dff",
|
||||
"editor.invisible": "#8b8773ff",
|
||||
"editor.wrap_guide": "#20201d0d",
|
||||
"editor.active_wrap_guide": "#20201d1a",
|
||||
@@ -1603,9 +1599,8 @@
|
||||
"editor.subheader.background": "#2c2b23ff",
|
||||
"editor.active_line.background": "#2c2b23bf",
|
||||
"editor.highlighted_line.background": "#2c2b23ff",
|
||||
"editor.line_number": "#6b6b65",
|
||||
"editor.active_line_number": "#e6e6e5",
|
||||
"editor.hover_line_number": "#babab6",
|
||||
"editor.line_number": "#f4f3ec59",
|
||||
"editor.active_line_number": "#f4f3ecff",
|
||||
"editor.invisible": "#7a7867ff",
|
||||
"editor.wrap_guide": "#f4f3ec0d",
|
||||
"editor.active_wrap_guide": "#f4f3ec1a",
|
||||
@@ -1989,9 +1984,8 @@
|
||||
"editor.subheader.background": "#ebeae3ff",
|
||||
"editor.active_line.background": "#ebeae3bf",
|
||||
"editor.highlighted_line.background": "#ebeae3ff",
|
||||
"editor.line_number": "#abaaa4",
|
||||
"editor.active_line_number": "#282725",
|
||||
"editor.hover_line_number": "#42423d",
|
||||
"editor.line_number": "#22221b59",
|
||||
"editor.active_line_number": "#22221bff",
|
||||
"editor.invisible": "#7a7867ff",
|
||||
"editor.wrap_guide": "#22221b0d",
|
||||
"editor.active_wrap_guide": "#22221b1a",
|
||||
@@ -2375,9 +2369,8 @@
|
||||
"editor.subheader.background": "#27211eff",
|
||||
"editor.active_line.background": "#27211ebf",
|
||||
"editor.highlighted_line.background": "#27211eff",
|
||||
"editor.line_number": "#656362k",
|
||||
"editor.active_line_number": "#e6e5e5",
|
||||
"editor.hover_line_number": "#b9b7b7",
|
||||
"editor.line_number": "#f0eeed59",
|
||||
"editor.active_line_number": "#f0eeedff",
|
||||
"editor.invisible": "#89817dff",
|
||||
"editor.wrap_guide": "#f0eeed0d",
|
||||
"editor.active_wrap_guide": "#f0eeed1a",
|
||||
@@ -2761,9 +2754,8 @@
|
||||
"editor.subheader.background": "#e9e6e4ff",
|
||||
"editor.active_line.background": "#e9e6e4bf",
|
||||
"editor.highlighted_line.background": "#e9e6e4ff",
|
||||
"editor.line_number": "#a3a19f",
|
||||
"editor.active_line_number": "#272625",
|
||||
"editor.hover_line_number": "#4e4d4b",
|
||||
"editor.line_number": "#1b191859",
|
||||
"editor.active_line_number": "#1b1918ff",
|
||||
"editor.invisible": "#89817dff",
|
||||
"editor.wrap_guide": "#1b19180d",
|
||||
"editor.active_wrap_guide": "#1b19181a",
|
||||
@@ -3147,9 +3139,8 @@
|
||||
"editor.subheader.background": "#252025ff",
|
||||
"editor.active_line.background": "#252025bf",
|
||||
"editor.highlighted_line.background": "#252025ff",
|
||||
"editor.line_number": "#555256",
|
||||
"editor.active_line_number": "#e6e5e6",
|
||||
"editor.hover_line_number": "#c0bec1",
|
||||
"editor.line_number": "#f7f3f759",
|
||||
"editor.active_line_number": "#f7f3f7ff",
|
||||
"editor.invisible": "#8b7b8bff",
|
||||
"editor.wrap_guide": "#f7f3f70d",
|
||||
"editor.active_wrap_guide": "#f7f3f71a",
|
||||
@@ -3533,9 +3524,8 @@
|
||||
"editor.subheader.background": "#e0d5e0ff",
|
||||
"editor.active_line.background": "#e0d5e0bf",
|
||||
"editor.highlighted_line.background": "#e0d5e0ff",
|
||||
"editor.line_number": "#a9a7aa",
|
||||
"editor.active_line_number": "#262627",
|
||||
"editor.hover_line_number": "#403f41",
|
||||
"editor.line_number": "#1b181b59",
|
||||
"editor.active_line_number": "#1b181bff",
|
||||
"editor.invisible": "#8b7b8bff",
|
||||
"editor.wrap_guide": "#1b181b0d",
|
||||
"editor.active_wrap_guide": "#1b181b1a",
|
||||
@@ -3919,9 +3909,8 @@
|
||||
"editor.subheader.background": "#1c2529ff",
|
||||
"editor.active_line.background": "#1c2529bf",
|
||||
"editor.highlighted_line.background": "#1c2529ff",
|
||||
"editor.line_number": "#61686ck",
|
||||
"editor.active_line_number": "#eaebec",
|
||||
"editor.hover_line_number": "#bcc0c3",
|
||||
"editor.line_number": "#ebf8ff59",
|
||||
"editor.active_line_number": "#ebf8ffff",
|
||||
"editor.invisible": "#66889aff",
|
||||
"editor.wrap_guide": "#ebf8ff0d",
|
||||
"editor.active_wrap_guide": "#ebf8ff1a",
|
||||
@@ -4305,9 +4294,8 @@
|
||||
"editor.subheader.background": "#cdeaf9ff",
|
||||
"editor.active_line.background": "#cdeaf9bf",
|
||||
"editor.highlighted_line.background": "#cdeaf9ff",
|
||||
"editor.line_number": "#a3abafk",
|
||||
"editor.active_line_number": "#242729",
|
||||
"editor.hover_line_number": "#3b4144",
|
||||
"editor.line_number": "#161b1d59",
|
||||
"editor.active_line_number": "#161b1dff",
|
||||
"editor.invisible": "#66889aff",
|
||||
"editor.wrap_guide": "#161b1d0d",
|
||||
"editor.active_wrap_guide": "#161b1d1a",
|
||||
@@ -4691,9 +4679,8 @@
|
||||
"editor.subheader.background": "#252020ff",
|
||||
"editor.active_line.background": "#252020bf",
|
||||
"editor.highlighted_line.background": "#252020ff",
|
||||
"editor.line_number": "#666262",
|
||||
"editor.active_line_number": "#e6e5e5",
|
||||
"editor.hover_line_number": "#b9b6b6",
|
||||
"editor.line_number": "#f4ecec59",
|
||||
"editor.active_line_number": "#f4ececff",
|
||||
"editor.invisible": "#726a6aff",
|
||||
"editor.wrap_guide": "#f4ecec0d",
|
||||
"editor.active_wrap_guide": "#f4ecec1a",
|
||||
@@ -5077,9 +5064,8 @@
|
||||
"editor.subheader.background": "#ebe3e3ff",
|
||||
"editor.active_line.background": "#ebe3e3bf",
|
||||
"editor.highlighted_line.background": "#ebe3e3ff",
|
||||
"editor.line_number": "#a7a2a2",
|
||||
"editor.active_line_number": "#272525",
|
||||
"editor.hover_line_number": "#3f3c3c",
|
||||
"editor.line_number": "#1b181859",
|
||||
"editor.active_line_number": "#1b1818ff",
|
||||
"editor.invisible": "#726a6aff",
|
||||
"editor.wrap_guide": "#1b18180d",
|
||||
"editor.active_wrap_guide": "#1b18181a",
|
||||
@@ -5463,9 +5449,8 @@
|
||||
"editor.subheader.background": "#1f2621ff",
|
||||
"editor.active_line.background": "#1f2621bf",
|
||||
"editor.highlighted_line.background": "#1f2621ff",
|
||||
"editor.line_number": "#626763",
|
||||
"editor.active_line_number": "#e5e6e5",
|
||||
"editor.hover_line_number": "#b6b9b7",
|
||||
"editor.line_number": "#ecf4ee59",
|
||||
"editor.active_line_number": "#ecf4eeff",
|
||||
"editor.invisible": "#6c7a71ff",
|
||||
"editor.wrap_guide": "#ecf4ee0d",
|
||||
"editor.active_wrap_guide": "#ecf4ee1a",
|
||||
@@ -5849,9 +5834,8 @@
|
||||
"editor.subheader.background": "#e3ebe6ff",
|
||||
"editor.active_line.background": "#e3ebe6bf",
|
||||
"editor.highlighted_line.background": "#e3ebe6ff",
|
||||
"editor.line_number": "#a3a9a4",
|
||||
"editor.active_line_number": "#252825",
|
||||
"editor.hover_line_number": "#313532",
|
||||
"editor.line_number": "#171c1959",
|
||||
"editor.active_line_number": "#171c19ff",
|
||||
"editor.invisible": "#6c7a71ff",
|
||||
"editor.wrap_guide": "#171c190d",
|
||||
"editor.active_wrap_guide": "#171c191a",
|
||||
@@ -6235,9 +6219,8 @@
|
||||
"editor.subheader.background": "#1f231fff",
|
||||
"editor.active_line.background": "#1f231fbf",
|
||||
"editor.highlighted_line.background": "#1f231fff",
|
||||
"editor.line_number": "#626561",
|
||||
"editor.active_line_number": "#e5e6e5",
|
||||
"editor.hover_line_number": "#b7b9b6",
|
||||
"editor.line_number": "#f3faf359",
|
||||
"editor.active_line_number": "#f3faf3ff",
|
||||
"editor.invisible": "#738b73ff",
|
||||
"editor.wrap_guide": "#f3faf30d",
|
||||
"editor.active_wrap_guide": "#f3faf31a",
|
||||
@@ -6621,9 +6604,8 @@
|
||||
"editor.subheader.background": "#daeedaff",
|
||||
"editor.active_line.background": "#daeedabf",
|
||||
"editor.highlighted_line.background": "#daeedaff",
|
||||
"editor.line_number": "#a6aaa5",
|
||||
"editor.active_line_number": "#262725",
|
||||
"editor.hover_line_number": "#3f423e",
|
||||
"editor.line_number": "#13151359",
|
||||
"editor.active_line_number": "#131513ff",
|
||||
"editor.invisible": "#738b73ff",
|
||||
"editor.wrap_guide": "#1315130d",
|
||||
"editor.active_wrap_guide": "#1315131a",
|
||||
@@ -7007,9 +6989,8 @@
|
||||
"editor.subheader.background": "#262f51ff",
|
||||
"editor.active_line.background": "#262f51bf",
|
||||
"editor.highlighted_line.background": "#262f51ff",
|
||||
"editor.line_number": "#6b6f85",
|
||||
"editor.active_line_number": "#e3e4e8",
|
||||
"editor.hover_line_number": "#b8bac6",
|
||||
"editor.line_number": "#f5f7ff59",
|
||||
"editor.active_line_number": "#f5f7ffff",
|
||||
"editor.invisible": "#7a819cff",
|
||||
"editor.wrap_guide": "#f5f7ff0d",
|
||||
"editor.active_wrap_guide": "#f5f7ff1a",
|
||||
@@ -7393,9 +7374,8 @@
|
||||
"editor.subheader.background": "#e5e8f5ff",
|
||||
"editor.active_line.background": "#e5e8f5bf",
|
||||
"editor.highlighted_line.background": "#e5e8f5ff",
|
||||
"editor.line_number": "#abaebd",
|
||||
"editor.active_line_number": "#22232b",
|
||||
"editor.hover_line_number": "#434656",
|
||||
"editor.line_number": "#20264659",
|
||||
"editor.active_line_number": "#202646ff",
|
||||
"editor.invisible": "#7a819cff",
|
||||
"editor.wrap_guide": "#2026460d",
|
||||
"editor.active_wrap_guide": "#2026461a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#1f2127ff",
|
||||
"editor.active_line.background": "#1f2127bf",
|
||||
"editor.highlighted_line.background": "#1f2127ff",
|
||||
"editor.line_number": "#4b4c4e",
|
||||
"editor.active_line_number": "#cbcccd",
|
||||
"editor.hover_line_number": "#a1a2a5",
|
||||
"editor.line_number": "#bfbdb659",
|
||||
"editor.active_line_number": "#bfbdb6ff",
|
||||
"editor.invisible": "#666767ff",
|
||||
"editor.wrap_guide": "#bfbdb60d",
|
||||
"editor.active_wrap_guide": "#bfbdb61a",
|
||||
@@ -430,9 +429,8 @@
|
||||
"editor.subheader.background": "#ececedff",
|
||||
"editor.active_line.background": "#ececedbf",
|
||||
"editor.highlighted_line.background": "#ececedff",
|
||||
"editor.line_number": "#b0b3b5",
|
||||
"editor.active_line_number": "#313435",
|
||||
"editor.hover_line_number": "#62686a",
|
||||
"editor.line_number": "#5c616659",
|
||||
"editor.active_line_number": "#5c6166ff",
|
||||
"editor.invisible": "#acafb1ff",
|
||||
"editor.wrap_guide": "#5c61660d",
|
||||
"editor.active_wrap_guide": "#5c61661a",
|
||||
@@ -801,9 +799,8 @@
|
||||
"editor.subheader.background": "#353944ff",
|
||||
"editor.active_line.background": "#353944bf",
|
||||
"editor.highlighted_line.background": "#353944ff",
|
||||
"editor.line_number": "#575c6b",
|
||||
"editor.active_line_number": "#e1e3ea",
|
||||
"editor.hover_line_number": "#b2b6c8",
|
||||
"editor.line_number": "#cccac259",
|
||||
"editor.active_line_number": "#cccac2ff",
|
||||
"editor.invisible": "#787a7cff",
|
||||
"editor.wrap_guide": "#cccac20d",
|
||||
"editor.active_wrap_guide": "#cccac21a",
|
||||
|
||||
@@ -68,9 +68,8 @@
|
||||
"editor.subheader.background": "#3a3735ff",
|
||||
"editor.active_line.background": "#3a3735bf",
|
||||
"editor.highlighted_line.background": "#3a3735ff",
|
||||
"editor.line_number": "#6e6b5e",
|
||||
"editor.active_line_number": "#dedcd3",
|
||||
"editor.hover_line_number": "#c9c5b6",
|
||||
"editor.line_number": "#fbf1c759",
|
||||
"editor.active_line_number": "#fbf1c7ff",
|
||||
"editor.invisible": "#928474ff",
|
||||
"editor.wrap_guide": "#fbf1c70d",
|
||||
"editor.active_wrap_guide": "#fbf1c71a",
|
||||
@@ -453,9 +452,8 @@
|
||||
"editor.subheader.background": "#393634ff",
|
||||
"editor.active_line.background": "#393634bf",
|
||||
"editor.highlighted_line.background": "#393634ff",
|
||||
"editor.line_number": "#6e6b5e",
|
||||
"editor.active_line_number": "#dedcd3",
|
||||
"editor.hover_line_number": "#c9c5b6",
|
||||
"editor.line_number": "#fbf1c759",
|
||||
"editor.active_line_number": "#fbf1c7ff",
|
||||
"editor.invisible": "#928474ff",
|
||||
"editor.wrap_guide": "#fbf1c70d",
|
||||
"editor.active_wrap_guide": "#fbf1c71a",
|
||||
@@ -838,9 +836,8 @@
|
||||
"editor.subheader.background": "#3b3735ff",
|
||||
"editor.active_line.background": "#3b3735bf",
|
||||
"editor.highlighted_line.background": "#3b3735ff",
|
||||
"editor.line_number": "#6e6b5e",
|
||||
"editor.active_line_number": "#dedcd3",
|
||||
"editor.hover_line_number": "#c9c5b6",
|
||||
"editor.line_number": "#fbf1c759",
|
||||
"editor.active_line_number": "#fbf1c7ff",
|
||||
"editor.invisible": "#928474ff",
|
||||
"editor.wrap_guide": "#fbf1c70d",
|
||||
"editor.active_wrap_guide": "#fbf1c71a",
|
||||
@@ -1223,9 +1220,8 @@
|
||||
"editor.subheader.background": "#ecddb4ff",
|
||||
"editor.active_line.background": "#ecddb4bf",
|
||||
"editor.highlighted_line.background": "#ecddb4ff",
|
||||
"editor.line_number": "#a9a389",
|
||||
"editor.active_line_number": "#3b382b",
|
||||
"editor.hover_line_number": "#5e5a45",
|
||||
"editor.line_number": "#28282859",
|
||||
"editor.active_line_number": "#282828ff",
|
||||
"editor.invisible": "#928474ff",
|
||||
"editor.wrap_guide": "#2828280d",
|
||||
"editor.active_wrap_guide": "#2828281a",
|
||||
@@ -1608,9 +1604,8 @@
|
||||
"editor.subheader.background": "#ecddb5ff",
|
||||
"editor.active_line.background": "#ecddb5bf",
|
||||
"editor.highlighted_line.background": "#ecddb5ff",
|
||||
"editor.line_number": "#a9a389",
|
||||
"editor.active_line_number": "#3b382b",
|
||||
"editor.hover_line_number": "#5e5a45",
|
||||
"editor.line_number": "#28282859",
|
||||
"editor.active_line_number": "#282828ff",
|
||||
"editor.invisible": "#928474ff",
|
||||
"editor.wrap_guide": "#2828280d",
|
||||
"editor.active_wrap_guide": "#2828281a",
|
||||
@@ -1993,9 +1988,8 @@
|
||||
"editor.subheader.background": "#ecdcb3ff",
|
||||
"editor.active_line.background": "#ecdcb3bf",
|
||||
"editor.highlighted_line.background": "#ecdcb3ff",
|
||||
"editor.line_number": "#a9a389",
|
||||
"editor.active_line_number": "#3b382b",
|
||||
"editor.hover_line_number": "#5e5a45",
|
||||
"editor.line_number": "#28282859",
|
||||
"editor.active_line_number": "#282828ff",
|
||||
"editor.invisible": "#928474ff",
|
||||
"editor.wrap_guide": "#2828280d",
|
||||
"editor.active_wrap_guide": "#2828281a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#2f343eff",
|
||||
"editor.active_line.background": "#2f343ebf",
|
||||
"editor.highlighted_line.background": "#2f343eff",
|
||||
"editor.line_number": "#4e5a5f",
|
||||
"editor.active_line_number": "#d0d4da",
|
||||
"editor.hover_line_number": "#acb0b4",
|
||||
"editor.line_number": "#c8ccd459",
|
||||
"editor.active_line_number": "#dce0e5ff",
|
||||
"editor.invisible": "#878a98ff",
|
||||
"editor.wrap_guide": "#c8ccd40d",
|
||||
"editor.active_wrap_guide": "#c8ccd41a",
|
||||
@@ -435,9 +434,8 @@
|
||||
"editor.subheader.background": "#ebebecff",
|
||||
"editor.active_line.background": "#ebebecbf",
|
||||
"editor.highlighted_line.background": "#ebebecff",
|
||||
"editor.line_number": "#b4b4bb",
|
||||
"editor.active_line_number": "#44454b",
|
||||
"editor.hover_line_number": "#61616b",
|
||||
"editor.line_number": "#383a4159",
|
||||
"editor.active_line_number": "#242529ff",
|
||||
"editor.invisible": "#a3a3a4ff",
|
||||
"editor.wrap_guide": "#383a410d",
|
||||
"editor.active_wrap_guide": "#383a411a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#1c1b2aff",
|
||||
"editor.active_line.background": "#1c1b2abf",
|
||||
"editor.highlighted_line.background": "#1c1b2aff",
|
||||
"editor.line_number": "#605e6e",
|
||||
"editor.active_line_number": "#c9c8d0",
|
||||
"editor.hover_line_number": "#aeadb8",
|
||||
"editor.line_number": "#e0def459",
|
||||
"editor.active_line_number": "#e0def4ff",
|
||||
"editor.invisible": "#28253cff",
|
||||
"editor.wrap_guide": "#e0def40d",
|
||||
"editor.active_wrap_guide": "#e0def41a",
|
||||
@@ -440,9 +439,8 @@
|
||||
"editor.subheader.background": "#fef9f2ff",
|
||||
"editor.active_line.background": "#fef9f2bf",
|
||||
"editor.highlighted_line.background": "#fef9f2ff",
|
||||
"editor.line_number": "#b4adb8",
|
||||
"editor.active_line_number": "#4e4752",
|
||||
"editor.hover_line_number": "#685f6d",
|
||||
"editor.line_number": "#57527959",
|
||||
"editor.active_line_number": "#575279ff",
|
||||
"editor.invisible": "#9691a4ff",
|
||||
"editor.wrap_guide": "#5752790d",
|
||||
"editor.active_wrap_guide": "#5752791a",
|
||||
@@ -821,9 +819,8 @@
|
||||
"editor.subheader.background": "#28253cff",
|
||||
"editor.active_line.background": "#28253cbf",
|
||||
"editor.highlighted_line.background": "#28253cff",
|
||||
"editor.line_number": "#6b697d",
|
||||
"editor.active_line_number": "#d6d5dc",
|
||||
"editor.hover_line_number": "#bbbac5",
|
||||
"editor.line_number": "#e0def459",
|
||||
"editor.active_line_number": "#e0def4ff",
|
||||
"editor.invisible": "#595571ff",
|
||||
"editor.wrap_guide": "#e0def40d",
|
||||
"editor.active_wrap_guide": "#e0def41a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#2b3038ff",
|
||||
"editor.active_line.background": "#2b3038bf",
|
||||
"editor.highlighted_line.background": "#2b3038ff",
|
||||
"editor.line_number": "#6b6b61",
|
||||
"editor.active_line_number": "#dbdbd7",
|
||||
"editor.hover_line_number": "#b6b6af",
|
||||
"editor.line_number": "#fdf4c159",
|
||||
"editor.active_line_number": "#fdf4c1ff",
|
||||
"editor.invisible": "#7c6f64ff",
|
||||
"editor.wrap_guide": "#fdf4c10d",
|
||||
"editor.active_wrap_guide": "#fdf4c11a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#04313bff",
|
||||
"editor.active_line.background": "#04313bbf",
|
||||
"editor.highlighted_line.background": "#04313bff",
|
||||
"editor.line_number": "#5a6d6f",
|
||||
"editor.active_line_number": "#e3e8e8",
|
||||
"editor.hover_line_number": "#abb9ba",
|
||||
"editor.line_number": "#fdf6e359",
|
||||
"editor.active_line_number": "#fdf6e3ff",
|
||||
"editor.invisible": "#6c8287ff",
|
||||
"editor.wrap_guide": "#fdf6e30d",
|
||||
"editor.active_wrap_guide": "#fdf6e31a",
|
||||
@@ -430,9 +429,8 @@
|
||||
"editor.subheader.background": "#f3eddaff",
|
||||
"editor.active_line.background": "#f3eddabf",
|
||||
"editor.highlighted_line.background": "#f3eddaff",
|
||||
"editor.line_number": "#a8ad9f",
|
||||
"editor.active_line_number": "#272923",
|
||||
"editor.hover_line_number": "#42453b",
|
||||
"editor.line_number": "#002a3559",
|
||||
"editor.active_line_number": "#002a35ff",
|
||||
"editor.invisible": "#6c8287ff",
|
||||
"editor.wrap_guide": "#002a350d",
|
||||
"editor.active_wrap_guide": "#002a351a",
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
"editor.subheader.background": "#231f16ff",
|
||||
"editor.active_line.background": "#231f16bf",
|
||||
"editor.highlighted_line.background": "#231f16ff",
|
||||
"editor.line_number": "#676559",
|
||||
"editor.active_line_number": "#e3e2de",
|
||||
"editor.hover_line_number": "#b8b6ad",
|
||||
"editor.line_number": "#f8f5de59",
|
||||
"editor.active_line_number": "#f8f5deff",
|
||||
"editor.invisible": "#494433ff",
|
||||
"editor.wrap_guide": "#f8f5de0d",
|
||||
"editor.active_wrap_guide": "#f8f5de1a",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "activity_indicator"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "anthropic"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -77,8 +77,8 @@ impl Model {
|
||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||
Model::Claude3Opus => "claude-3-opus-latest",
|
||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||
Model::Claude3Haiku => "claude-3-haiku-20240307",
|
||||
Model::Claude3Sonnet => "claude-3-sonnet-latest",
|
||||
Model::Claude3Haiku => "claude-3-haiku-latest",
|
||||
Self::Custom { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "assets"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "assistant"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
@@ -21,14 +21,16 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assets.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
cargo_toml.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
context_server.workspace = true
|
||||
@@ -37,7 +39,13 @@ editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
globset.workspace = true
|
||||
gpui.workspace = true
|
||||
handlebars.workspace = true
|
||||
heed.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
@@ -46,30 +54,40 @@ language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_library.workspace = true
|
||||
proto.workspace = true
|
||||
regex.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
semantic_index.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
similar.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
telemetry.workspace = true
|
||||
strum.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
|
||||
@@ -1,46 +1,98 @@
|
||||
#![cfg_attr(target_os = "windows", allow(unused, dead_code))]
|
||||
|
||||
mod assistant_configuration;
|
||||
pub mod assistant_panel;
|
||||
pub mod assistant_settings;
|
||||
mod context;
|
||||
pub mod context_store;
|
||||
mod inline_assistant;
|
||||
mod patch;
|
||||
mod prompt_library;
|
||||
mod prompts;
|
||||
mod slash_command;
|
||||
pub(crate) mod slash_command_picker;
|
||||
pub mod slash_command_settings;
|
||||
mod slash_command_working_set;
|
||||
mod streaming_diff;
|
||||
mod terminal_inline_assistant;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::slash_command::project_command::ProjectSlashCommandFeatureFlag;
|
||||
pub use crate::slash_command_working_set::{SlashCommandId, SlashCommandWorkingSet};
|
||||
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag};
|
||||
use client::Client;
|
||||
use client::{proto, Client};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
pub use context::*;
|
||||
pub use context_store::*;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::{actions, AppContext, Global, UpdateGlobal};
|
||||
use gpui::impl_actions;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
||||
};
|
||||
use prompt_library::PromptBuilder;
|
||||
pub use patch::*;
|
||||
pub use prompts::PromptBuilder;
|
||||
use prompts::PromptLoadingParams;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use slash_command::search_command::SearchSlashCommandFeatureFlag;
|
||||
use slash_command::{
|
||||
auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
|
||||
docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
|
||||
search_command, selection_command, symbols_command, tab_command, terminal_command,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
pub(crate) use streaming_diff::*;
|
||||
use util::ResultExt;
|
||||
|
||||
pub use crate::assistant_panel::{AssistantPanel, AssistantPanelEvent};
|
||||
pub(crate) use crate::inline_assistant::*;
|
||||
use crate::slash_command::streaming_example_command;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
|
||||
actions!(
|
||||
assistant,
|
||||
[
|
||||
Assist,
|
||||
Edit,
|
||||
Split,
|
||||
CopyCode,
|
||||
CycleMessageRole,
|
||||
QuoteSelection,
|
||||
InsertIntoEditor,
|
||||
ToggleFocus,
|
||||
InsertActivePrompt,
|
||||
DeployHistory,
|
||||
DeployPromptLibrary,
|
||||
ConfirmCommand,
|
||||
NewContext,
|
||||
ToggleModelSelector,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist
|
||||
]
|
||||
);
|
||||
|
||||
#[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)]
|
||||
pub struct MessageId(clock::Lamport);
|
||||
|
||||
impl MessageId {
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0.as_u64()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct LanguageModelUsage {
|
||||
pub prompt_tokens: u32,
|
||||
@@ -55,6 +107,55 @@ pub struct LanguageModelChoiceDelta {
|
||||
pub finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MessageStatus {
|
||||
Pending,
|
||||
Done,
|
||||
Error(SharedString),
|
||||
Canceled,
|
||||
}
|
||||
|
||||
impl MessageStatus {
|
||||
pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
|
||||
match status.variant {
|
||||
Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
|
||||
Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
|
||||
Some(proto::context_message_status::Variant::Error(error)) => {
|
||||
MessageStatus::Error(error.message.into())
|
||||
}
|
||||
Some(proto::context_message_status::Variant::Canceled(_)) => MessageStatus::Canceled,
|
||||
None => MessageStatus::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::ContextMessageStatus {
|
||||
match self {
|
||||
MessageStatus::Pending => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Pending(
|
||||
proto::context_message_status::Pending {},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Done => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Done(
|
||||
proto::context_message_status::Done {},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Error(message) => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Error(
|
||||
proto::context_message_status::Error {
|
||||
message: message.to_string(),
|
||||
},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Canceled => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Canceled(
|
||||
proto::context_message_status::Canceled {},
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state pertaining to the Assistant.
|
||||
#[derive(Default)]
|
||||
struct Assistant {
|
||||
@@ -91,9 +192,9 @@ impl Assistant {
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
stdout_is_a_pty: bool,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
) -> Arc<PromptBuilder> {
|
||||
cx.set_global(Assistant::default());
|
||||
AssistantSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
@@ -125,7 +226,7 @@ pub fn init(
|
||||
})
|
||||
.detach();
|
||||
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
context_store::init(&client.clone().into());
|
||||
prompt_library::init(cx);
|
||||
init_language_model_settings(cx);
|
||||
assistant_slash_command::init(cx);
|
||||
@@ -133,6 +234,16 @@ pub fn init(
|
||||
assistant_panel::init(cx);
|
||||
context_server::init(cx);
|
||||
|
||||
let prompt_builder = prompts::PromptBuilder::new(Some(PromptLoadingParams {
|
||||
fs: fs.clone(),
|
||||
repo_path: stdout_is_a_pty
|
||||
.then(|| std::env::current_dir().log_err())
|
||||
.flatten(),
|
||||
cx,
|
||||
}))
|
||||
.log_err()
|
||||
.map(Arc::new)
|
||||
.unwrap_or_else(|| Arc::new(prompts::PromptBuilder::new(None).unwrap()));
|
||||
register_slash_commands(Some(prompt_builder.clone()), cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
@@ -163,6 +274,8 @@ pub fn init(
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
prompt_builder
|
||||
}
|
||||
|
||||
fn init_language_model_settings(cx: &mut AppContext) {
|
||||
@@ -207,28 +320,27 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
|
||||
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut AppContext) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
|
||||
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
|
||||
slash_command_registry.register_command(file_command::FileSlashCommand, true);
|
||||
slash_command_registry.register_command(delta_command::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(tab_command::TabSlashCommand, true);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TerminalSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
|
||||
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(selection_command::SelectionCommand, true);
|
||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
||||
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, true);
|
||||
|
||||
if let Some(prompt_builder) = prompt_builder {
|
||||
cx.observe_flag::<assistant_slash_commands::ProjectSlashCommandFeatureFlag, _>({
|
||||
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(
|
||||
assistant_slash_commands::ProjectSlashCommand::new(prompt_builder.clone()),
|
||||
project_command::ProjectSlashCommand::new(prompt_builder.clone()),
|
||||
true,
|
||||
);
|
||||
}
|
||||
@@ -237,24 +349,23 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
.detach();
|
||||
}
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::AutoSlashCommandFeatureFlag, _>({
|
||||
cx.observe_flag::<auto_command::AutoSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
// [#auto-staff-ship] TODO remove this when /auto is no longer staff-shipped
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::AutoCommand, true);
|
||||
slash_command_registry.register_command(auto_command::AutoCommand, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||
cx.observe_flag::<streaming_example_command::StreamingExampleSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry.register_command(
|
||||
assistant_slash_commands::StreamingExampleSlashCommand,
|
||||
streaming_example_command::StreamingExampleSlashCommand,
|
||||
false,
|
||||
);
|
||||
}
|
||||
@@ -266,12 +377,11 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
|
||||
cx.observe_flag::<assistant_slash_commands::SearchSlashCommandFeatureFlag, _>({
|
||||
cx.observe_flag::<search_command::SearchSlashCommandFeatureFlag, _>({
|
||||
let slash_command_registry = slash_command_registry.clone();
|
||||
move |is_enabled, _cx| {
|
||||
if is_enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::SearchSlashCommand, true);
|
||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -283,17 +393,35 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
|
||||
let settings = SlashCommandSettings::get_global(cx);
|
||||
|
||||
if settings.docs.enabled {
|
||||
slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
|
||||
slash_command_registry.register_command(docs_command::DocsSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
|
||||
slash_command_registry.unregister_command(docs_command::DocsSlashCommand);
|
||||
}
|
||||
|
||||
if settings.cargo_workspace.enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry
|
||||
.unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
|
||||
.unregister_command(cargo_workspace_command::CargoWorkspaceSlashCommand);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn humanize_token_count(count: usize) -> String {
|
||||
match count {
|
||||
0..=999 => count.to_string(),
|
||||
1000..=9999 => {
|
||||
let thousands = count / 1000;
|
||||
let hundreds = (count % 1000 + 50) / 100;
|
||||
if hundreds == 0 {
|
||||
format!("{}k", thousands)
|
||||
} else if hundreds == 10 {
|
||||
format!("{}k", thousands + 1)
|
||||
} else {
|
||||
format!("{}.{}k", thousands, hundreds)
|
||||
}
|
||||
}
|
||||
_ => format!("{}k", (count + 500) / 1000),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{canvas, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use ui::{prelude::*, ElevationIndex};
|
||||
use workspace::Item;
|
||||
|
||||
pub struct ConfigurationView {
|
||||
focus_handle: FocusHandle,
|
||||
configuration_views: HashMap<LanguageModelProviderId, AnyView>,
|
||||
_registry_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let registry_subscription = cx.subscribe(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|this, _, event: &language_model::Event, cx| match event {
|
||||
language_model::Event::AddedProvider(provider_id) => {
|
||||
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
||||
if let Some(provider) = provider {
|
||||
this.add_configuration_view(&provider, cx);
|
||||
}
|
||||
}
|
||||
language_model::Event::RemovedProvider(provider_id) => {
|
||||
this.remove_configuration_view(provider_id);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
);
|
||||
|
||||
let mut this = Self {
|
||||
focus_handle,
|
||||
configuration_views: HashMap::default(),
|
||||
_registry_subscription: registry_subscription,
|
||||
};
|
||||
this.build_configuration_views(cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
for provider in providers {
|
||||
self.add_configuration_view(&provider, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
|
||||
self.configuration_views.remove(provider_id);
|
||||
}
|
||||
|
||||
fn add_configuration_view(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let configuration_view = provider.configuration_view(cx);
|
||||
self.configuration_views
|
||||
.insert(provider.id(), configuration_view);
|
||||
}
|
||||
|
||||
fn render_provider_view(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Div {
|
||||
let provider_id = provider.id().0.clone();
|
||||
let provider_name = provider.name().0.clone();
|
||||
let configuration_view = self.configuration_views.get(&provider.id()).cloned();
|
||||
|
||||
let open_new_context = cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_, _, cx| {
|
||||
cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
|
||||
.when(provider.is_authenticated(cx), move |this| {
|
||||
this.child(
|
||||
h_flex().justify_end().child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-context-{provider_id}")),
|
||||
"Open New Chat",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.on_click(open_new_context),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.p(DynamicSpacing::Base08.rems(cx))
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.when(configuration_view.is_none(), |this| {
|
||||
this.child(div().child(Label::new(format!(
|
||||
"No configuration view for {}",
|
||||
provider_name
|
||||
))))
|
||||
})
|
||||
.when_some(configuration_view, |this, configuration_view| {
|
||||
this.child(configuration_view)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ConfigurationView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
let provider_views = providers
|
||||
.into_iter()
|
||||
.map(|provider| self.render_provider_view(&provider, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut element = v_flex()
|
||||
.id("assistant-configuration-view")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap_1()
|
||||
.child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
|
||||
.child(
|
||||
Label::new(
|
||||
"At least one LLM provider must be configured to use the Assistant.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.mt_1()
|
||||
.gap_6()
|
||||
.flex_1()
|
||||
.children(provider_views),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
// We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
|
||||
// because we couldn't the element to take up the size of the parent.
|
||||
canvas(
|
||||
move |bounds, cx| {
|
||||
element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
|
||||
element
|
||||
},
|
||||
|_, mut element, cx| {
|
||||
element.paint(cx);
|
||||
},
|
||||
)
|
||||
.flex_1()
|
||||
.w_full()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ConfigurationViewEvent {
|
||||
NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
|
||||
}
|
||||
|
||||
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
|
||||
|
||||
impl FocusableView for ConfigurationView {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ConfigurationView {
|
||||
type Event = ConfigurationViewEvent;
|
||||
|
||||
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||
Some("Configuration".into())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ use anthropic::Model as AnthropicModel;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use gpui::{AppContext, Pixels};
|
||||
use language_model::{CloudModel, LanguageModel};
|
||||
use lmstudio::Model as LmStudioModel;
|
||||
use ollama::Model as OllamaModel;
|
||||
use schemars::{schema::Schema, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -41,11 +40,6 @@ pub enum AssistantProviderContentV1 {
|
||||
default_model: Option<OllamaModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
#[serde(rename = "lmstudio")]
|
||||
LmStudio {
|
||||
default_model: Option<LmStudioModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -143,12 +137,6 @@ impl AssistantSettingsContent {
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::LmStudio { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "lmstudio".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
}),
|
||||
inline_alternatives: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
@@ -226,18 +214,6 @@ impl AssistantSettingsContent {
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"lmstudio" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::LmStudio { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::LmStudio {
|
||||
default_model: Some(lmstudio::Model::new(&model, None, None)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"openai" => {
|
||||
let (api_url, available_models) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::OpenAi {
|
||||
@@ -336,7 +312,6 @@ fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema:
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
@@ -380,7 +355,7 @@ pub struct AssistantSettingsContentV1 {
|
||||
default_height: Option<f32>,
|
||||
/// The provider of the assistant service.
|
||||
///
|
||||
/// This can be "openai", "anthropic", "ollama", "lmstudio", "zed.dev"
|
||||
/// This can be "openai", "anthropic", "ollama", "zed.dev"
|
||||
/// each with their respective default models and configurations.
|
||||
provider: Option<AssistantProviderContentV1>,
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
#[cfg(test)]
|
||||
mod context_tests;
|
||||
|
||||
use crate::patch::{AssistantEdit, AssistantPatch, AssistantPatchStatus};
|
||||
use crate::slash_command_working_set::SlashCommandWorkingSet;
|
||||
use crate::{
|
||||
prompts::PromptBuilder,
|
||||
slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
|
||||
AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{
|
||||
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
|
||||
};
|
||||
use assistant_slash_commands::FileCommandMetadata;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{self, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
@@ -19,6 +22,7 @@ use gpui::{
|
||||
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString,
|
||||
Subscription, Task,
|
||||
};
|
||||
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
@@ -33,7 +37,6 @@ use language_models::{
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use prompt_library::PromptBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
@@ -48,9 +51,9 @@ use std::{
|
||||
};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use text::{BufferSnapshot, ToPoint};
|
||||
use ui::IconName;
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
use workspace::ui::IconName;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct ContextId(String);
|
||||
@@ -69,64 +72,6 @@ impl ContextId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct MessageId(pub clock::Lamport);
|
||||
|
||||
impl MessageId {
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0.as_u64()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MessageStatus {
|
||||
Pending,
|
||||
Done,
|
||||
Error(SharedString),
|
||||
Canceled,
|
||||
}
|
||||
|
||||
impl MessageStatus {
|
||||
pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
|
||||
match status.variant {
|
||||
Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
|
||||
Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
|
||||
Some(proto::context_message_status::Variant::Error(error)) => {
|
||||
MessageStatus::Error(error.message.into())
|
||||
}
|
||||
Some(proto::context_message_status::Variant::Canceled(_)) => MessageStatus::Canceled,
|
||||
None => MessageStatus::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::ContextMessageStatus {
|
||||
match self {
|
||||
MessageStatus::Pending => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Pending(
|
||||
proto::context_message_status::Pending {},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Done => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Done(
|
||||
proto::context_message_status::Done {},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Error(message) => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Error(
|
||||
proto::context_message_status::Error {
|
||||
message: message.to_string(),
|
||||
},
|
||||
)),
|
||||
},
|
||||
MessageStatus::Canceled => proto::ContextMessageStatus {
|
||||
variant: Some(proto::context_message_status::Variant::Canceled(
|
||||
proto::context_message_status::Canceled {},
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RequestType {
|
||||
/// Request a normal chat response from the model.
|
||||
@@ -477,7 +422,7 @@ pub struct MessageCacheMetadata {
|
||||
pub struct MessageMetadata {
|
||||
pub role: Role,
|
||||
pub status: MessageStatus,
|
||||
pub timestamp: clock::Lamport,
|
||||
pub(crate) timestamp: clock::Lamport,
|
||||
#[serde(skip)]
|
||||
pub cache: Option<MessageCacheMetadata>,
|
||||
}
|
||||
@@ -598,8 +543,8 @@ pub struct Context {
|
||||
parsed_slash_commands: Vec<ParsedSlashCommand>,
|
||||
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
|
||||
edits_since_last_parse: language::Subscription,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
pub(crate) slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
pub(crate) tools: Arc<ToolWorkingSet>,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
@@ -844,14 +789,6 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slash_commands(&self) -> &Arc<SlashCommandWorkingSet> {
|
||||
&self.slash_commands
|
||||
}
|
||||
|
||||
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
|
||||
&self.tools
|
||||
}
|
||||
|
||||
pub fn set_capability(
|
||||
&mut self,
|
||||
capability: language::Capability,
|
||||
@@ -1110,7 +1047,11 @@ impl Context {
|
||||
self.summary.as_ref()
|
||||
}
|
||||
|
||||
pub fn patch_containing(&self, position: Point, cx: &AppContext) -> Option<&AssistantPatch> {
|
||||
pub(crate) fn patch_containing(
|
||||
&self,
|
||||
position: Point,
|
||||
cx: &AppContext,
|
||||
) -> Option<&AssistantPatch> {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let index = self.patches.binary_search_by(|patch| {
|
||||
let patch_range = patch.range.to_point(&buffer);
|
||||
@@ -1133,7 +1074,7 @@ impl Context {
|
||||
self.patches.iter().map(|patch| patch.range.clone())
|
||||
}
|
||||
|
||||
pub fn patch_for_range(
|
||||
pub(crate) fn patch_for_range(
|
||||
&self,
|
||||
range: &Range<language::Anchor>,
|
||||
cx: &AppContext,
|
||||
@@ -1223,7 +1164,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_count(&self) -> Option<usize> {
|
||||
pub(crate) fn token_count(&self) -> Option<usize> {
|
||||
self.token_count
|
||||
}
|
||||
|
||||
@@ -2304,10 +2245,7 @@ impl Context {
|
||||
|
||||
let mut request = self.to_completion_request(request_type, cx);
|
||||
|
||||
// Don't attach tools for now; we'll be removing tool use from
|
||||
// Assistant1 shortly.
|
||||
#[allow(clippy::overly_complex_bool_expr)]
|
||||
if false && cx.has_flag::<ToolUseFeatureFlag>() {
|
||||
if cx.has_flag::<ToolUseFeatureFlag>() {
|
||||
request.tools = self
|
||||
.tools
|
||||
.tools(cx)
|
||||
@@ -2940,7 +2878,7 @@ impl Context {
|
||||
self.message_anchors.insert(insertion_ix, new_anchor);
|
||||
}
|
||||
|
||||
pub fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) {
|
||||
pub(super) fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) {
|
||||
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||
return;
|
||||
};
|
||||
@@ -3179,7 +3117,7 @@ impl Context {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
|
||||
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
|
||||
let timestamp = self.next_timestamp();
|
||||
let summary = self.summary.get_or_insert(ContextSummary::default());
|
||||
summary.timestamp = timestamp;
|
||||
@@ -1,13 +1,15 @@
|
||||
use super::{AssistantEdit, MessageCacheMetadata};
|
||||
use crate::slash_command_working_set::SlashCommandWorkingSet;
|
||||
use crate::{
|
||||
AssistantEdit, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId,
|
||||
ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
|
||||
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
|
||||
Context, ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId,
|
||||
MessageStatus, PromptBuilder,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
|
||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
|
||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
|
||||
};
|
||||
use assistant_slash_commands::FileSlashCommand;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
@@ -21,7 +23,6 @@ use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Rol
|
||||
use parking_lot::Mutex;
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::Project;
|
||||
use prompt_library::PromptBuilder;
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@@ -47,6 +48,7 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|cx| {
|
||||
@@ -187,6 +189,7 @@ fn test_message_splitting(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
LanguageModelRegistry::test(cx);
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
@@ -291,6 +294,7 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|cx| {
|
||||
@@ -386,6 +390,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
cx.update(Project::init_settings);
|
||||
cx.update(assistant_panel::init);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
@@ -403,7 +408,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
.await;
|
||||
|
||||
let slash_command_registry = cx.update(SlashCommandRegistry::default_global);
|
||||
slash_command_registry.register_command(FileSlashCommand, false);
|
||||
slash_command_registry.register_command(file_command::FileSlashCommand, false);
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
@@ -693,6 +698,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
let project = Project::test(fs, [Path::new("/root")], cx).await;
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
|
||||
cx.update(assistant_panel::init);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
|
||||
// Create a new context
|
||||
@@ -1075,6 +1081,7 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
cx.update(assistant_panel::init);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|cx| {
|
||||
@@ -1166,6 +1173,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
cx.set_global(settings_store);
|
||||
cx.update(LanguageModelRegistry::test);
|
||||
|
||||
cx.update(assistant_panel::init);
|
||||
let slash_commands = cx.update(SlashCommandRegistry::default_global);
|
||||
slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
|
||||
slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false);
|
||||
@@ -1438,6 +1446,7 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
LanguageModelRegistry::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
assistant_panel::init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let context = cx.new_model(|cx| {
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::slash_command::context_server_command;
|
||||
use crate::SlashCommandId;
|
||||
use crate::{
|
||||
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
||||
SavedContextMetadata,
|
||||
prompts::PromptBuilder, slash_command_working_set::SlashCommandWorkingSet, Context,
|
||||
ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext, SavedContextMetadata,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||
use clock::ReplicaId;
|
||||
@@ -19,7 +20,6 @@ use gpui::{
|
||||
use language::LanguageRegistry;
|
||||
use paths::contexts_dir;
|
||||
use project::Project;
|
||||
use prompt_library::PromptBuilder;
|
||||
use regex::Regex;
|
||||
use rpc::AnyProtoClient;
|
||||
use std::sync::LazyLock;
|
||||
@@ -33,7 +33,7 @@ use std::{
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub(crate) fn init(client: &AnyProtoClient) {
|
||||
pub fn init(client: &AnyProtoClient) {
|
||||
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
|
||||
client.add_model_request_handler(ContextStore::handle_open_context);
|
||||
client.add_model_request_handler(ContextStore::handle_create_context);
|
||||
@@ -497,7 +497,11 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
|
||||
pub(super) fn loaded_context_for_id(
|
||||
&self,
|
||||
id: &ContextId,
|
||||
cx: &AppContext,
|
||||
) -> Option<Model<Context>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
if context.read(cx).id() == id {
|
||||
@@ -831,14 +835,14 @@ impl ContextStore {
|
||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||
let slash_command_ids = prompts
|
||||
.into_iter()
|
||||
.filter(assistant_slash_commands::acceptable_prompt)
|
||||
.filter(context_server_command::acceptable_prompt)
|
||||
.map(|prompt| {
|
||||
log::info!(
|
||||
"registering context server command: {:?}",
|
||||
prompt.name
|
||||
);
|
||||
slash_command_working_set.insert(Arc::new(
|
||||
assistant_slash_commands::ContextServerSlashCommand::new(
|
||||
context_server_command::ContextServerSlashCommand::new(
|
||||
context_server_manager.clone(),
|
||||
&server,
|
||||
prompt,
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, CyclePreviousInlineAssist,
|
||||
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
|
||||
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_context_editor::{humanize_token_count, RequestType};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::{telemetry::Telemetry, ErrorExt};
|
||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||
use editor::{
|
||||
@@ -16,9 +16,7 @@ use editor::{
|
||||
EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot,
|
||||
ToOffset as _, ToPoint,
|
||||
};
|
||||
use feature_flags::{
|
||||
Assistant2FeatureFlag, FeatureFlagAppExt as _, FeatureFlagViewExt as _, ZedPro,
|
||||
};
|
||||
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
@@ -40,7 +38,6 @@ use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use prompt_library::PromptBuilder;
|
||||
use rope::Rope;
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use smol::future::FutureExt;
|
||||
@@ -55,7 +52,6 @@ use std::{
|
||||
task::{self, Poll},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
@@ -77,16 +73,7 @@ pub fn init(
|
||||
let workspace = cx.view().clone();
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
inline_assistant.register_workspace(&workspace, cx)
|
||||
});
|
||||
|
||||
cx.observe_flag::<Assistant2FeatureFlag, _>({
|
||||
|is_assistant2_enabled, _view, cx| {
|
||||
InlineAssistant::update_global(cx, |inline_assistant, _cx| {
|
||||
inline_assistant.is_assistant2_enabled = is_assistant2_enabled;
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -104,7 +91,6 @@ pub struct InlineAssistant {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
is_assistant2_enabled: bool,
|
||||
}
|
||||
|
||||
impl Global for InlineAssistant {}
|
||||
@@ -126,7 +112,6 @@ impl InlineAssistant {
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
fs,
|
||||
is_assistant2_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,22 +172,15 @@ impl InlineAssistant {
|
||||
item: &dyn ItemHandle,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let is_assistant2_enabled = self.is_assistant2_enabled;
|
||||
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
if is_assistant2_enabled {
|
||||
editor
|
||||
.remove_code_action_provider(ASSISTANT_CODE_ACTION_PROVIDER_ID.into(), cx);
|
||||
} else {
|
||||
editor.add_code_action_provider(
|
||||
Rc::new(AssistantCodeActionProvider {
|
||||
editor: cx.view().downgrade(),
|
||||
workspace: workspace.downgrade(),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.push_code_action_provider(
|
||||
Rc::new(AssistantCodeActionProvider {
|
||||
editor: cx.view().downgrade(),
|
||||
workspace: workspace.downgrade(),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1206,7 +1184,6 @@ impl InlineAssistant {
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_show_scrollbars(false, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
@@ -1728,7 +1705,7 @@ impl PromptEditor {
|
||||
}
|
||||
|
||||
fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
|
||||
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, cx)
|
||||
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
|
||||
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -3449,13 +3426,7 @@ struct AssistantCodeActionProvider {
|
||||
workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
const ASSISTANT_CODE_ACTION_PROVIDER_ID: &str = "assistant";
|
||||
|
||||
impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
fn id(&self) -> Arc<str> {
|
||||
ASSISTANT_CODE_ACTION_PROVIDER_ID.into()
|
||||
}
|
||||
|
||||
fn code_actions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{cmp, ops::Range, path::Path, sync::Arc};
|
||||
use text::{AnchorRangeExt as _, Bias, OffsetRangeExt as _, Point};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AssistantPatch {
|
||||
pub(crate) struct AssistantPatch {
|
||||
pub range: Range<language::Anchor>,
|
||||
pub title: SharedString,
|
||||
pub edits: Arc<[Result<AssistantEdit>]>,
|
||||
@@ -17,13 +17,13 @@ pub struct AssistantPatch {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AssistantPatchStatus {
|
||||
pub(crate) enum AssistantPatchStatus {
|
||||
Pending,
|
||||
Ready,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AssistantEdit {
|
||||
pub(crate) struct AssistantEdit {
|
||||
pub path: String,
|
||||
pub kind: AssistantEditKind,
|
||||
}
|
||||
@@ -55,7 +55,7 @@ pub enum AssistantEditKind {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ResolvedPatch {
|
||||
pub(crate) struct ResolvedPatch {
|
||||
pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>,
|
||||
pub errors: Vec<AssistantPatchResolutionError>,
|
||||
}
|
||||
@@ -74,7 +74,7 @@ pub struct ResolvedEdit {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct AssistantPatchResolutionError {
|
||||
pub(crate) struct AssistantPatchResolutionError {
|
||||
pub edit_ix: usize,
|
||||
pub message: String,
|
||||
}
|
||||
@@ -425,7 +425,7 @@ impl AssistantEditKind {
|
||||
}
|
||||
|
||||
impl AssistantPatch {
|
||||
pub async fn resolve(
|
||||
pub(crate) async fn resolve(
|
||||
&self,
|
||||
project: Model<Project>,
|
||||
cx: &mut AsyncAppContext,
|
||||
@@ -1,40 +1,50 @@
|
||||
mod prompt_store;
|
||||
mod prompts;
|
||||
|
||||
use anyhow::Result;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssistant};
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::CompletionProvider;
|
||||
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use futures::{
|
||||
future::{self, BoxFuture, Shared},
|
||||
FutureExt,
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
actions, point, size, transparent_black, Action, AppContext, Bounds, EventEmitter, PromptLevel,
|
||||
Subscription, Task, TextStyle, TitlebarOptions, View, WindowBounds, WindowHandle,
|
||||
WindowOptions,
|
||||
actions, point, size, transparent_black, Action, AppContext, BackgroundExecutor, Bounds,
|
||||
EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TextStyle, TitlebarOptions,
|
||||
UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
||||
};
|
||||
use heed::{
|
||||
types::{SerdeBincode, SerdeJson, Str},
|
||||
Database, RoTxn,
|
||||
};
|
||||
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use release_channel::ReleaseChannel;
|
||||
use rope::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
future::Future,
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use text::LineEnding;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
div, prelude::*, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ParentElement, Render,
|
||||
SharedString, Styled, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::assistant::InlineAssist;
|
||||
|
||||
pub use crate::prompt_store::*;
|
||||
pub use crate::prompts::*;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
prompt_store::init(cx);
|
||||
}
|
||||
use zed_actions::InlineAssist;
|
||||
|
||||
actions!(
|
||||
prompt_library,
|
||||
@@ -46,27 +56,22 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
/// Init starts loading the PromptStore in the background and assigns
|
||||
/// a shared future to a global.
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let db_path = paths::prompts_dir().join("prompts-library-db.0.mdb");
|
||||
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
|
||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||
.boxed()
|
||||
.shared();
|
||||
cx.set_global(GlobalPromptStore(prompt_store_future))
|
||||
}
|
||||
|
||||
const BUILT_IN_TOOLTIP_TEXT: &'static str = concat!(
|
||||
"This prompt supports special functionality.\n",
|
||||
"It's read-only, but you can remove it from your default prompt."
|
||||
);
|
||||
|
||||
pub trait InlineAssistDelegate {
|
||||
fn assist(
|
||||
&self,
|
||||
prompt_editor: &View<Editor>,
|
||||
initial_prompt: Option<String>,
|
||||
cx: &mut ViewContext<PromptLibrary>,
|
||||
);
|
||||
|
||||
/// Returns whether the Assistant panel was focused.
|
||||
fn focus_assistant_panel(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// This function opens a new prompt library window if one doesn't exist already.
|
||||
/// If one exists, it brings it to the foreground.
|
||||
///
|
||||
@@ -75,8 +80,6 @@ pub trait InlineAssistDelegate {
|
||||
/// to a prompt library.
|
||||
pub fn open_prompt_library(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
inline_assist_delegate: Box<dyn InlineAssistDelegate>,
|
||||
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<WindowHandle<PromptLibrary>>> {
|
||||
let existing_window = cx
|
||||
@@ -106,17 +109,7 @@ pub fn open_prompt_library(
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
cx.new_view(|cx| {
|
||||
PromptLibrary::new(
|
||||
store,
|
||||
language_registry,
|
||||
inline_assist_delegate,
|
||||
make_completion_provider,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
},
|
||||
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
|
||||
)
|
||||
})?
|
||||
})
|
||||
@@ -130,8 +123,6 @@ pub struct PromptLibrary {
|
||||
active_prompt_id: Option<PromptId>,
|
||||
picker: View<Picker<PromptPickerDelegate>>,
|
||||
pending_load: Task<()>,
|
||||
inline_assist_delegate: Box<dyn InlineAssistDelegate>,
|
||||
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -322,8 +313,6 @@ impl PromptLibrary {
|
||||
fn new(
|
||||
store: Arc<PromptStore>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
inline_assist_delegate: Box<dyn InlineAssistDelegate>,
|
||||
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = PromptPickerDelegate {
|
||||
@@ -345,8 +334,6 @@ impl PromptLibrary {
|
||||
prompt_editors: HashMap::default(),
|
||||
active_prompt_id: None,
|
||||
pending_load: Task::ready(()),
|
||||
inline_assist_delegate,
|
||||
make_completion_provider,
|
||||
_subscriptions: vec![cx.subscribe(&picker, Self::handle_picker_event)],
|
||||
picker,
|
||||
}
|
||||
@@ -501,7 +488,6 @@ impl PromptLibrary {
|
||||
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
|
||||
let language_registry = self.language_registry.clone();
|
||||
let prompt = self.store.load(prompt_id);
|
||||
let make_completion_provider = self.make_completion_provider.clone();
|
||||
self.pending_load = cx.spawn(|this, mut cx| async move {
|
||||
let prompt = prompt.await;
|
||||
let markdown = language_registry.language_for_name("Markdown").await;
|
||||
@@ -536,7 +522,13 @@ impl PromptLibrary {
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_use_modal_editing(false);
|
||||
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
||||
editor.set_completion_provider(Some(make_completion_provider()));
|
||||
editor.set_completion_provider(Some(Box::new(
|
||||
SlashCommandCompletionProvider::new(
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
)));
|
||||
if focus {
|
||||
editor.focus(cx);
|
||||
}
|
||||
@@ -700,19 +692,20 @@ impl PromptLibrary {
|
||||
|
||||
let initial_prompt = action.prompt.clone();
|
||||
if provider.is_authenticated(cx) {
|
||||
self.inline_assist_delegate
|
||||
.assist(prompt_editor, initial_prompt, cx);
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&prompt_editor, None, None, initial_prompt, cx)
|
||||
})
|
||||
} else {
|
||||
for window in cx.windows() {
|
||||
if let Some(workspace) = window.downcast::<Workspace>() {
|
||||
let panel = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
cx.activate_window();
|
||||
self.inline_assist_delegate
|
||||
.focus_assistant_panel(workspace, cx)
|
||||
workspace.focus_panel::<AssistantPanel>(cx)
|
||||
})
|
||||
.ok();
|
||||
if panel == Some(true) {
|
||||
.ok()
|
||||
.flatten();
|
||||
if panel.is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1172,3 +1165,381 @@ impl Render for PromptLibrary {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PromptMetadata {
|
||||
pub id: PromptId,
|
||||
pub title: Option<SharedString>,
|
||||
pub default: bool,
|
||||
pub saved_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum PromptId {
|
||||
User { uuid: Uuid },
|
||||
EditWorkflow,
|
||||
}
|
||||
|
||||
impl PromptId {
|
||||
pub fn new() -> PromptId {
|
||||
PromptId::User {
|
||||
uuid: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_built_in(&self) -> bool {
|
||||
!matches!(self, PromptId::User { .. })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PromptStore {
|
||||
executor: BackgroundExecutor,
|
||||
env: heed::Env,
|
||||
metadata_cache: RwLock<MetadataCache>,
|
||||
metadata: Database<SerdeJson<PromptId>, SerdeJson<PromptMetadata>>,
|
||||
bodies: Database<SerdeJson<PromptId>, Str>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MetadataCache {
|
||||
metadata: Vec<PromptMetadata>,
|
||||
metadata_by_id: HashMap<PromptId, PromptMetadata>,
|
||||
}
|
||||
|
||||
impl MetadataCache {
|
||||
fn from_db(
|
||||
db: Database<SerdeJson<PromptId>, SerdeJson<PromptMetadata>>,
|
||||
txn: &RoTxn,
|
||||
) -> Result<Self> {
|
||||
let mut cache = MetadataCache::default();
|
||||
for result in db.iter(txn)? {
|
||||
let (prompt_id, metadata) = result?;
|
||||
cache.metadata.push(metadata.clone());
|
||||
cache.metadata_by_id.insert(prompt_id, metadata);
|
||||
}
|
||||
cache.sort();
|
||||
Ok(cache)
|
||||
}
|
||||
|
||||
fn insert(&mut self, metadata: PromptMetadata) {
|
||||
self.metadata_by_id.insert(metadata.id, metadata.clone());
|
||||
if let Some(old_metadata) = self.metadata.iter_mut().find(|m| m.id == metadata.id) {
|
||||
*old_metadata = metadata;
|
||||
} else {
|
||||
self.metadata.push(metadata);
|
||||
}
|
||||
self.sort();
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: PromptId) {
|
||||
self.metadata.retain(|metadata| metadata.id != id);
|
||||
self.metadata_by_id.remove(&id);
|
||||
}
|
||||
|
||||
fn sort(&mut self) {
|
||||
self.metadata.sort_unstable_by(|a, b| {
|
||||
a.title
|
||||
.cmp(&b.title)
|
||||
.then_with(|| b.saved_at.cmp(&a.saved_at))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl PromptStore {
|
||||
pub fn global(cx: &AppContext) -> impl Future<Output = Result<Arc<Self>>> {
|
||||
let store = GlobalPromptStore::global(cx).0.clone();
|
||||
async move { store.await.map_err(|err| anyhow!(err)) }
|
||||
}
|
||||
|
||||
pub fn new(db_path: PathBuf, executor: BackgroundExecutor) -> Task<Result<Self>> {
|
||||
executor.spawn({
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
|
||||
let db_env = unsafe {
|
||||
heed::EnvOpenOptions::new()
|
||||
.map_size(1024 * 1024 * 1024) // 1GB
|
||||
.max_dbs(4) // Metadata and bodies (possibly v1 of both as well)
|
||||
.open(db_path)?
|
||||
};
|
||||
|
||||
let mut txn = db_env.write_txn()?;
|
||||
let metadata = db_env.create_database(&mut txn, Some("metadata.v2"))?;
|
||||
let bodies = db_env.create_database(&mut txn, Some("bodies.v2"))?;
|
||||
|
||||
// Remove edit workflow prompt, as we decided to opt into it using
|
||||
// a slash command instead.
|
||||
metadata.delete(&mut txn, &PromptId::EditWorkflow).ok();
|
||||
bodies.delete(&mut txn, &PromptId::EditWorkflow).ok();
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
Self::upgrade_dbs(&db_env, metadata, bodies).log_err();
|
||||
|
||||
let txn = db_env.read_txn()?;
|
||||
let metadata_cache = MetadataCache::from_db(metadata, &txn)?;
|
||||
txn.commit()?;
|
||||
|
||||
Ok(PromptStore {
|
||||
executor,
|
||||
env: db_env,
|
||||
metadata_cache: RwLock::new(metadata_cache),
|
||||
metadata,
|
||||
bodies,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn upgrade_dbs(
|
||||
env: &heed::Env,
|
||||
metadata_db: heed::Database<SerdeJson<PromptId>, SerdeJson<PromptMetadata>>,
|
||||
bodies_db: heed::Database<SerdeJson<PromptId>, Str>,
|
||||
) -> Result<()> {
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
pub struct PromptIdV1(Uuid);
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PromptMetadataV1 {
|
||||
pub id: PromptIdV1,
|
||||
pub title: Option<SharedString>,
|
||||
pub default: bool,
|
||||
pub saved_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
let mut txn = env.write_txn()?;
|
||||
let Some(bodies_v1_db) = env
|
||||
.open_database::<SerdeBincode<PromptIdV1>, SerdeBincode<String>>(
|
||||
&txn,
|
||||
Some("bodies"),
|
||||
)?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut bodies_v1 = bodies_v1_db
|
||||
.iter(&txn)?
|
||||
.collect::<heed::Result<HashMap<_, _>>>()?;
|
||||
|
||||
let Some(metadata_v1_db) = env
|
||||
.open_database::<SerdeBincode<PromptIdV1>, SerdeBincode<PromptMetadataV1>>(
|
||||
&txn,
|
||||
Some("metadata"),
|
||||
)?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let metadata_v1 = metadata_v1_db
|
||||
.iter(&txn)?
|
||||
.collect::<heed::Result<HashMap<_, _>>>()?;
|
||||
|
||||
for (prompt_id_v1, metadata_v1) in metadata_v1 {
|
||||
let prompt_id_v2 = PromptId::User {
|
||||
uuid: prompt_id_v1.0,
|
||||
};
|
||||
let Some(body_v1) = bodies_v1.remove(&prompt_id_v1) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if metadata_db
|
||||
.get(&txn, &prompt_id_v2)?
|
||||
.map_or(true, |metadata_v2| {
|
||||
metadata_v1.saved_at > metadata_v2.saved_at
|
||||
})
|
||||
{
|
||||
metadata_db.put(
|
||||
&mut txn,
|
||||
&prompt_id_v2,
|
||||
&PromptMetadata {
|
||||
id: prompt_id_v2,
|
||||
title: metadata_v1.title.clone(),
|
||||
default: metadata_v1.default,
|
||||
saved_at: metadata_v1.saved_at,
|
||||
},
|
||||
)?;
|
||||
bodies_db.put(&mut txn, &prompt_id_v2, &body_v1)?;
|
||||
}
|
||||
}
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(&self, id: PromptId) -> Task<Result<String>> {
|
||||
let env = self.env.clone();
|
||||
let bodies = self.bodies;
|
||||
self.executor.spawn(async move {
|
||||
let txn = env.read_txn()?;
|
||||
let mut prompt = bodies
|
||||
.get(&txn, &id)?
|
||||
.ok_or_else(|| anyhow!("prompt not found"))?
|
||||
.into();
|
||||
LineEnding::normalize(&mut prompt);
|
||||
Ok(prompt)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default_prompt_metadata(&self) -> Vec<PromptMetadata> {
|
||||
return self
|
||||
.metadata_cache
|
||||
.read()
|
||||
.metadata
|
||||
.iter()
|
||||
.filter(|metadata| metadata.default)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
pub fn delete(&self, id: PromptId) -> Task<Result<()>> {
|
||||
self.metadata_cache.write().remove(id);
|
||||
|
||||
let db_connection = self.env.clone();
|
||||
let bodies = self.bodies;
|
||||
let metadata = self.metadata;
|
||||
|
||||
self.executor.spawn(async move {
|
||||
let mut txn = db_connection.write_txn()?;
|
||||
|
||||
metadata.delete(&mut txn, &id)?;
|
||||
bodies.delete(&mut txn, &id)?;
|
||||
|
||||
txn.commit()?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the number of prompts in the store.
|
||||
fn prompt_count(&self) -> usize {
|
||||
self.metadata_cache.read().metadata.len()
|
||||
}
|
||||
|
||||
fn metadata(&self, id: PromptId) -> Option<PromptMetadata> {
|
||||
self.metadata_cache.read().metadata_by_id.get(&id).cloned()
|
||||
}
|
||||
|
||||
pub fn id_for_title(&self, title: &str) -> Option<PromptId> {
|
||||
let metadata_cache = self.metadata_cache.read();
|
||||
let metadata = metadata_cache
|
||||
.metadata
|
||||
.iter()
|
||||
.find(|metadata| metadata.title.as_ref().map(|title| &***title) == Some(title))?;
|
||||
Some(metadata.id)
|
||||
}
|
||||
|
||||
pub fn search(&self, query: String) -> Task<Vec<PromptMetadata>> {
|
||||
let cached_metadata = self.metadata_cache.read().metadata.clone();
|
||||
let executor = self.executor.clone();
|
||||
self.executor.spawn(async move {
|
||||
let mut matches = if query.is_empty() {
|
||||
cached_metadata
|
||||
} else {
|
||||
let candidates = cached_metadata
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(ix, metadata)| {
|
||||
Some(StringMatchCandidate::new(ix, metadata.title.as_ref()?))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let matches = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
false,
|
||||
100,
|
||||
&AtomicBool::default(),
|
||||
executor,
|
||||
)
|
||||
.await;
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|mat| cached_metadata[mat.candidate_id].clone())
|
||||
.collect()
|
||||
};
|
||||
matches.sort_by_key(|metadata| Reverse(metadata.default));
|
||||
matches
|
||||
})
|
||||
}
|
||||
|
||||
fn save(
|
||||
&self,
|
||||
id: PromptId,
|
||||
title: Option<SharedString>,
|
||||
default: bool,
|
||||
body: Rope,
|
||||
) -> Task<Result<()>> {
|
||||
if id.is_built_in() {
|
||||
return Task::ready(Err(anyhow!("built-in prompts cannot be saved")));
|
||||
}
|
||||
|
||||
let prompt_metadata = PromptMetadata {
|
||||
id,
|
||||
title,
|
||||
default,
|
||||
saved_at: Utc::now(),
|
||||
};
|
||||
self.metadata_cache.write().insert(prompt_metadata.clone());
|
||||
|
||||
let db_connection = self.env.clone();
|
||||
let bodies = self.bodies;
|
||||
let metadata = self.metadata;
|
||||
|
||||
self.executor.spawn(async move {
|
||||
let mut txn = db_connection.write_txn()?;
|
||||
|
||||
metadata.put(&mut txn, &id, &prompt_metadata)?;
|
||||
bodies.put(&mut txn, &id, &body.to_string())?;
|
||||
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn save_metadata(
|
||||
&self,
|
||||
id: PromptId,
|
||||
mut title: Option<SharedString>,
|
||||
default: bool,
|
||||
) -> Task<Result<()>> {
|
||||
let mut cache = self.metadata_cache.write();
|
||||
|
||||
if id.is_built_in() {
|
||||
title = cache
|
||||
.metadata_by_id
|
||||
.get(&id)
|
||||
.and_then(|metadata| metadata.title.clone());
|
||||
}
|
||||
|
||||
let prompt_metadata = PromptMetadata {
|
||||
id,
|
||||
title,
|
||||
default,
|
||||
saved_at: Utc::now(),
|
||||
};
|
||||
|
||||
cache.insert(prompt_metadata.clone());
|
||||
|
||||
let db_connection = self.env.clone();
|
||||
let metadata = self.metadata;
|
||||
|
||||
self.executor.spawn(async move {
|
||||
let mut txn = db_connection.write_txn()?;
|
||||
metadata.put(&mut txn, &id, &prompt_metadata)?;
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn first(&self) -> Option<PromptMetadata> {
|
||||
self.metadata_cache.read().metadata.first().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a shared future to a prompt store so it can be assigned as a context global.
|
||||
pub struct GlobalPromptStore(
|
||||
Shared<BoxFuture<'static, Result<Arc<PromptStore>, Arc<anyhow::Error>>>>,
|
||||
);
|
||||
|
||||
impl Global for GlobalPromptStore {}
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use assets::Assets;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, AssetSource};
|
||||
use gpui::AssetSource;
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use language::{BufferSnapshot, LanguageName, Point};
|
||||
use parking_lot::Mutex;
|
||||
@@ -56,19 +56,6 @@ pub struct PromptBuilder {
|
||||
}
|
||||
|
||||
impl PromptBuilder {
|
||||
pub fn load(fs: Arc<dyn Fs>, stdout_is_a_pty: bool, cx: &mut AppContext) -> Arc<Self> {
|
||||
Self::new(Some(PromptLoadingParams {
|
||||
fs: fs.clone(),
|
||||
repo_path: stdout_is_a_pty
|
||||
.then(|| std::env::current_dir().log_err())
|
||||
.flatten(),
|
||||
cx,
|
||||
}))
|
||||
.log_err()
|
||||
.map(Arc::new)
|
||||
.unwrap_or_else(|| Arc::new(Self::new(None).unwrap()))
|
||||
}
|
||||
|
||||
pub fn new(loading_params: Option<PromptLoadingParams>) -> Result<Self> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
Self::register_built_in_templates(&mut handlebars)?;
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::context_editor::ContextEditor;
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
use anyhow::Result;
|
||||
pub use assistant_slash_command::SlashCommand;
|
||||
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
|
||||
use assistant_slash_command::AfterCompletion;
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
|
||||
use editor::{CompletionProvider, Editor};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
|
||||
use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
|
||||
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
|
||||
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
|
||||
use parking_lot::Mutex;
|
||||
use project::CompletionIntent;
|
||||
use rope::Point;
|
||||
@@ -18,15 +19,41 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use ui::ActiveTheme;
|
||||
use workspace::Workspace;
|
||||
pub mod auto_command;
|
||||
pub mod cargo_workspace_command;
|
||||
pub mod context_server_command;
|
||||
pub mod default_command;
|
||||
pub mod delta_command;
|
||||
pub mod diagnostics_command;
|
||||
pub mod docs_command;
|
||||
pub mod fetch_command;
|
||||
pub mod file_command;
|
||||
pub mod now_command;
|
||||
pub mod project_command;
|
||||
pub mod prompt_command;
|
||||
pub mod search_command;
|
||||
pub mod selection_command;
|
||||
pub mod streaming_example_command;
|
||||
pub mod symbols_command;
|
||||
pub mod tab_command;
|
||||
pub mod terminal_command;
|
||||
|
||||
pub struct SlashCommandCompletionProvider {
|
||||
pub(crate) struct SlashCommandCompletionProvider {
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
}
|
||||
|
||||
pub(crate) struct SlashCommandLine {
|
||||
/// The range within the line containing the command name.
|
||||
pub name: Range<usize>,
|
||||
/// Ranges within the line containing the command arguments.
|
||||
pub arguments: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
@@ -328,3 +355,73 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommandLine {
|
||||
pub(crate) fn parse(line: &str) -> Option<Self> {
|
||||
let mut call: Option<Self> = None;
|
||||
let mut ix = 0;
|
||||
for c in line.chars() {
|
||||
let next_ix = ix + c.len_utf8();
|
||||
if let Some(call) = &mut call {
|
||||
// The command arguments start at the first non-whitespace character
|
||||
// after the command name, and continue until the end of the line.
|
||||
if let Some(argument) = call.arguments.last_mut() {
|
||||
if c.is_whitespace() {
|
||||
if (*argument).is_empty() {
|
||||
argument.start = next_ix;
|
||||
argument.end = next_ix;
|
||||
} else {
|
||||
argument.end = ix;
|
||||
call.arguments.push(next_ix..next_ix);
|
||||
}
|
||||
} else {
|
||||
argument.end = next_ix;
|
||||
}
|
||||
}
|
||||
// The command name ends at the first whitespace character.
|
||||
else if !call.name.is_empty() {
|
||||
if c.is_whitespace() {
|
||||
call.arguments = vec![next_ix..next_ix];
|
||||
} else {
|
||||
call.name.end = next_ix;
|
||||
}
|
||||
}
|
||||
// The command name must begin with a letter.
|
||||
else if c.is_alphabetic() {
|
||||
call.name.end = next_ix;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Commands start with a slash.
|
||||
else if c == '/' {
|
||||
call = Some(SlashCommandLine {
|
||||
name: next_ix..next_ix,
|
||||
arguments: Vec::new(),
|
||||
});
|
||||
}
|
||||
// The line can't contain anything before the slash except for whitespace.
|
||||
else if !c.is_whitespace() {
|
||||
return None;
|
||||
}
|
||||
ix = next_ix;
|
||||
}
|
||||
call
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_label_for_command(
|
||||
command_name: &str,
|
||||
arguments: &[&str],
|
||||
cx: &AppContext,
|
||||
) -> CodeLabel {
|
||||
let mut label = CodeLabel::default();
|
||||
label.push_str(command_name, None);
|
||||
label.push_str(" ", None);
|
||||
label.push_str(
|
||||
&arguments.join(" "),
|
||||
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
||||
);
|
||||
label.filter_range = 0..command_name.len();
|
||||
label
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use ui::{prelude::*, BorrowAppContext};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::create_label_for_command;
|
||||
use crate::slash_command::create_label_for_command;
|
||||
|
||||
pub struct AutoSlashCommandFeatureFlag;
|
||||
|
||||
@@ -26,7 +26,7 @@ impl FeatureFlag for AutoSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "auto-slash-command";
|
||||
}
|
||||
|
||||
pub struct AutoCommand;
|
||||
pub(crate) struct AutoCommand;
|
||||
|
||||
impl SlashCommand for AutoCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -15,7 +15,7 @@ use std::{
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct CargoWorkspaceSlashCommand;
|
||||
pub(crate) struct CargoWorkspaceSlashCommand;
|
||||
|
||||
impl CargoWorkspaceSlashCommand {
|
||||
async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {
|
||||
@@ -16,7 +16,7 @@ use text::LineEnding;
|
||||
use ui::{IconName, SharedString};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::create_label_for_command;
|
||||
use crate::slash_command::create_label_for_command;
|
||||
|
||||
pub struct ContextServerSlashCommand {
|
||||
server_manager: Model<ContextServerManager>,
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::prompt_library::PromptStore;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
@@ -5,7 +6,6 @@ use assistant_slash_command::{
|
||||
};
|
||||
use gpui::{Task, WeakView};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use prompt_library::PromptStore;
|
||||
use std::{
|
||||
fmt::Write,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct DefaultSlashCommand;
|
||||
pub(crate) struct DefaultSlashCommand;
|
||||
|
||||
impl SlashCommand for DefaultSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::file_command::{FileCommandMetadata, FileSlashCommand};
|
||||
use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
@@ -13,7 +13,7 @@ use text::OffsetRangeExt;
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct DeltaSlashCommand;
|
||||
pub(crate) struct DeltaSlashCommand;
|
||||
|
||||
impl SlashCommand for DeltaSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -21,9 +21,9 @@ use util::paths::PathMatcher;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::create_label_for_command;
|
||||
use crate::slash_command::create_label_for_command;
|
||||
|
||||
pub struct DiagnosticsSlashCommand;
|
||||
pub(crate) struct DiagnosticsSlashCommand;
|
||||
|
||||
impl DiagnosticsSlashCommand {
|
||||
fn search_paths(
|
||||
@@ -19,7 +19,7 @@ use ui::prelude::*;
|
||||
use util::{maybe, ResultExt};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct DocsSlashCommand;
|
||||
pub(crate) struct DocsSlashCommand;
|
||||
|
||||
impl DocsSlashCommand {
|
||||
pub const NAME: &'static str = "docs";
|
||||
@@ -367,7 +367,7 @@ fn is_item_path_delimiter(char: char) -> bool {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum DocsSlashCommandArgs {
|
||||
pub(crate) enum DocsSlashCommandArgs {
|
||||
NoProvider,
|
||||
SearchPackageDocs {
|
||||
provider: ProviderId,
|
||||
@@ -23,7 +23,7 @@ enum ContentType {
|
||||
Json,
|
||||
}
|
||||
|
||||
pub struct FetchSlashCommand;
|
||||
pub(crate) struct FetchSlashCommand;
|
||||
|
||||
impl FetchSlashCommand {
|
||||
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
|
||||
@@ -21,7 +21,7 @@ use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct FileSlashCommand;
|
||||
pub(crate) struct FileSlashCommand;
|
||||
|
||||
impl FileSlashCommand {
|
||||
fn search_paths(
|
||||
@@ -561,7 +561,7 @@ mod test {
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
|
||||
use super::collect_files;
|
||||
use crate::slash_command::file_command::collect_files;
|
||||
|
||||
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
@@ -12,7 +12,7 @@ use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct NowSlashCommand;
|
||||
pub(crate) struct NowSlashCommand;
|
||||
|
||||
impl SlashCommand for NowSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -1,26 +1,17 @@
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
ops::DerefMut,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
use super::{
|
||||
create_label_for_command, search_command::add_search_result_section, SlashCommand,
|
||||
SlashCommandOutput,
|
||||
};
|
||||
|
||||
use crate::PromptBuilder;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
};
|
||||
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
|
||||
use feature_flags::FeatureFlag;
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use language::{Anchor, CodeLabel, LspAdapterDelegate};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelTool};
|
||||
use prompt_library::PromptBuilder;
|
||||
use schemars::JsonSchema;
|
||||
use semantic_index::SemanticDb;
|
||||
use serde::Deserialize;
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
use super::{create_label_for_command, search_command::add_search_result_section};
|
||||
|
||||
pub struct ProjectSlashCommandFeatureFlag;
|
||||
|
||||
@@ -28,6 +19,15 @@ impl FeatureFlag for ProjectSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "project-slash-command";
|
||||
}
|
||||
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
ops::DerefMut,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct ProjectSlashCommand {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::prompt_library::PromptStore;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
@@ -5,12 +6,11 @@ use assistant_slash_command::{
|
||||
};
|
||||
use gpui::{Task, WeakView};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use prompt_library::PromptStore;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct PromptSlashCommand;
|
||||
pub(crate) struct PromptSlashCommand;
|
||||
|
||||
impl SlashCommand for PromptSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -14,10 +14,10 @@ use std::{
|
||||
use ui::{prelude::*, IconName};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::create_label_for_command;
|
||||
use crate::file_command::{build_entry_output_section, codeblock_fence_for_path};
|
||||
use crate::slash_command::create_label_for_command;
|
||||
use crate::slash_command::file_command::{build_entry_output_section, codeblock_fence_for_path};
|
||||
|
||||
pub struct SearchSlashCommandFeatureFlag;
|
||||
pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||
|
||||
impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "search-slash-command";
|
||||
@@ -27,7 +27,7 @@ impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SearchSlashCommand;
|
||||
pub(crate) struct SearchSlashCommand;
|
||||
|
||||
impl SlashCommand for SearchSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
98
crates/assistant/src/slash_command/selection_command.rs
Normal file
98
crates/assistant/src/slash_command/selection_command.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::assistant_panel::selections_creases;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
|
||||
SlashCommandOutputSection, SlashCommandResult,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use ui::{IconName, SharedString, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SelectionCommand;
|
||||
|
||||
impl SlashCommand for SelectionCommand {
|
||||
fn name(&self) -> String {
|
||||
"selection".into()
|
||||
}
|
||||
|
||||
fn label(&self, _cx: &AppContext) -> CodeLabel {
|
||||
CodeLabel::plain(self.name(), None)
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Insert editor selection".into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Quote
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.description()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn accepts_arguments(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let mut events = vec![];
|
||||
|
||||
let Some(creases) = workspace
|
||||
.update(cx, selections_creases)
|
||||
.unwrap_or_else(|e| {
|
||||
events.push(Err(e));
|
||||
None
|
||||
})
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no active selection")));
|
||||
};
|
||||
|
||||
for (text, title) in creases {
|
||||
events.push(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::TextSnippet,
|
||||
label: SharedString::from(title),
|
||||
metadata: None,
|
||||
}));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text,
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
events.push(Ok(SlashCommandEvent::EndSection));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
}
|
||||
|
||||
let result = futures::stream::iter(events).boxed();
|
||||
|
||||
Task::ready(Ok(result))
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ impl FeatureFlag for StreamingExampleSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "streaming-example-slash-command";
|
||||
}
|
||||
|
||||
pub struct StreamingExampleSlashCommand;
|
||||
pub(crate) struct StreamingExampleSlashCommand;
|
||||
|
||||
impl SlashCommand for StreamingExampleSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -11,7 +11,7 @@ use std::{path::Path, sync::atomic::AtomicBool};
|
||||
use ui::{IconName, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct OutlineSlashCommand;
|
||||
pub(crate) struct OutlineSlashCommand;
|
||||
|
||||
impl SlashCommand for OutlineSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
@@ -16,9 +16,9 @@ use ui::{prelude::*, ActiveTheme, WindowContext};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::file_command::append_buffer_to_output;
|
||||
use crate::slash_command::file_command::append_buffer_to_output;
|
||||
|
||||
pub struct TabSlashCommand;
|
||||
pub(crate) struct TabSlashCommand;
|
||||
|
||||
const ALL_TABS_COMPLETION_ITEM: &str = "all";
|
||||
|
||||
@@ -12,14 +12,14 @@ use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use ui::prelude::*;
|
||||
use workspace::{dock::Panel, Workspace};
|
||||
|
||||
use crate::DEFAULT_CONTEXT_LINES;
|
||||
|
||||
use super::create_label_for_command;
|
||||
|
||||
pub struct TerminalSlashCommand;
|
||||
pub(crate) struct TerminalSlashCommand;
|
||||
|
||||
const LINE_COUNT_ARG: &str = "--line-count";
|
||||
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
|
||||
impl SlashCommand for TerminalSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"terminal".into()
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
|
||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
|
||||
|
||||
use crate::context_editor::ContextEditor;
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandRegistry};
|
||||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{SlashCommand, SlashCommandRegistry};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct SlashCommandId(usize);
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::{AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::{
|
||||
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType,
|
||||
DEFAULT_CONTEXT_LINES,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_context_editor::{humanize_token_count, RequestType};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{
|
||||
@@ -20,7 +22,6 @@ use language_model::{
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_models::report_assistant_event;
|
||||
use prompt_library::PromptBuilder;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::{
|
||||
cmp,
|
||||
@@ -740,7 +741,7 @@ impl PromptEditor {
|
||||
}
|
||||
|
||||
fn placeholder_text(cx: &WindowContext) -> String {
|
||||
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, cx)
|
||||
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
|
||||
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||
.unwrap_or_default();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "assistant2"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
@@ -12,36 +12,27 @@ workspace = true
|
||||
path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"gpui/test-support",
|
||||
"language/test-support",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anthropic = { workspace = true, features = ["schemars"] }
|
||||
anyhow.workspace = true
|
||||
assistant_context_editor.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assets.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
context_server.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
handlebars.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
@@ -51,22 +42,26 @@ lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
ordered-float.workspace = true
|
||||
paths.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_library.workspace = true
|
||||
proto.workspace = true
|
||||
rope.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
similar.workspace = true
|
||||
smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
terminal.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
|
||||
@@ -76,16 +76,6 @@ impl ActiveThread {
|
||||
self.thread.read(cx).summary()
|
||||
}
|
||||
|
||||
pub fn summary_or_default(&self, cx: &AppContext) -> SharedString {
|
||||
self.thread.read(cx).summary_or_default()
|
||||
}
|
||||
|
||||
pub fn cancel_last_completion(&mut self, cx: &mut AppContext) -> bool {
|
||||
self.last_error.take();
|
||||
self.thread
|
||||
.update(cx, |thread, _cx| thread.cancel_last_completion())
|
||||
}
|
||||
|
||||
pub fn last_error(&self) -> Option<ThreadError> {
|
||||
self.last_error.clone()
|
||||
}
|
||||
@@ -118,10 +108,10 @@ impl ActiveThread {
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
code_block: StyleRefinement {
|
||||
margin: EdgesRefinement {
|
||||
top: Some(Length::Definite(rems(0.).into())),
|
||||
top: Some(Length::Definite(rems(1.0).into())),
|
||||
left: Some(Length::Definite(rems(0.).into())),
|
||||
right: Some(Length::Definite(rems(0.).into())),
|
||||
bottom: Some(Length::Definite(rems(0.5).into())),
|
||||
bottom: Some(Length::Definite(rems(1.).into())),
|
||||
},
|
||||
padding: EdgesRefinement {
|
||||
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
@@ -129,10 +119,10 @@ impl ActiveThread {
|
||||
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
},
|
||||
background: Some(colors.editor_background.into()),
|
||||
border_color: Some(colors.border_variant),
|
||||
background: Some(colors.editor_foreground.opacity(0.01).into()),
|
||||
border_color: Some(colors.border_variant.opacity(0.3)),
|
||||
border_widths: EdgesRefinement {
|
||||
top: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
top: Some(AbsoluteLength::Pixels(Pixels(1.0))),
|
||||
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
@@ -251,78 +241,63 @@ impl ActiveThread {
|
||||
let context = self.thread.read(cx).context_for_message(message_id);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let message_content = v_flex()
|
||||
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
if !context.is_empty() {
|
||||
parent.child(
|
||||
h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
|
||||
context
|
||||
.into_iter()
|
||||
.map(|context| ContextPill::added(context, false, false, None)),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
});
|
||||
|
||||
let styled_message = match message.role {
|
||||
Role::User => v_flex()
|
||||
.id(("message-container", ix))
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.shadow_sm()
|
||||
.child(
|
||||
h_flex()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.bg(colors.editor_foreground.opacity(0.05))
|
||||
.border_b_1()
|
||||
.border_color(colors.border)
|
||||
.justify_between()
|
||||
.rounded_t(px(6.))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::PersonCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Label::new("You")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(message_content),
|
||||
),
|
||||
Role::Assistant => div().id(("message-container", ix)).child(message_content),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
.rounded_md()
|
||||
.child(message_content),
|
||||
),
|
||||
let (role_icon, role_name, role_color) = match message.role {
|
||||
Role::User => (IconName::Person, "You", Color::Muted),
|
||||
Role::Assistant => (IconName::ZedAssistant, "Assistant", Color::Accent),
|
||||
Role::System => (IconName::Settings, "System", Color::Default),
|
||||
};
|
||||
|
||||
styled_message.into_any()
|
||||
div()
|
||||
.id(("message-container", ix))
|
||||
.py_1()
|
||||
.px_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(colors.border_variant)
|
||||
.bg(colors.editor_background)
|
||||
.rounded_md()
|
||||
.child(
|
||||
h_flex()
|
||||
.py_1p5()
|
||||
.px_2p5()
|
||||
.border_b_1()
|
||||
.border_color(colors.border_variant)
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(role_icon)
|
||||
.size(IconSize::XSmall)
|
||||
.color(role_color),
|
||||
)
|
||||
.child(
|
||||
Label::new(role_name)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(role_color),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
if !context.is_empty() {
|
||||
parent.child(h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
|
||||
context.iter().map(|context| {
|
||||
ContextPill::new_added(context.clone(), false, None)
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ActiveThread {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.pt_1p5()
|
||||
.child(list(self.list_state.clone()).flex_grow())
|
||||
list(self.list_state.clone()).flex_1().py_1()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod active_thread;
|
||||
mod assistant_configuration;
|
||||
mod assistant_model_selector;
|
||||
mod assistant_panel;
|
||||
mod assistant_settings;
|
||||
mod buffer_codegen;
|
||||
mod context;
|
||||
mod context_picker;
|
||||
@@ -10,6 +10,8 @@ mod context_strip;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod message_editor;
|
||||
mod prompts;
|
||||
mod streaming_diff;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
@@ -19,55 +21,51 @@ mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::Client;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::Fs;
|
||||
use gpui::{actions, AppContext};
|
||||
use prompt_library::PromptBuilder;
|
||||
use prompts::PromptLoadingParams;
|
||||
use settings::Settings as _;
|
||||
use util::ResultExt;
|
||||
|
||||
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::assistant_panel::AssistantPanel;
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
|
||||
actions!(
|
||||
assistant2,
|
||||
[
|
||||
ToggleFocus,
|
||||
NewThread,
|
||||
NewPromptEditor,
|
||||
ToggleContextPicker,
|
||||
ToggleModelSelector,
|
||||
RemoveAllContext,
|
||||
OpenHistory,
|
||||
OpenPromptEditorHistory,
|
||||
OpenConfiguration,
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
ChatMode,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
FocusUp,
|
||||
FocusDown,
|
||||
FocusLeft,
|
||||
FocusRight,
|
||||
RemoveFocusedContext,
|
||||
AcceptSuggestedContext
|
||||
CyclePreviousInlineAssist
|
||||
]
|
||||
);
|
||||
|
||||
const NAMESPACE: &str = "assistant2";
|
||||
|
||||
/// Initializes the `assistant2` crate.
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, stdout_is_a_pty: bool, cx: &mut AppContext) {
|
||||
AssistantSettings::register(cx);
|
||||
assistant_panel::init(cx);
|
||||
|
||||
let prompt_builder = prompts::PromptBuilder::new(Some(PromptLoadingParams {
|
||||
fs: fs.clone(),
|
||||
repo_path: stdout_is_a_pty
|
||||
.then(|| std::env::current_dir().log_err())
|
||||
.flatten(),
|
||||
cx,
|
||||
}))
|
||||
.log_err()
|
||||
.map(Arc::new)
|
||||
.unwrap_or_else(|| Arc::new(prompts::PromptBuilder::new(None).unwrap()));
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{Action, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use ui::{prelude::*, ElevationIndex};
|
||||
use zed_actions::assistant::DeployPromptLibrary;
|
||||
|
||||
pub struct AssistantConfiguration {
|
||||
focus_handle: FocusHandle,
|
||||
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
||||
_registry_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl AssistantConfiguration {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let registry_subscription = cx.subscribe(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|this, _, event: &language_model::Event, cx| match event {
|
||||
language_model::Event::AddedProvider(provider_id) => {
|
||||
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
||||
if let Some(provider) = provider {
|
||||
this.add_provider_configuration_view(&provider, cx);
|
||||
}
|
||||
}
|
||||
language_model::Event::RemovedProvider(provider_id) => {
|
||||
this.remove_provider_configuration_view(provider_id);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
);
|
||||
|
||||
let mut this = Self {
|
||||
focus_handle,
|
||||
configuration_views_by_provider: HashMap::default(),
|
||||
_registry_subscription: registry_subscription,
|
||||
};
|
||||
this.build_provider_configuration_views(cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn build_provider_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
for provider in providers {
|
||||
self.add_provider_configuration_view(&provider, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
|
||||
self.configuration_views_by_provider.remove(provider_id);
|
||||
}
|
||||
|
||||
fn add_provider_configuration_view(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let configuration_view = provider.configuration_view(cx);
|
||||
self.configuration_views_by_provider
|
||||
.insert(provider.id(), configuration_view);
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for AssistantConfiguration {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AssistantConfigurationEvent {
|
||||
NewThread(Arc<dyn LanguageModelProvider>),
|
||||
}
|
||||
|
||||
impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
|
||||
|
||||
impl AssistantConfiguration {
|
||||
fn render_provider_configuration(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let provider_id = provider.id().0.clone();
|
||||
let provider_name = provider.name().0.clone();
|
||||
let configuration_view = self
|
||||
.configuration_views_by_provider
|
||||
.get(&provider.id())
|
||||
.cloned();
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
|
||||
.when(provider.is_authenticated(cx), |parent| {
|
||||
parent.child(
|
||||
h_flex().justify_end().child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Open New Thread",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.p(DynamicSpacing::Base08.rems(cx))
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.map(|parent| match configuration_view {
|
||||
Some(configuration_view) => parent.child(configuration_view),
|
||||
None => parent.child(div().child(Label::new(format!(
|
||||
"No configuration view for {provider_name}",
|
||||
)))),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantConfiguration {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
|
||||
v_flex()
|
||||
.id("assistant-configuration")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
h_flex().p(DynamicSpacing::Base16.rems(cx)).child(
|
||||
Button::new("open-prompt-library", "Open Prompt Library")
|
||||
.style(ButtonStyle::Filled)
|
||||
.full_width()
|
||||
.icon(IconName::Book)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, cx| {
|
||||
cx.dispatch_action(DeployPromptLibrary.boxed_clone())
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.mt_1()
|
||||
.gap_6()
|
||||
.flex_1()
|
||||
.children(
|
||||
providers
|
||||
.into_iter()
|
||||
.map(|provider| self.render_provider_configuration(&provider, cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
use assistant_settings::AssistantSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{FocusHandle, View};
|
||||
use gpui::View;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
||||
|
||||
use crate::ToggleModelSelector;
|
||||
use crate::{assistant_settings::AssistantSettings, ToggleModelSelector};
|
||||
|
||||
pub struct AssistantModelSelector {
|
||||
selector: View<LanguageModelSelector>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl AssistantModelSelector {
|
||||
pub(crate) fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -37,7 +34,6 @@ impl AssistantModelSelector {
|
||||
)
|
||||
}),
|
||||
menu_handle,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +41,7 @@ impl AssistantModelSelector {
|
||||
impl Render for AssistantModelSelector {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let focus_handle = self.selector.focus_handle(cx).clone();
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
@@ -53,6 +49,7 @@ impl Render for AssistantModelSelector {
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
div()
|
||||
|
||||
@@ -1,49 +1,36 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::{
|
||||
make_lsp_adapter_delegate, AssistantPanelDelegate, ContextEditor, ContextHistory,
|
||||
SlashCommandCompletionProvider,
|
||||
};
|
||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::zed_urls;
|
||||
use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, Corner, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Model, Pixels, Subscription, Task, UpdateGlobal, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Model, Pixels, Task, View, ViewContext, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use project::Project;
|
||||
use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
|
||||
use settings::{update_settings_file, Settings};
|
||||
use settings::Settings;
|
||||
use time::UtcOffset;
|
||||
use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip};
|
||||
use util::ResultExt as _;
|
||||
use ui::{prelude::*, KeyBinding, Tab, Tooltip};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
use zed_actions::assistant::{DeployPromptLibrary, ToggleFocus};
|
||||
|
||||
use crate::active_thread::ActiveThread;
|
||||
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
||||
use crate::assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{Thread, ThreadError, ThreadId};
|
||||
use crate::thread_history::{PastThread, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{
|
||||
InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory,
|
||||
OpenPromptEditorHistory,
|
||||
};
|
||||
use crate::{NewThread, OpenHistory, ToggleFocus};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace
|
||||
.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
})
|
||||
.register_action(|workspace, _: &NewThread, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| panel.new_thread(cx));
|
||||
@@ -55,24 +42,6 @@ pub fn init(cx: &mut AppContext) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_history(cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &NewPromptEditor, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenPromptEditorHistory, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenConfiguration, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_configuration(cx));
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
@@ -81,31 +50,20 @@ pub fn init(cx: &mut AppContext) {
|
||||
|
||||
enum ActiveView {
|
||||
Thread,
|
||||
PromptEditor,
|
||||
History,
|
||||
PromptEditorHistory,
|
||||
Configuration,
|
||||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: View<ActiveThread>,
|
||||
message_editor: View<MessageEditor>,
|
||||
context_store: Model<assistant_context_editor::ContextStore>,
|
||||
context_editor: Option<View<ContextEditor>>,
|
||||
context_history: Option<View<ContextHistory>>,
|
||||
configuration: Option<View<AssistantConfiguration>>,
|
||||
configuration_subscription: Option<Subscription>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
local_timezone: UtcOffset,
|
||||
active_view: ActiveView,
|
||||
history: View<ThreadHistory>,
|
||||
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
width: Option<Pixels>,
|
||||
height: Option<Pixels>,
|
||||
}
|
||||
@@ -113,7 +71,6 @@ pub struct AssistantPanel {
|
||||
impl AssistantPanel {
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
@@ -125,22 +82,8 @@ impl AssistantPanel {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
||||
let context_store = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
assistant_context_editor::ContextStore::new(
|
||||
project,
|
||||
prompt_builder.clone(),
|
||||
slash_commands,
|
||||
tools.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
cx.new_view(|cx| Self::new(workspace, thread_store, context_store, tools, cx))
|
||||
cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -148,75 +91,50 @@ impl AssistantPanel {
|
||||
fn new(
|
||||
workspace: &Workspace,
|
||||
thread_store: Model<ThreadStore>,
|
||||
context_store: Model<assistant_context_editor::ContextStore>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let project = workspace.project().clone();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let language_registry = workspace.project().read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = cx.view().downgrade();
|
||||
|
||||
let message_editor = cx.new_view(|cx| {
|
||||
MessageEditor::new(
|
||||
fs.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.downgrade(),
|
||||
thread.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
Self {
|
||||
active_view: ActiveView::Thread,
|
||||
workspace: workspace.clone(),
|
||||
project,
|
||||
fs: fs.clone(),
|
||||
language_registry: language_registry.clone(),
|
||||
thread_store: thread_store.clone(),
|
||||
thread: cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
workspace,
|
||||
workspace.clone(),
|
||||
language_registry,
|
||||
tools.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
message_editor,
|
||||
context_store,
|
||||
context_editor: None,
|
||||
context_history: None,
|
||||
configuration: None,
|
||||
configuration_subscription: None,
|
||||
message_editor: cx.new_view(|cx| {
|
||||
MessageEditor::new(
|
||||
fs.clone(),
|
||||
workspace,
|
||||
thread_store.downgrade(),
|
||||
thread.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
tools,
|
||||
local_timezone: UtcOffset::from_whole_seconds(
|
||||
chrono::Local::now().offset().local_minus_utc(),
|
||||
)
|
||||
.unwrap(),
|
||||
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
new_item_context_menu_handle: PopoverMenuHandle::default(),
|
||||
open_history_context_menu_handle: PopoverMenuHandle::default(),
|
||||
width: None,
|
||||
height: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_focus(
|
||||
workspace: &mut Workspace,
|
||||
_: &ToggleFocus,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
if !settings.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace.toggle_panel_focus::<Self>(cx);
|
||||
}
|
||||
|
||||
pub(crate) fn local_timezone(&self) -> UtcOffset {
|
||||
self.local_timezone
|
||||
}
|
||||
@@ -225,11 +143,6 @@ impl AssistantPanel {
|
||||
&self.thread_store
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
self.thread
|
||||
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let thread = self
|
||||
.thread_store
|
||||
@@ -257,110 +170,12 @@ impl AssistantPanel {
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
fn new_prompt_editor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.active_view = ActiveView::PromptEditor;
|
||||
|
||||
let context = self
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| context_store.create(cx));
|
||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
self.context_editor = Some(cx.new_view(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
lsp_adapter_delegate,
|
||||
cx,
|
||||
);
|
||||
editor.insert_default_prompt(cx);
|
||||
editor
|
||||
}));
|
||||
|
||||
if let Some(context_editor) = self.context_editor.as_ref() {
|
||||
context_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
|
||||
open_prompt_library(
|
||||
self.language_registry.clone(),
|
||||
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
|
||||
Arc::new(|| {
|
||||
Box::new(SlashCommandCompletionProvider::new(
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
))
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.active_view = ActiveView::History;
|
||||
self.history.focus_handle(cx).focus(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn open_prompt_editor_history(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.active_view = ActiveView::PromptEditorHistory;
|
||||
self.context_history = Some(cx.new_view(|cx| {
|
||||
ContextHistory::new(
|
||||
self.project.clone(),
|
||||
self.context_store.clone(),
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
||||
if let Some(context_history) = self.context_history.as_ref() {
|
||||
context_history.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn open_saved_prompt_editor(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let context = self
|
||||
.context_store
|
||||
.update(cx, |store, cx| store.open_local_context(path.clone(), cx));
|
||||
let fs = self.fs.clone();
|
||||
let project = self.project.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let context = context.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(
|
||||
context,
|
||||
fs,
|
||||
workspace,
|
||||
project,
|
||||
lsp_adapter_delegate,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
this.active_view = ActiveView::PromptEditor;
|
||||
this.context_editor = Some(editor);
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
|
||||
let Some(thread) = self
|
||||
.thread_store
|
||||
@@ -391,46 +206,6 @@ impl AssistantPanel {
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
pub(crate) fn open_configuration(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.active_view = ActiveView::Configuration;
|
||||
self.configuration = Some(cx.new_view(AssistantConfiguration::new));
|
||||
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
self.configuration_subscription =
|
||||
Some(cx.subscribe(configuration, Self::handle_assistant_configuration_event));
|
||||
|
||||
configuration.focus_handle(cx).focus(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_assistant_configuration_event(
|
||||
&mut self,
|
||||
_view: View<AssistantConfiguration>,
|
||||
event: &AssistantConfigurationEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
AssistantConfigurationEvent::NewThread(provider) => {
|
||||
if LanguageModelRegistry::read_global(cx)
|
||||
.active_provider()
|
||||
.map_or(true, |active_provider| {
|
||||
active_provider.id() != provider.id()
|
||||
})
|
||||
{
|
||||
if let Some(model) = provider.provided_models(cx).first().cloned() {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.new_thread(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
|
||||
self.thread.read(cx).thread.clone()
|
||||
}
|
||||
@@ -446,27 +221,6 @@ impl FocusableView for AssistantPanel {
|
||||
match self.active_view {
|
||||
ActiveView::Thread => self.message_editor.focus_handle(cx),
|
||||
ActiveView::History => self.history.focus_handle(cx),
|
||||
ActiveView::PromptEditor => {
|
||||
if let Some(context_editor) = self.context_editor.as_ref() {
|
||||
context_editor.focus_handle(cx)
|
||||
} else {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
ActiveView::PromptEditorHistory => {
|
||||
if let Some(context_history) = self.context_history.as_ref() {
|
||||
context_history.focus_handle(cx)
|
||||
} else {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
ActiveView::Configuration => {
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
configuration.focus_handle(cx)
|
||||
} else {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,12 +232,8 @@ impl Panel for AssistantPanel {
|
||||
"AssistantPanel2"
|
||||
}
|
||||
|
||||
fn position(&self, cx: &WindowContext) -> DockPosition {
|
||||
match AssistantSettings::get_global(cx).dock {
|
||||
AssistantDockPosition::Left => DockPosition::Left,
|
||||
AssistantDockPosition::Bottom => DockPosition::Bottom,
|
||||
AssistantDockPosition::Right => DockPosition::Right,
|
||||
}
|
||||
fn position(&self, _cx: &WindowContext) -> DockPosition {
|
||||
DockPosition::Right
|
||||
}
|
||||
|
||||
fn position_is_valid(&self, _: DockPosition) -> bool {
|
||||
@@ -529,13 +279,8 @@ impl Panel for AssistantPanel {
|
||||
Some(proto::PanelId::AssistantPanel)
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
if !settings.enabled || !settings.button {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(IconName::ZedAssistant)
|
||||
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
|
||||
Some(IconName::ZedAssistant2)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
@@ -553,28 +298,15 @@ impl Panel for AssistantPanel {
|
||||
|
||||
impl AssistantPanel {
|
||||
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let thread = self.thread.read(cx);
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
let title = match self.active_view {
|
||||
ActiveView::Thread => {
|
||||
if thread.is_empty() {
|
||||
thread.summary_or_default(cx)
|
||||
} else {
|
||||
thread
|
||||
.summary(cx)
|
||||
.unwrap_or_else(|| SharedString::from("Loading Summary…"))
|
||||
}
|
||||
}
|
||||
ActiveView::PromptEditor => self
|
||||
.context_editor
|
||||
.as_ref()
|
||||
.map(|context_editor| {
|
||||
SharedString::from(context_editor.read(cx).title(cx).to_string())
|
||||
})
|
||||
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
|
||||
ActiveView::History => "History / Thread".into(),
|
||||
ActiveView::PromptEditorHistory => "History / Prompt Editor".into(),
|
||||
ActiveView::Configuration => "Configuration".into(),
|
||||
let title = if self.thread.read(cx).is_empty() {
|
||||
SharedString::from("New Thread")
|
||||
} else {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.summary(cx)
|
||||
.unwrap_or_else(|| SharedString::from("Loading Summary…"))
|
||||
};
|
||||
|
||||
h_flex()
|
||||
@@ -596,40 +328,41 @@ impl AssistantPanel {
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
||||
.trigger(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("New…", cx)),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
.menu(move |cx| {
|
||||
Some(ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("New Thread", NewThread.boxed_clone())
|
||||
.action("New Prompt Editor", NewPromptEditor.boxed_clone())
|
||||
}))
|
||||
IconButton::new("new-thread", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Thread",
|
||||
&NewThread,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(NewThread.boxed_clone());
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-history-popover-menu")
|
||||
.trigger(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("History…", cx)),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.open_history_context_menu_handle.clone())
|
||||
.menu(move |cx| {
|
||||
Some(ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Thread History", OpenHistory.boxed_clone())
|
||||
.action(
|
||||
"Prompt Editor History",
|
||||
OpenPromptEditorHistory.boxed_clone(),
|
||||
)
|
||||
}))
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Open History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(OpenHistory.boxed_clone());
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -637,8 +370,8 @@ impl AssistantPanel {
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(OpenConfiguration.boxed_clone());
|
||||
.on_click(move |_event, _cx| {
|
||||
println!("Configure Assistant");
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -679,12 +412,13 @@ impl AssistantPanel {
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
recent_threads.into_iter().map(|thread| {
|
||||
// TODO: keyboard navigation
|
||||
PastThread::new(thread, cx.view().downgrade(), false)
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
recent_threads
|
||||
.into_iter()
|
||||
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Button::new("view-all-past-threads", "View All Past Threads")
|
||||
@@ -871,14 +605,12 @@ impl Render for AssistantPanel {
|
||||
.key_context("AssistantPanel2")
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(|this, _: &NewThread, cx| {
|
||||
this.new_thread(cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
|
||||
this.open_history(cx);
|
||||
}))
|
||||
.on_action(cx.listener(Self::deploy_prompt_library))
|
||||
.child(self.render_toolbar(cx))
|
||||
.map(|parent| match self.active_view {
|
||||
ActiveView::Thread => parent
|
||||
@@ -891,83 +623,6 @@ impl Render for AssistantPanel {
|
||||
)
|
||||
.children(self.render_last_error(cx)),
|
||||
ActiveView::History => parent.child(self.history.clone()),
|
||||
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
|
||||
ActiveView::PromptEditorHistory => parent.children(self.context_history.clone()),
|
||||
ActiveView::Configuration => parent.children(self.configuration.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct PromptLibraryInlineAssist {
|
||||
workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl PromptLibraryInlineAssist {
|
||||
pub fn new(workspace: WeakView<Workspace>) -> Self {
|
||||
Self { workspace }
|
||||
}
|
||||
}
|
||||
|
||||
impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
fn assist(
|
||||
&self,
|
||||
prompt_editor: &View<Editor>,
|
||||
_initial_prompt: Option<String>,
|
||||
cx: &mut ViewContext<PromptLibrary>,
|
||||
) {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&prompt_editor, self.workspace.clone(), None, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn focus_assistant_panel(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> bool {
|
||||
workspace.focus_panel::<AssistantPanel>(cx).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConcreteAssistantPanelDelegate;
|
||||
|
||||
impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
fn active_context_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<View<ContextEditor>> {
|
||||
let panel = workspace.panel::<AssistantPanel>(cx)?;
|
||||
panel.update(cx, |panel, _cx| panel.context_editor.clone())
|
||||
}
|
||||
|
||||
fn open_saved_context(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
path: std::path::PathBuf,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return Task::ready(Err(anyhow!("Assistant panel not found")));
|
||||
};
|
||||
|
||||
panel.update(cx, |panel, cx| panel.open_saved_prompt_editor(path, cx))
|
||||
}
|
||||
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_context_id: assistant_context_editor::ContextId,
|
||||
_cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<View<ContextEditor>>> {
|
||||
Task::ready(Err(anyhow!("opening remote context not implemented")))
|
||||
}
|
||||
|
||||
fn quote_selection(
|
||||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_creases: Vec<(String, String)>,
|
||||
_cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
501
crates/assistant2/src/assistant_settings.rs
Normal file
501
crates/assistant2/src/assistant_settings.rs
Normal file
@@ -0,0 +1,501 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::open_ai::Model as OpenAiModel;
|
||||
use anthropic::Model as AnthropicModel;
|
||||
use gpui::Pixels;
|
||||
use language_model::{CloudModel, LanguageModel};
|
||||
use ollama::Model as OllamaModel;
|
||||
use schemars::{schema::Schema, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AssistantDockPosition {
|
||||
Left,
|
||||
#[default]
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[serde(tag = "name", rename_all = "snake_case")]
|
||||
pub enum AssistantProviderContentV1 {
|
||||
#[serde(rename = "zed.dev")]
|
||||
ZedDotDev { default_model: Option<CloudModel> },
|
||||
#[serde(rename = "openai")]
|
||||
OpenAi {
|
||||
default_model: Option<OpenAiModel>,
|
||||
api_url: Option<String>,
|
||||
available_models: Option<Vec<OpenAiModel>>,
|
||||
},
|
||||
#[serde(rename = "anthropic")]
|
||||
Anthropic {
|
||||
default_model: Option<AnthropicModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
#[serde(rename = "ollama")]
|
||||
Ollama {
|
||||
default_model: Option<OllamaModel>,
|
||||
api_url: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AssistantSettings {
|
||||
pub enabled: bool,
|
||||
pub button: bool,
|
||||
pub dock: AssistantDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_model: LanguageModelSelection,
|
||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||
pub using_outdated_settings_version: bool,
|
||||
pub enable_experimental_live_diffs: bool,
|
||||
}
|
||||
|
||||
/// Assistant panel settings
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum AssistantSettingsContent {
|
||||
Versioned(VersionedAssistantSettingsContent),
|
||||
Legacy(LegacyAssistantSettingsContent),
|
||||
}
|
||||
|
||||
impl JsonSchema for AssistantSettingsContent {
|
||||
fn schema_name() -> String {
|
||||
VersionedAssistantSettingsContent::schema_name()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
VersionedAssistantSettingsContent::json_schema(gen)
|
||||
}
|
||||
|
||||
fn is_referenceable() -> bool {
|
||||
VersionedAssistantSettingsContent::is_referenceable()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AssistantSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self::Versioned(VersionedAssistantSettingsContent::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl AssistantSettingsContent {
|
||||
pub fn is_version_outdated(&self) -> bool {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
VersionedAssistantSettingsContent::V1(_) => true,
|
||||
VersionedAssistantSettingsContent::V2(_) => false,
|
||||
},
|
||||
AssistantSettingsContent::Legacy(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> AssistantSettingsContentV2 {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 {
|
||||
enabled: settings.enabled,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
default_height: settings.default_width,
|
||||
default_model: settings
|
||||
.provider
|
||||
.clone()
|
||||
.and_then(|provider| match provider {
|
||||
AssistantProviderContentV1::ZedDotDev { default_model } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "zed.dev".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::OpenAi { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "openai".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::Anthropic { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "anthropic".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
AssistantProviderContentV1::Ollama { default_model, .. } => {
|
||||
default_model.map(|model| LanguageModelSelection {
|
||||
provider: "ollama".to_string(),
|
||||
model: model.id().to_string(),
|
||||
})
|
||||
}
|
||||
}),
|
||||
inline_alternatives: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
},
|
||||
VersionedAssistantSettingsContent::V2(settings) => settings.clone(),
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
default_height: settings.default_height,
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "openai".to_string(),
|
||||
model: settings
|
||||
.default_open_ai_model
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.id()
|
||||
.to_string(),
|
||||
}),
|
||||
inline_alternatives: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dock(&mut self, dock: AssistantDockPosition) {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
VersionedAssistantSettingsContent::V1(settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
VersionedAssistantSettingsContent::V2(settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
|
||||
let model = language_model.id().0.to_string();
|
||||
let provider = language_model.provider_id().0.to_string();
|
||||
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
VersionedAssistantSettingsContent::V1(settings) => match provider.as_ref() {
|
||||
"zed.dev" => {
|
||||
log::warn!("attempted to set zed.dev model on outdated settings");
|
||||
}
|
||||
"anthropic" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Anthropic {
|
||||
default_model: AnthropicModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"ollama" => {
|
||||
let api_url = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
|
||||
api_url.clone()
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::Ollama {
|
||||
default_model: Some(ollama::Model::new(&model, None, None)),
|
||||
api_url,
|
||||
});
|
||||
}
|
||||
"openai" => {
|
||||
let (api_url, available_models) = match &settings.provider {
|
||||
Some(AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
available_models,
|
||||
..
|
||||
}) => (api_url.clone(), available_models.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
settings.provider = Some(AssistantProviderContentV1::OpenAi {
|
||||
default_model: OpenAiModel::from_id(&model).ok(),
|
||||
api_url,
|
||||
available_models,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
VersionedAssistantSettingsContent::V2(settings) => {
|
||||
settings.default_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => {
|
||||
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
|
||||
settings.default_open_ai_model = Some(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(tag = "version")]
|
||||
pub enum VersionedAssistantSettingsContent {
|
||||
#[serde(rename = "1")]
|
||||
V1(AssistantSettingsContentV1),
|
||||
#[serde(rename = "2")]
|
||||
V2(AssistantSettingsContentV2),
|
||||
}
|
||||
|
||||
impl Default for VersionedAssistantSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self::V2(AssistantSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
default_model: None,
|
||||
inline_alternatives: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct AssistantSettingsContentV2 {
|
||||
/// Whether the Assistant is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
enabled: Option<bool>,
|
||||
/// Whether to show the assistant panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
button: Option<bool>,
|
||||
/// Where to dock the assistant.
|
||||
///
|
||||
/// Default: right
|
||||
dock: Option<AssistantDockPosition>,
|
||||
/// Default width in pixels when the assistant is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
default_width: Option<f32>,
|
||||
/// Default height in pixels when the assistant is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The default model to use when creating new chats.
|
||||
default_model: Option<LanguageModelSelection>,
|
||||
/// Additional models with which to generate alternatives when performing inline assists.
|
||||
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||
/// Enable experimental live diffs in the assistant panel.
|
||||
///
|
||||
/// Default: false
|
||||
enable_experimental_live_diffs: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelSelection {
|
||||
#[schemars(schema_with = "providers_schema")]
|
||||
pub provider: String,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"google".into(),
|
||||
"ollama".into(),
|
||||
"openai".into(),
|
||||
"zed.dev".into(),
|
||||
"copilot_chat".into(),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
impl Default for LanguageModelSelection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
provider: "openai".to_string(),
|
||||
model: "gpt-4".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct AssistantSettingsContentV1 {
|
||||
/// Whether the Assistant is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
enabled: Option<bool>,
|
||||
/// Whether to show the assistant panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
button: Option<bool>,
|
||||
/// Where to dock the assistant.
|
||||
///
|
||||
/// Default: right
|
||||
dock: Option<AssistantDockPosition>,
|
||||
/// Default width in pixels when the assistant is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
default_width: Option<f32>,
|
||||
/// Default height in pixels when the assistant is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The provider of the assistant service.
|
||||
///
|
||||
/// This can be "openai", "anthropic", "ollama", "zed.dev"
|
||||
/// each with their respective default models and configurations.
|
||||
provider: Option<AssistantProviderContentV1>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct LegacyAssistantSettingsContent {
|
||||
/// Whether to show the assistant panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
/// Where to dock the assistant.
|
||||
///
|
||||
/// Default: right
|
||||
pub dock: Option<AssistantDockPosition>,
|
||||
/// Default width in pixels when the assistant is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
pub default_width: Option<f32>,
|
||||
/// Default height in pixels when the assistant is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
pub default_height: Option<f32>,
|
||||
/// The default OpenAI model to use when creating new chats.
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
/// OpenAI API base URL to use when creating new chats.
|
||||
///
|
||||
/// Default: https://api.openai.com/v1
|
||||
pub openai_api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings for AssistantSettings {
|
||||
const KEY: Option<&'static str> = Some("assistant");
|
||||
|
||||
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
|
||||
|
||||
type FileContent = AssistantSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::AppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut settings = AssistantSettings::default();
|
||||
|
||||
for value in sources.defaults_and_customizations() {
|
||||
if value.is_version_outdated() {
|
||||
settings.using_outdated_settings_version = true;
|
||||
}
|
||||
|
||||
let value = value.upgrade();
|
||||
merge(&mut settings.enabled, value.enabled);
|
||||
merge(&mut settings.button, value.button);
|
||||
merge(&mut settings.dock, value.dock);
|
||||
merge(
|
||||
&mut settings.default_width,
|
||||
value.default_width.map(Into::into),
|
||||
);
|
||||
merge(
|
||||
&mut settings.default_height,
|
||||
value.default_height.map(Into::into),
|
||||
);
|
||||
merge(&mut settings.default_model, value.default_model);
|
||||
merge(&mut settings.inline_alternatives, value.inline_alternatives);
|
||||
merge(
|
||||
&mut settings.enable_experimental_live_diffs,
|
||||
value.enable_experimental_live_diffs,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
}
|
||||
|
||||
fn merge<T>(target: &mut T, value: Option<T>) {
|
||||
if let Some(value) = value {
|
||||
*target = value;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fs::Fs;
|
||||
use gpui::{ReadGlobal, TestAppContext};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deserialize_assistant_settings_with_version(cx: &mut TestAppContext) {
|
||||
let fs = fs::FakeFs::new(cx.executor().clone());
|
||||
fs.create_dir(paths::settings_file().parent().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.update(|cx| {
|
||||
let test_settings = settings::SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
AssistantSettings::register(cx);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(!AssistantSettings::get_global(cx).using_outdated_settings_version);
|
||||
assert_eq!(
|
||||
AssistantSettings::get_global(cx).default_model,
|
||||
LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
model: "claude-3-5-sonnet".into(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
settings::SettingsStore::global(cx).update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
|settings, _| {
|
||||
*settings = AssistantSettingsContent::Versioned(
|
||||
VersionedAssistantSettingsContent::V2(AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "test-provider".into(),
|
||||
model: "gpt-99".into(),
|
||||
}),
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
}),
|
||||
)
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let raw_settings_value = fs.load(paths::settings_file()).await.unwrap();
|
||||
assert!(raw_settings_value.contains(r#""version": "2""#));
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AssistantSettingsTest {
|
||||
assistant: AssistantSettingsContent,
|
||||
}
|
||||
|
||||
let assistant_settings: AssistantSettingsTest =
|
||||
serde_json_lenient::from_str(&raw_settings_value).unwrap();
|
||||
|
||||
assert!(!assistant_settings.assistant.is_version_outdated());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::context::attach_context_to_message;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use crate::{
|
||||
prompts::PromptBuilder,
|
||||
streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::HashSet;
|
||||
@@ -15,7 +19,6 @@ use language_model::{
|
||||
use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use prompt_library::PromptBuilder;
|
||||
use rope::Rope;
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
@@ -28,7 +31,6 @@ use std::{
|
||||
task::{self, Poll},
|
||||
time::Instant,
|
||||
};
|
||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
|
||||
pub struct BufferCodegen {
|
||||
@@ -419,7 +421,8 @@ impl CodegenAlternative {
|
||||
};
|
||||
|
||||
if let Some(context_store) = &self.context_store {
|
||||
attach_context_to_message(&mut request_message, context_store.read(cx).snapshot(cx));
|
||||
let context = context_store.update(cx, |this, _cx| this.context().clone());
|
||||
attach_context_to_message(&mut request_message, context);
|
||||
}
|
||||
|
||||
request_message.content.push(prompt.into());
|
||||
@@ -1050,7 +1053,7 @@ mod tests {
|
||||
stream::{self},
|
||||
Stream,
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use gpui::{Context, TestAppContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{AppContext, Model, SharedString};
|
||||
use language::Buffer;
|
||||
use gpui::SharedString;
|
||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use text::BufferId;
|
||||
use ui::IconName;
|
||||
use util::post_inc;
|
||||
|
||||
use crate::{context_store::buffer_path_log_err, thread::Thread};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct ContextId(pub(crate) usize);
|
||||
|
||||
@@ -23,18 +14,16 @@ impl ContextId {
|
||||
|
||||
/// Some context attached to a message in a thread.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextSnapshot {
|
||||
pub struct Context {
|
||||
pub id: ContextId,
|
||||
pub name: SharedString,
|
||||
pub parent: Option<SharedString>,
|
||||
pub tooltip: Option<SharedString>,
|
||||
pub icon_path: Option<SharedString>,
|
||||
pub kind: ContextKind,
|
||||
/// Joining these strings separated by \n yields text for model. Not refreshed by `snapshot`.
|
||||
pub text: Box<[SharedString]>,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ContextKind {
|
||||
File,
|
||||
Directory,
|
||||
@@ -42,284 +31,62 @@ pub enum ContextKind {
|
||||
Thread,
|
||||
}
|
||||
|
||||
impl ContextKind {
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
ContextKind::File => "File",
|
||||
ContextKind::Directory => "Folder",
|
||||
ContextKind::FetchedUrl => "Fetch",
|
||||
ContextKind::Thread => "Thread",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(&self) -> IconName {
|
||||
match self {
|
||||
ContextKind::File => IconName::File,
|
||||
ContextKind::Directory => IconName::Folder,
|
||||
ContextKind::FetchedUrl => IconName::Globe,
|
||||
ContextKind::Thread => IconName::MessageCircle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Context {
|
||||
File(FileContext),
|
||||
Directory(DirectoryContext),
|
||||
FetchedUrl(FetchedUrlContext),
|
||||
Thread(ThreadContext),
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn id(&self) -> ContextId {
|
||||
match self {
|
||||
Self::File(file) => file.id,
|
||||
Self::Directory(directory) => directory.snapshot.id,
|
||||
Self::FetchedUrl(url) => url.id,
|
||||
Self::Thread(thread) => thread.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileContext {
|
||||
pub id: ContextId,
|
||||
pub context_buffer: ContextBuffer,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectoryContext {
|
||||
pub path: Rc<Path>,
|
||||
pub context_buffers: Vec<ContextBuffer>,
|
||||
pub snapshot: ContextSnapshot,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FetchedUrlContext {
|
||||
pub id: ContextId,
|
||||
pub url: SharedString,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
// TODO: Model<Thread> holds onto the thread even if the thread is deleted. Can either handle this
|
||||
// explicitly or have a WeakModel<Thread> and remove during snapshot.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadContext {
|
||||
pub id: ContextId,
|
||||
pub thread: Model<Thread>,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
// TODO: Model<Buffer> holds onto the buffer even if the file is deleted and closed. Should remove
|
||||
// the context from the message editor in this case.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextBuffer {
|
||||
pub id: BufferId,
|
||||
pub buffer: Model<Buffer>,
|
||||
pub version: clock::Global,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
|
||||
match &self {
|
||||
Self::File(file_context) => file_context.snapshot(cx),
|
||||
Self::Directory(directory_context) => Some(directory_context.snapshot()),
|
||||
Self::FetchedUrl(fetched_url_context) => Some(fetched_url_context.snapshot()),
|
||||
Self::Thread(thread_context) => Some(thread_context.snapshot(cx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileContext {
|
||||
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
|
||||
let buffer = self.context_buffer.buffer.read(cx);
|
||||
let path = buffer_path_log_err(buffer)?;
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => full_path.clone(),
|
||||
};
|
||||
let parent = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p.to_string_lossy().into_owned().into());
|
||||
|
||||
let icon_path = FileIcons::get_icon(&path, cx);
|
||||
|
||||
Some(ContextSnapshot {
|
||||
id: self.id,
|
||||
name,
|
||||
parent,
|
||||
tooltip: Some(full_path),
|
||||
icon_path,
|
||||
kind: ContextKind::File,
|
||||
text: Box::new([self.context_buffer.text.clone()]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryContext {
|
||||
pub fn new(
|
||||
id: ContextId,
|
||||
path: &Path,
|
||||
context_buffers: Vec<ContextBuffer>,
|
||||
) -> DirectoryContext {
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => full_path.clone(),
|
||||
};
|
||||
|
||||
let parent = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p.to_string_lossy().into_owned().into());
|
||||
|
||||
// TODO: include directory path in text?
|
||||
let text = context_buffers
|
||||
.iter()
|
||||
.map(|b| b.text.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
|
||||
DirectoryContext {
|
||||
path: path.into(),
|
||||
context_buffers,
|
||||
snapshot: ContextSnapshot {
|
||||
id,
|
||||
name,
|
||||
parent,
|
||||
tooltip: Some(full_path),
|
||||
icon_path: None,
|
||||
kind: ContextKind::Directory,
|
||||
text,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> ContextSnapshot {
|
||||
self.snapshot.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchedUrlContext {
|
||||
pub fn snapshot(&self) -> ContextSnapshot {
|
||||
ContextSnapshot {
|
||||
id: self.id,
|
||||
name: self.url.clone(),
|
||||
parent: None,
|
||||
tooltip: None,
|
||||
icon_path: None,
|
||||
kind: ContextKind::FetchedUrl,
|
||||
text: Box::new([self.text.clone()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadContext {
|
||||
pub fn snapshot(&self, cx: &AppContext) -> ContextSnapshot {
|
||||
let thread = self.thread.read(cx);
|
||||
ContextSnapshot {
|
||||
id: self.id,
|
||||
name: thread.summary().unwrap_or("New thread".into()),
|
||||
parent: None,
|
||||
tooltip: None,
|
||||
icon_path: None,
|
||||
kind: ContextKind::Thread,
|
||||
text: Box::new([self.text.clone()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach_context_to_message(
|
||||
message: &mut LanguageModelRequestMessage,
|
||||
contexts: impl Iterator<Item = ContextSnapshot>,
|
||||
context: impl IntoIterator<Item = Context>,
|
||||
) {
|
||||
let mut file_context = Vec::new();
|
||||
let mut directory_context = Vec::new();
|
||||
let mut fetch_context = Vec::new();
|
||||
let mut thread_context = Vec::new();
|
||||
let mut file_context = String::new();
|
||||
let mut directory_context = String::new();
|
||||
let mut fetch_context = String::new();
|
||||
let mut thread_context = String::new();
|
||||
|
||||
let mut capacity = 0;
|
||||
for context in contexts {
|
||||
capacity += context.text.len();
|
||||
for context in context.into_iter() {
|
||||
match context.kind {
|
||||
ContextKind::File => file_context.push(context),
|
||||
ContextKind::Directory => directory_context.push(context),
|
||||
ContextKind::FetchedUrl => fetch_context.push(context),
|
||||
ContextKind::Thread => thread_context.push(context),
|
||||
}
|
||||
}
|
||||
if !file_context.is_empty() {
|
||||
capacity += 1;
|
||||
}
|
||||
if !directory_context.is_empty() {
|
||||
capacity += 1;
|
||||
}
|
||||
if !fetch_context.is_empty() {
|
||||
capacity += 1 + fetch_context.len();
|
||||
}
|
||||
if !thread_context.is_empty() {
|
||||
capacity += 1 + thread_context.len();
|
||||
}
|
||||
if capacity == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut context_chunks = Vec::with_capacity(capacity);
|
||||
|
||||
if !file_context.is_empty() {
|
||||
context_chunks.push("The following files are available:\n");
|
||||
for context in &file_context {
|
||||
for chunk in &context.text {
|
||||
context_chunks.push(&chunk);
|
||||
ContextKind::File { .. } => {
|
||||
file_context.push_str(&context.text);
|
||||
file_context.push('\n');
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
directory_context.push_str(&context.text);
|
||||
directory_context.push('\n');
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
fetch_context.push_str(&context.name);
|
||||
fetch_context.push('\n');
|
||||
fetch_context.push_str(&context.text);
|
||||
fetch_context.push('\n');
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
thread_context.push_str(&context.name);
|
||||
thread_context.push('\n');
|
||||
thread_context.push_str(&context.text);
|
||||
thread_context.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut context_text = String::new();
|
||||
if !file_context.is_empty() {
|
||||
context_text.push_str("The following files are available:\n");
|
||||
context_text.push_str(&file_context);
|
||||
}
|
||||
|
||||
if !directory_context.is_empty() {
|
||||
context_chunks.push("The following directories are available:\n");
|
||||
for context in &directory_context {
|
||||
for chunk in &context.text {
|
||||
context_chunks.push(&chunk);
|
||||
}
|
||||
}
|
||||
context_text.push_str("The following directories are available:\n");
|
||||
context_text.push_str(&directory_context);
|
||||
}
|
||||
|
||||
if !fetch_context.is_empty() {
|
||||
context_chunks.push("The following fetched results are available:\n");
|
||||
for context in &fetch_context {
|
||||
context_chunks.push(&context.name);
|
||||
for chunk in &context.text {
|
||||
context_chunks.push(&chunk);
|
||||
}
|
||||
}
|
||||
context_text.push_str("The following fetched results are available\n");
|
||||
context_text.push_str(&fetch_context);
|
||||
}
|
||||
|
||||
if !thread_context.is_empty() {
|
||||
context_chunks.push("The following previous conversation threads are available:\n");
|
||||
for context in &thread_context {
|
||||
context_chunks.push(&context.name);
|
||||
for chunk in &context.text {
|
||||
context_chunks.push(&chunk);
|
||||
}
|
||||
}
|
||||
context_text.push_str("The following previous conversation threads are available\n");
|
||||
context_text.push_str(&thread_context);
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
context_chunks.len() == capacity,
|
||||
"attach_context_message calculated capacity of {}, but length was {}",
|
||||
capacity,
|
||||
context_chunks.len()
|
||||
);
|
||||
|
||||
if !context_chunks.is_empty() {
|
||||
message
|
||||
.content
|
||||
.push(MessageContent::Text(context_chunks.join("\n")));
|
||||
if !context_text.is_empty() {
|
||||
message.content.push(MessageContent::Text(context_text));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,16 @@ mod fetch_context_picker;
|
||||
mod file_context_picker;
|
||||
mod thread_context_picker;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::Editor;
|
||||
use file_context_picker::render_file_context_entry;
|
||||
use gpui::{
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, View, WeakModel, WeakView,
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, SharedString, Task, View,
|
||||
WeakModel, WeakView,
|
||||
};
|
||||
use project::ProjectPath;
|
||||
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
|
||||
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
|
||||
@@ -23,7 +21,6 @@ use crate::context_picker::file_context_picker::FileContextPicker;
|
||||
use crate::context_picker::thread_context_picker::ThreadContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::AssistantPanel;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ConfirmBehavior {
|
||||
@@ -33,7 +30,7 @@ pub enum ConfirmBehavior {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ContextPickerMode {
|
||||
Default(View<ContextMenu>),
|
||||
Default,
|
||||
File(View<FileContextPicker>),
|
||||
Directory(View<DirectoryContextPicker>),
|
||||
Fetch(View<FetchContextPicker>),
|
||||
@@ -42,11 +39,7 @@ enum ContextPickerMode {
|
||||
|
||||
pub(super) struct ContextPicker {
|
||||
mode: ContextPickerMode,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
picker: View<Picker<ContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl ContextPicker {
|
||||
@@ -54,313 +47,56 @@ impl ContextPicker {
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
editor: WeakView<Editor>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
ContextPicker {
|
||||
mode: ContextPickerMode::Default(ContextMenu::build(cx, |menu, _cx| menu)),
|
||||
let mut entries = Vec::new();
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "File".into(),
|
||||
kind: ContextKind::File,
|
||||
icon: IconName::File,
|
||||
});
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Folder".into(),
|
||||
kind: ContextKind::Directory,
|
||||
icon: IconName::Folder,
|
||||
});
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Fetch".into(),
|
||||
kind: ContextKind::FetchedUrl,
|
||||
icon: IconName::Globe,
|
||||
});
|
||||
|
||||
if thread_store.is_some() {
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Thread".into(),
|
||||
kind: ContextKind::Thread,
|
||||
icon: IconName::MessageCircle,
|
||||
});
|
||||
}
|
||||
|
||||
let delegate = ContextPickerDelegate {
|
||||
context_picker: cx.view().downgrade(),
|
||||
workspace,
|
||||
context_store,
|
||||
thread_store,
|
||||
editor,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
}
|
||||
}
|
||||
entries,
|
||||
selected_ix: 0,
|
||||
};
|
||||
|
||||
pub fn init(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.mode = ContextPickerMode::Default(self.build_menu(cx));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn build_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
|
||||
let context_picker = cx.view().clone();
|
||||
|
||||
let menu = ContextMenu::build(cx, move |menu, cx| {
|
||||
let recent = self.recent_entries(cx);
|
||||
let has_recent = !recent.is_empty();
|
||||
let recent_entries = recent
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
|
||||
|
||||
let mut context_kinds = vec![
|
||||
ContextKind::File,
|
||||
ContextKind::Directory,
|
||||
ContextKind::FetchedUrl,
|
||||
];
|
||||
if self.allow_threads() {
|
||||
context_kinds.push(ContextKind::Thread);
|
||||
}
|
||||
|
||||
let menu = menu
|
||||
.when(has_recent, |menu| {
|
||||
menu.custom_row(|_| {
|
||||
div()
|
||||
.mb_1()
|
||||
.child(
|
||||
Label::new("Recent")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
})
|
||||
.extend(recent_entries)
|
||||
.when(has_recent, |menu| menu.separator())
|
||||
.extend(context_kinds.into_iter().map(|kind| {
|
||||
let context_picker = context_picker.clone();
|
||||
|
||||
ContextMenuEntry::new(kind.label())
|
||||
.icon(kind.icon())
|
||||
.handler(move |cx| {
|
||||
context_picker.update(cx, |this, cx| this.select_kind(kind, cx))
|
||||
})
|
||||
}));
|
||||
|
||||
match self.confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => menu.keep_open_on_confirm(),
|
||||
ConfirmBehavior::Close => menu,
|
||||
}
|
||||
let picker = cx.new_view(|cx| {
|
||||
Picker::nonsearchable_uniform_list(delegate, cx).max_height(Some(rems(20.).into()))
|
||||
});
|
||||
|
||||
cx.subscribe(&menu, move |_, _, _: &DismissEvent, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.detach();
|
||||
|
||||
menu
|
||||
}
|
||||
|
||||
/// Whether threads are allowed as context.
|
||||
pub fn allow_threads(&self) -> bool {
|
||||
self.thread_store.is_some()
|
||||
}
|
||||
|
||||
fn select_kind(&mut self, kind: ContextKind, cx: &mut ViewContext<Self>) {
|
||||
let context_picker = cx.view().downgrade();
|
||||
|
||||
match kind {
|
||||
ContextKind::File => {
|
||||
self.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||
FileContextPicker::new(
|
||||
context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.editor.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
self.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||
DirectoryContextPicker::new(
|
||||
context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
self.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
FetchContextPicker::new(
|
||||
context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||
self.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
thread_store.clone(),
|
||||
context_picker.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
cx.focus_self();
|
||||
}
|
||||
|
||||
fn recent_menu_item(
|
||||
&self,
|
||||
context_picker: View<ContextPicker>,
|
||||
ix: usize,
|
||||
entry: RecentEntry,
|
||||
) -> ContextMenuItem {
|
||||
match entry {
|
||||
RecentEntry::File {
|
||||
project_path,
|
||||
path_prefix,
|
||||
} => {
|
||||
let context_store = self.context_store.clone();
|
||||
let path = project_path.path.clone();
|
||||
|
||||
ContextMenuItem::custom_entry(
|
||||
move |cx| {
|
||||
render_file_context_entry(
|
||||
ElementId::NamedInteger("ctx-recent".into(), ix),
|
||||
&path,
|
||||
&path_prefix,
|
||||
context_store.clone(),
|
||||
cx,
|
||||
)
|
||||
.into_any()
|
||||
},
|
||||
move |cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.add_recent_file(project_path.clone(), cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
RecentEntry::Thread(thread) => {
|
||||
let context_store = self.context_store.clone();
|
||||
let view_thread = thread.clone();
|
||||
|
||||
ContextMenuItem::custom_entry(
|
||||
move |cx| {
|
||||
render_thread_context_entry(&view_thread, context_store.clone(), cx)
|
||||
.into_any()
|
||||
},
|
||||
move |cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.add_recent_thread(thread.clone(), cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
ContextPicker {
|
||||
mode: ContextPickerMode::Default,
|
||||
picker,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_recent_file(&self, project_path: ProjectPath, cx: &mut ViewContext<Self>) {
|
||||
let Some(context_store) = self.context_store.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let task = context_store.update(cx, |context_store, cx| {
|
||||
context_store.add_file_from_path(project_path.clone(), cx)
|
||||
});
|
||||
|
||||
cx.spawn(|_, mut cx| async move { task.await.notify_async_err(&mut cx) })
|
||||
.detach();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn add_recent_thread(&self, thread: ThreadContextEntry, cx: &mut ViewContext<Self>) {
|
||||
let Some(context_store) = self.context_store.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(thread) = self
|
||||
.thread_store
|
||||
.clone()
|
||||
.and_then(|this| this.upgrade())
|
||||
.and_then(|this| this.update(cx, |this, cx| this.open_thread(&thread.id, cx)))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
context_store.update(cx, |context_store, cx| {
|
||||
context_store.add_thread(thread, cx);
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn recent_entries(&self, cx: &mut WindowContext) -> Vec<RecentEntry> {
|
||||
let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let Some(context_store) = self.context_store.upgrade().map(|cs| cs.read(cx)) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let mut recent = Vec::with_capacity(6);
|
||||
|
||||
let mut current_files = context_store.file_paths(cx);
|
||||
|
||||
if let Some(active_path) = Self::active_singleton_buffer_path(&workspace, cx) {
|
||||
current_files.insert(active_path);
|
||||
}
|
||||
|
||||
let project = workspace.project().read(cx);
|
||||
|
||||
recent.extend(
|
||||
workspace
|
||||
.recent_navigation_history_iter(cx)
|
||||
.filter(|(path, _)| !current_files.contains(&path.path.to_path_buf()))
|
||||
.take(4)
|
||||
.filter_map(|(project_path, _)| {
|
||||
project
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
.map(|worktree| RecentEntry::File {
|
||||
project_path,
|
||||
path_prefix: worktree.read(cx).root_name().into(),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
let mut current_threads = context_store.thread_ids();
|
||||
|
||||
if let Some(active_thread) = workspace
|
||||
.panel::<AssistantPanel>(cx)
|
||||
.map(|panel| panel.read(cx).active_thread(cx))
|
||||
{
|
||||
current_threads.insert(active_thread.read(cx).id().clone());
|
||||
}
|
||||
|
||||
let Some(thread_store) = self
|
||||
.thread_store
|
||||
.as_ref()
|
||||
.and_then(|thread_store| thread_store.upgrade())
|
||||
else {
|
||||
return recent;
|
||||
};
|
||||
|
||||
thread_store.update(cx, |thread_store, cx| {
|
||||
recent.extend(
|
||||
thread_store
|
||||
.threads(cx)
|
||||
.into_iter()
|
||||
.filter(|thread| !current_threads.contains(thread.read(cx).id()))
|
||||
.take(2)
|
||||
.map(|thread| {
|
||||
let thread = thread.read(cx);
|
||||
|
||||
RecentEntry::Thread(ThreadContextEntry {
|
||||
id: thread.id().clone(),
|
||||
summary: thread.summary_or_default(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
recent
|
||||
}
|
||||
|
||||
fn active_singleton_buffer_path(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
|
||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||
let buffer = editor.buffer().read(cx).as_singleton()?;
|
||||
|
||||
let path = buffer.read(cx).file()?.path().to_path_buf();
|
||||
Some(path)
|
||||
pub fn reset_mode(&mut self) {
|
||||
self.mode = ContextPickerMode::Default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +105,7 @@ impl EventEmitter<DismissEvent> for ContextPicker {}
|
||||
impl FocusableView for ContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
match &self.mode {
|
||||
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
|
||||
ContextPickerMode::Default => self.picker.focus_handle(cx),
|
||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
||||
ContextPickerMode::Directory(directory_picker) => directory_picker.focus_handle(cx),
|
||||
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
||||
@@ -384,7 +120,7 @@ impl Render for ContextPicker {
|
||||
.w(px(400.))
|
||||
.min_w(px(400.))
|
||||
.map(|parent| match &self.mode {
|
||||
ContextPickerMode::Default(menu) => parent.child(menu.clone()),
|
||||
ContextPickerMode::Default => parent.child(self.picker.clone()),
|
||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
||||
ContextPickerMode::Directory(directory_picker) => {
|
||||
parent.child(directory_picker.clone())
|
||||
@@ -394,10 +130,140 @@ impl Render for ContextPicker {
|
||||
})
|
||||
}
|
||||
}
|
||||
enum RecentEntry {
|
||||
File {
|
||||
project_path: ProjectPath,
|
||||
path_prefix: Arc<str>,
|
||||
},
|
||||
Thread(ThreadContextEntry),
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ContextPickerEntry {
|
||||
name: SharedString,
|
||||
kind: ContextKind,
|
||||
icon: IconName,
|
||||
}
|
||||
|
||||
pub(crate) struct ContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
entries: Vec<ContextPickerEntry>,
|
||||
selected_ix: usize,
|
||||
}
|
||||
|
||||
impl PickerDelegate for ContextPickerDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_ix
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_ix = ix.min(self.entries.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Select a context source…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(entry) = self.entries.get(self.selected_ix) {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| {
|
||||
match entry.kind {
|
||||
ContextKind::File => {
|
||||
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||
FileContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||
DirectoryContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
FetchContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
thread_store.clone(),
|
||||
self.context_picker.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.focus_self();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| match this.mode {
|
||||
ContextPickerMode::Default => cx.emit(DismissEvent),
|
||||
ContextPickerMode::File(_)
|
||||
| ContextPickerMode::Directory(_)
|
||||
| ContextPickerMode::Fetch(_)
|
||||
| ContextPickerMode::Thread(_) => {}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let entry = &self.entries[ix];
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Dense)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
h_flex()
|
||||
.min_w(px(250.))
|
||||
.max_w(px(400.))
|
||||
.gap_2()
|
||||
.child(Icon::new(entry.icon).size(IconSize::Small))
|
||||
.child(Label::new(entry.name.clone()).single_line()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,17 @@ use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, Worktree, WorktreeId};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_store::{push_fenced_codeblock, ContextStore};
|
||||
|
||||
pub struct DirectoryContextPicker {
|
||||
picker: View<Picker<DirectoryContextPickerDelegate>>,
|
||||
@@ -178,37 +179,104 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
return;
|
||||
};
|
||||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
||||
path: mat.path.clone(),
|
||||
};
|
||||
|
||||
let Some(task) = self
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.add_directory(project_path, cx)
|
||||
})
|
||||
.ok()
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let path = mat.path.clone();
|
||||
|
||||
let already_included = self
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
if let Some(context_id) = context_store.included_directory(&path) {
|
||||
context_store.remove_context(&context_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(true);
|
||||
if already_included {
|
||||
return;
|
||||
}
|
||||
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
match task.await.notify_async_err(&mut cx) {
|
||||
None => anyhow::Ok(()),
|
||||
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
|
||||
let worktree = project.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
|
||||
})??;
|
||||
|
||||
let files = worktree.update(&mut cx, |worktree, _cx| {
|
||||
collect_files_in_path(worktree, &path)
|
||||
})?;
|
||||
|
||||
let open_buffer_tasks = project.update(&mut cx, |project, cx| {
|
||||
files
|
||||
.into_iter()
|
||||
.map(|file_path| {
|
||||
project.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: file_path.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let open_all_buffers_tasks = cx.background_executor().spawn(async move {
|
||||
let mut buffers = Vec::with_capacity(open_buffer_tasks.len());
|
||||
|
||||
for open_buffer_task in open_buffer_tasks {
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
anyhow::Ok(buffers)
|
||||
});
|
||||
|
||||
let buffers = open_all_buffers_tasks.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let mut text = String::new();
|
||||
|
||||
for buffer in buffers {
|
||||
let buffer = buffer.read(cx);
|
||||
let path = buffer.file().map_or(&path, |file| file.path());
|
||||
push_fenced_codeblock(&path, buffer.text(), &mut text);
|
||||
}
|
||||
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
context_store.insert_directory(&path, text);
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
.update(cx, |this, cx| {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
@@ -226,7 +294,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
||||
context_store
|
||||
.read(cx)
|
||||
.includes_directory(&path_match.path)
|
||||
.included_directory(&path_match.path)
|
||||
.is_some()
|
||||
});
|
||||
|
||||
@@ -250,3 +318,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
if entry.is_dir() {
|
||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||
} else if entry.is_file() {
|
||||
files.push(entry.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
@@ -81,12 +81,11 @@ impl FetchContextPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: String) -> Result<String> {
|
||||
let url = if !url.starts_with("https://") && !url.starts_with("http://") {
|
||||
format!("https://{url}")
|
||||
} else {
|
||||
url
|
||||
};
|
||||
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
|
||||
let mut url = url.to_owned();
|
||||
if !url.starts_with("https://") && !url.starts_with("http://") {
|
||||
url = format!("https://{url}");
|
||||
}
|
||||
|
||||
let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
|
||||
|
||||
@@ -195,16 +194,15 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
let url = self.url.clone();
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let text = cx
|
||||
.background_executor()
|
||||
.spawn(Self::build_message(http_client, url.clone()))
|
||||
.await?;
|
||||
let text = Self::build_message(http_client, &url).await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
context_store.add_fetched_url(url, text);
|
||||
if context_store.included_url(&url).is_none() {
|
||||
context_store.insert_fetched_url(url, text);
|
||||
}
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
@@ -222,7 +220,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
.update(cx, |this, cx| {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
@@ -235,7 +234,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
||||
context_store.read(cx).includes_url(&self.url).is_some()
|
||||
context_store.read(cx).included_url(&self.url).is_some()
|
||||
});
|
||||
|
||||
Some(
|
||||
|
||||
@@ -1,30 +1,17 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::actions::FoldAt;
|
||||
use editor::display_map::{Crease, FoldId};
|
||||
use editor::scroll::Autoscroll;
|
||||
use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
|
||||
use file_icons::FileIcons;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
AnyElement, AppContext, DismissEvent, Empty, FocusHandle, FocusableView, Stateful, Task, View,
|
||||
WeakModel, WeakView,
|
||||
};
|
||||
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use rope::Point;
|
||||
use text::SelectionGoal;
|
||||
use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip};
|
||||
use ui::{prelude::*, ListItem, Tooltip};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::{ContextStore, FileInclusion};
|
||||
use crate::context_store::{ContextStore, IncludedFile};
|
||||
|
||||
pub struct FileContextPicker {
|
||||
picker: View<Picker<FileContextPickerDelegate>>,
|
||||
@@ -34,7 +21,6 @@ impl FileContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -42,7 +28,6 @@ impl FileContextPicker {
|
||||
let delegate = FileContextPickerDelegate::new(
|
||||
context_picker,
|
||||
workspace,
|
||||
editor,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
@@ -67,7 +52,6 @@ impl Render for FileContextPicker {
|
||||
pub struct FileContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<PathMatch>,
|
||||
@@ -78,14 +62,12 @@ impl FileContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_picker,
|
||||
workspace,
|
||||
editor,
|
||||
context_store,
|
||||
confirm_behavior,
|
||||
matches: Vec::new(),
|
||||
@@ -211,126 +193,78 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(file_name) = mat
|
||||
.path
|
||||
.file_name()
|
||||
.map(|os_str| os_str.to_string_lossy().into_owned())
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let path = mat.path.clone();
|
||||
|
||||
let full_path = mat.path.display().to_string();
|
||||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
||||
path: mat.path.clone(),
|
||||
};
|
||||
|
||||
let Some(editor) = self.editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
|
||||
{
|
||||
let mut selections = editor.selections.all::<MultiBufferPoint>(cx);
|
||||
|
||||
for selection in selections.iter_mut() {
|
||||
if selection.is_empty() {
|
||||
let old_head = selection.head();
|
||||
let new_head = MultiBufferPoint::new(
|
||||
old_head.row,
|
||||
old_head.column.saturating_sub(1),
|
||||
);
|
||||
selection.set_head(new_head, SelectionGoal::None);
|
||||
}
|
||||
}
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
}
|
||||
|
||||
let start_anchors = {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
editor
|
||||
.selections
|
||||
.all::<Point>(cx)
|
||||
.into_iter()
|
||||
.map(|selection| snapshot.anchor_before(selection.start))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
editor.insert(&full_path, cx);
|
||||
|
||||
let end_anchors = {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
editor
|
||||
.selections
|
||||
.all::<Point>(cx)
|
||||
.into_iter()
|
||||
.map(|selection| snapshot.anchor_after(selection.end))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
editor.insert("\n", cx); // Needed to end the fold
|
||||
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: render_fold_icon_button(IconName::File, file_name.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let render_trailer = move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let mut rows_to_fold = BTreeSet::new();
|
||||
let crease_iter = start_anchors
|
||||
.into_iter()
|
||||
.zip(end_anchors)
|
||||
.map(|(start, end)| {
|
||||
rows_to_fold.insert(MultiBufferRow(start.to_point(&buffer).row));
|
||||
|
||||
Crease::inline(
|
||||
start..end,
|
||||
placeholder.clone(),
|
||||
fold_toggle("tool-use"),
|
||||
render_trailer,
|
||||
)
|
||||
});
|
||||
|
||||
editor.insert_creases(crease_iter, cx);
|
||||
|
||||
for buffer_row in rows_to_fold {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let Some(task) = self
|
||||
let already_included = self
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.add_file_from_path(project_path, cx)
|
||||
.update(cx, |context_store, _cx| {
|
||||
match context_store.included_file(&path) {
|
||||
Some(IncludedFile::Direct(context_id)) => {
|
||||
context_store.remove_context(&context_id);
|
||||
true
|
||||
}
|
||||
Some(IncludedFile::InDirectory(_)) => true,
|
||||
None => false,
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
.unwrap_or(true);
|
||||
if already_included {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
match task.await.notify_async_err(&mut cx) {
|
||||
None => anyhow::Ok(()),
|
||||
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
|
||||
let Some(open_buffer_task) = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: path.clone(),
|
||||
};
|
||||
|
||||
let task = project.open_buffer(project_path, cx);
|
||||
|
||||
Some(task)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
return anyhow::Ok(());
|
||||
};
|
||||
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.insert_file(buffer.read(cx));
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
.update(cx, |this, cx| {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
@@ -344,127 +278,76 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
|
||||
let (file_name, directory) = if path_match.path.as_ref() == Path::new("") {
|
||||
(SharedString::from(path_match.path_prefix.clone()), None)
|
||||
} else {
|
||||
let file_name = path_match
|
||||
.path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
let mut directory = format!("{}/", path_match.path_prefix);
|
||||
if let Some(parent) = path_match
|
||||
.path
|
||||
.parent()
|
||||
.filter(|parent| parent != &Path::new(""))
|
||||
{
|
||||
directory.push_str(&parent.to_string_lossy());
|
||||
directory.push('/');
|
||||
}
|
||||
|
||||
(file_name, Some(directory))
|
||||
};
|
||||
|
||||
let added = self
|
||||
.context_store
|
||||
.upgrade()
|
||||
.and_then(|context_store| context_store.read(cx).included_file(&path_match.path));
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(render_file_context_entry(
|
||||
ElementId::NamedInteger("file-ctx-picker".into(), ix),
|
||||
&path_match.path,
|
||||
&path_match.path_prefix,
|
||||
self.context_store.clone(),
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_file_context_entry(
|
||||
id: ElementId,
|
||||
path: &Path,
|
||||
path_prefix: &Arc<str>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &WindowContext,
|
||||
) -> Stateful<Div> {
|
||||
let (file_name, directory) = if path == Path::new("") {
|
||||
(SharedString::from(path_prefix.clone()), None)
|
||||
} else {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
let mut directory = format!("{}/", path_prefix);
|
||||
|
||||
if let Some(parent) = path.parent().filter(|parent| parent != &Path::new("")) {
|
||||
directory.push_str(&parent.to_string_lossy());
|
||||
directory.push('/');
|
||||
}
|
||||
|
||||
(file_name, Some(directory))
|
||||
};
|
||||
|
||||
let added = context_store
|
||||
.upgrade()
|
||||
.and_then(|context_store| context_store.read(cx).will_include_file_path(path, cx));
|
||||
|
||||
let file_icon = FileIcons::get_icon(&path, cx)
|
||||
.map(Icon::from_path)
|
||||
.unwrap_or_else(|| Icon::new(IconName::File));
|
||||
|
||||
h_flex()
|
||||
.id(id)
|
||||
.gap_1()
|
||||
.w_full()
|
||||
.child(file_icon.size(IconSize::Small))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(file_name))
|
||||
.children(directory.map(|directory| {
|
||||
Label::new(directory)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
})),
|
||||
)
|
||||
.child(div().w_full())
|
||||
.when_some(added, |el, added| match added {
|
||||
FileInclusion::Direct(_) => el.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
),
|
||||
FileInclusion::InDirectory(dir_name) => {
|
||||
let dir_name = dir_name.to_string_lossy().into_owned();
|
||||
|
||||
el.child(
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Included").size(LabelSize::Small)),
|
||||
.gap_2()
|
||||
.child(Label::new(file_name))
|
||||
.children(directory.map(|directory| {
|
||||
Label::new(directory)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
})),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
.when_some(added, |el, added| match added {
|
||||
IncludedFile::Direct(_) => el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
),
|
||||
IncludedFile::InDirectory(dir_name) => {
|
||||
let dir_name = dir_name.to_string_lossy().into_owned();
|
||||
|
||||
fn render_fold_icon_button(
|
||||
icon: IconName,
|
||||
label: SharedString,
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
|
||||
Arc::new(move |fold_id, _fold_range, _cx| {
|
||||
ButtonLike::new(fold_id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(icon))
|
||||
.child(Label::new(label.clone()).single_line())
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
fn fold_toggle(
|
||||
name: &'static str,
|
||||
) -> impl Fn(
|
||||
MultiBufferRow,
|
||||
bool,
|
||||
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||
&mut WindowContext,
|
||||
) -> AnyElement {
|
||||
move |row, is_folded, fold, _cx| {
|
||||
Disclosure::new((name, row.0 as u64), !is_folded)
|
||||
.toggle_state(is_folded)
|
||||
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||
.into_any_element()
|
||||
el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Included").size(LabelSize::Small)),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem};
|
||||
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::{self, ContextStore};
|
||||
use crate::context_store;
|
||||
use crate::thread::ThreadId;
|
||||
use crate::thread_store::ThreadStore;
|
||||
|
||||
@@ -47,9 +47,9 @@ impl Render for ThreadContextPicker {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThreadContextEntry {
|
||||
pub id: ThreadId,
|
||||
pub summary: SharedString,
|
||||
struct ThreadContextEntry {
|
||||
id: ThreadId,
|
||||
summary: SharedString,
|
||||
}
|
||||
|
||||
pub struct ThreadContextPickerDelegate {
|
||||
@@ -103,8 +103,10 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
this.threads(cx)
|
||||
.into_iter()
|
||||
.map(|thread| {
|
||||
const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
|
||||
|
||||
let id = thread.read(cx).id().clone();
|
||||
let summary = thread.read(cx).summary_or_default();
|
||||
let summary = thread.read(cx).summary().unwrap_or(DEFAULT_SUMMARY);
|
||||
ThreadContextEntry { id, summary }
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -165,7 +167,13 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
};
|
||||
|
||||
self.context_store
|
||||
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
|
||||
.update(cx, |context_store, cx| {
|
||||
if let Some(context_id) = context_store.included_thread(&entry.id) {
|
||||
context_store.remove_context(&context_id);
|
||||
} else {
|
||||
context_store.insert_thread(thread.read(cx));
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
match self.confirm_behavior {
|
||||
@@ -176,7 +184,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
.update(cx, |this, cx| {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
@@ -190,37 +199,27 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
) -> Option<Self::ListItem> {
|
||||
let thread = &self.matches[ix];
|
||||
|
||||
Some(ListItem::new(ix).inset(true).toggle_state(selected).child(
|
||||
render_thread_context_entry(thread, self.context_store.clone(), cx),
|
||||
))
|
||||
let added = self.context_store.upgrade().map_or(false, |ctx_store| {
|
||||
ctx_store.read(cx).included_thread(&thread.id).is_some()
|
||||
});
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(Label::new(thread.summary.clone()))
|
||||
.when(added, |el| {
|
||||
el.end_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_thread_context_entry(
|
||||
thread: &ThreadContextEntry,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Div {
|
||||
let added = context_store.upgrade().map_or(false, |ctx_store| {
|
||||
ctx_store.read(cx).includes_thread(&thread.id).is_some()
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.w_full()
|
||||
.child(Icon::new(IconName::MessageCircle).size(IconSize::Small))
|
||||
.child(Label::new(thread.summary.clone()))
|
||||
.child(div().w_full())
|
||||
.when(added, |el| {
|
||||
el.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
)
|
||||
.child(Label::new("Added").size(LabelSize::Small)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,56 +1,37 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use futures::{self, future, Future, FutureExt};
|
||||
use gpui::{AppContext, AsyncAppContext, Model, ModelContext, SharedString, Task, WeakView};
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::SharedString;
|
||||
use language::Buffer;
|
||||
use project::{ProjectPath, Worktree};
|
||||
use rope::Rope;
|
||||
use text::BufferId;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::{
|
||||
Context, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext, FetchedUrlContext,
|
||||
FileContext, ThreadContext,
|
||||
use crate::thread::Thread;
|
||||
use crate::{
|
||||
context::{Context, ContextId, ContextKind},
|
||||
thread::ThreadId,
|
||||
};
|
||||
use crate::context_strip::SuggestedContext;
|
||||
use crate::thread::{Thread, ThreadId};
|
||||
|
||||
pub struct ContextStore {
|
||||
workspace: WeakView<Workspace>,
|
||||
context: Vec<Context>,
|
||||
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
|
||||
next_context_id: ContextId,
|
||||
files: BTreeMap<BufferId, ContextId>,
|
||||
files: HashMap<PathBuf, ContextId>,
|
||||
directories: HashMap<PathBuf, ContextId>,
|
||||
threads: HashMap<ThreadId, ContextId>,
|
||||
fetched_urls: HashMap<String, ContextId>,
|
||||
}
|
||||
|
||||
impl ContextStore {
|
||||
pub fn new(workspace: WeakView<Workspace>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
workspace,
|
||||
context: Vec::new(),
|
||||
next_context_id: ContextId(0),
|
||||
files: BTreeMap::default(),
|
||||
files: HashMap::default(),
|
||||
directories: HashMap::default(),
|
||||
threads: HashMap::default(),
|
||||
fetched_urls: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot<'a>(
|
||||
&'a self,
|
||||
cx: &'a AppContext,
|
||||
) -> impl Iterator<Item = ContextSnapshot> + 'a {
|
||||
self.context()
|
||||
.iter()
|
||||
.flat_map(|context| context.snapshot(cx))
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Vec<Context> {
|
||||
&self.context
|
||||
}
|
||||
@@ -63,308 +44,121 @@ impl ContextStore {
|
||||
self.fetched_urls.clear();
|
||||
}
|
||||
|
||||
pub fn add_file_from_path(
|
||||
&mut self,
|
||||
project_path: ProjectPath,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("failed to read project")));
|
||||
};
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let open_buffer_task = project.update(&mut cx, |project, cx| {
|
||||
project.open_buffer(project_path.clone(), cx)
|
||||
})?;
|
||||
|
||||
let buffer_model = open_buffer_task.await?;
|
||||
let buffer_id = this.update(&mut cx, |_, cx| buffer_model.read(cx).remote_id())?;
|
||||
|
||||
let already_included = this.update(&mut cx, |this, _cx| {
|
||||
match this.will_include_buffer(buffer_id, &project_path.path) {
|
||||
Some(FileInclusion::Direct(context_id)) => {
|
||||
this.remove_context(context_id);
|
||||
true
|
||||
}
|
||||
Some(FileInclusion::InDirectory(_)) => true,
|
||||
None => false,
|
||||
}
|
||||
})?;
|
||||
|
||||
if already_included {
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
|
||||
let buffer = buffer_model.read(cx);
|
||||
collect_buffer_info_and_text(
|
||||
project_path.path.clone(),
|
||||
buffer_model,
|
||||
buffer,
|
||||
cx.to_async(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let text = text_task.await;
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.insert_file(make_context_buffer(buffer_info, text));
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_file_from_buffer(
|
||||
&mut self,
|
||||
buffer_model: Model<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
|
||||
let buffer = buffer_model.read(cx);
|
||||
let Some(file) = buffer.file() else {
|
||||
return Err(anyhow!("Buffer has no path."));
|
||||
};
|
||||
Ok(collect_buffer_info_and_text(
|
||||
file.path().clone(),
|
||||
buffer_model,
|
||||
buffer,
|
||||
cx.to_async(),
|
||||
))
|
||||
})??;
|
||||
|
||||
let text = text_task.await;
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.insert_file(make_context_buffer(buffer_info, text))
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_file(&mut self, context_buffer: ContextBuffer) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
self.files.insert(context_buffer.id, id);
|
||||
self.context
|
||||
.push(Context::File(FileContext { id, context_buffer }));
|
||||
}
|
||||
|
||||
pub fn add_directory(
|
||||
&mut self,
|
||||
project_path: ProjectPath,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("failed to read project")));
|
||||
};
|
||||
|
||||
let already_included = if let Some(context_id) = self.includes_directory(&project_path.path)
|
||||
{
|
||||
self.remove_context(context_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if already_included {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let worktree_id = project_path.worktree_id;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let worktree = project.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
|
||||
})??;
|
||||
|
||||
let files = worktree.update(&mut cx, |worktree, _cx| {
|
||||
collect_files_in_path(worktree, &project_path.path)
|
||||
})?;
|
||||
|
||||
let open_buffers_task = project.update(&mut cx, |project, cx| {
|
||||
let tasks = files.iter().map(|file_path| {
|
||||
project.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: file_path.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
future::join_all(tasks)
|
||||
})?;
|
||||
|
||||
let buffers = open_buffers_task.await;
|
||||
|
||||
let mut buffer_infos = Vec::new();
|
||||
let mut text_tasks = Vec::new();
|
||||
this.update(&mut cx, |_, cx| {
|
||||
for (path, buffer_model) in files.into_iter().zip(buffers) {
|
||||
let buffer_model = buffer_model?;
|
||||
let buffer = buffer_model.read(cx);
|
||||
let (buffer_info, text_task) =
|
||||
collect_buffer_info_and_text(path, buffer_model, buffer, cx.to_async());
|
||||
buffer_infos.push(buffer_info);
|
||||
text_tasks.push(text_task);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
let buffer_texts = future::join_all(text_tasks).await;
|
||||
let context_buffers = buffer_infos
|
||||
.into_iter()
|
||||
.zip(buffer_texts)
|
||||
.map(|(info, text)| make_context_buffer(info, text))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if context_buffers.is_empty() {
|
||||
bail!("No text files found in {}", &project_path.path.display());
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.insert_directory(&project_path.path, context_buffers);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_directory(&mut self, path: &Path, context_buffers: Vec<ContextBuffer>) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
self.directories.insert(path.to_path_buf(), id);
|
||||
|
||||
self.context.push(Context::Directory(DirectoryContext::new(
|
||||
id,
|
||||
path,
|
||||
context_buffers,
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn add_thread(&mut self, thread: Model<Thread>, cx: &mut ModelContext<Self>) {
|
||||
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
|
||||
self.remove_context(context_id);
|
||||
} else {
|
||||
self.insert_thread(thread, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_thread(&mut self, thread: Model<Thread>, cx: &AppContext) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
let text = thread.read(cx).text().into();
|
||||
|
||||
self.threads.insert(thread.read(cx).id().clone(), id);
|
||||
self.context
|
||||
.push(Context::Thread(ThreadContext { id, thread, text }));
|
||||
}
|
||||
|
||||
pub fn add_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
|
||||
if self.includes_url(&url).is_none() {
|
||||
self.insert_fetched_url(url, text);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
|
||||
self.fetched_urls.insert(url.clone(), id);
|
||||
self.context.push(Context::FetchedUrl(FetchedUrlContext {
|
||||
id,
|
||||
url: url.into(),
|
||||
text: text.into(),
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn accept_suggested_context(
|
||||
&mut self,
|
||||
suggested: &SuggestedContext,
|
||||
cx: &mut ModelContext<ContextStore>,
|
||||
) -> Task<Result<()>> {
|
||||
match suggested {
|
||||
SuggestedContext::File {
|
||||
buffer,
|
||||
icon_path: _,
|
||||
name: _,
|
||||
} => {
|
||||
if let Some(buffer) = buffer.upgrade() {
|
||||
return self.add_file_from_buffer(buffer, cx);
|
||||
};
|
||||
}
|
||||
SuggestedContext::Thread { thread, name: _ } => {
|
||||
if let Some(thread) = thread.upgrade() {
|
||||
self.insert_thread(thread, cx);
|
||||
};
|
||||
}
|
||||
}
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, id: ContextId) {
|
||||
let Some(ix) = self.context.iter().position(|context| context.id() == id) else {
|
||||
pub fn insert_file(&mut self, buffer: &Buffer) {
|
||||
let Some(file) = buffer.file() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match self.context.remove(ix) {
|
||||
Context::File(_) => {
|
||||
self.files.retain(|_, context_id| *context_id != id);
|
||||
let path = file.path();
|
||||
|
||||
let id = self.next_context_id.post_inc();
|
||||
self.files.insert(path.to_path_buf(), id);
|
||||
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => full_path.clone(),
|
||||
};
|
||||
|
||||
let parent = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p.to_string_lossy().into_owned().into());
|
||||
|
||||
let mut text = String::new();
|
||||
push_fenced_codeblock(path, buffer.text(), &mut text);
|
||||
|
||||
self.context.push(Context {
|
||||
id,
|
||||
name,
|
||||
parent,
|
||||
tooltip: Some(full_path),
|
||||
kind: ContextKind::File,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_directory(&mut self, path: &Path, text: impl Into<SharedString>) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
self.directories.insert(path.to_path_buf(), id);
|
||||
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||
None => full_path.clone(),
|
||||
};
|
||||
|
||||
let parent = path
|
||||
.parent()
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|p| p.to_string_lossy().into_owned().into());
|
||||
|
||||
self.context.push(Context {
|
||||
id,
|
||||
name,
|
||||
parent,
|
||||
tooltip: Some(full_path),
|
||||
kind: ContextKind::Directory,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_thread(&mut self, thread: &Thread) {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
self.threads.insert(thread.id().clone(), context_id);
|
||||
|
||||
self.context.push(Context {
|
||||
id: context_id,
|
||||
name: thread.summary().unwrap_or("New thread".into()),
|
||||
parent: None,
|
||||
tooltip: None,
|
||||
kind: ContextKind::Thread,
|
||||
text: thread.text().into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
self.fetched_urls.insert(url.clone(), context_id);
|
||||
|
||||
self.context.push(Context {
|
||||
id: context_id,
|
||||
name: url.into(),
|
||||
parent: None,
|
||||
tooltip: None,
|
||||
kind: ContextKind::FetchedUrl,
|
||||
text: text.into(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_context(&mut self, id: &ContextId) {
|
||||
let Some(ix) = self.context.iter().position(|context| context.id == *id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match self.context.remove(ix).kind {
|
||||
ContextKind::File => {
|
||||
self.files.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
Context::Directory(_) => {
|
||||
self.directories.retain(|_, context_id| *context_id != id);
|
||||
ContextKind::Directory => {
|
||||
self.directories.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
Context::FetchedUrl(_) => {
|
||||
self.fetched_urls.retain(|_, context_id| *context_id != id);
|
||||
ContextKind::FetchedUrl => {
|
||||
self.fetched_urls.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
Context::Thread(_) => {
|
||||
self.threads.retain(|_, context_id| *context_id != id);
|
||||
ContextKind::Thread => {
|
||||
self.threads.retain(|_, context_id| context_id != id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the buffer is already included directly in the context, or if it will be
|
||||
/// included in the context via a directory. Directory inclusion is based on paths rather than
|
||||
/// buffer IDs as the directory will be re-scanned.
|
||||
pub fn will_include_buffer(&self, buffer_id: BufferId, path: &Path) -> Option<FileInclusion> {
|
||||
if let Some(context_id) = self.files.get(&buffer_id) {
|
||||
return Some(FileInclusion::Direct(*context_id));
|
||||
pub fn included_file(&self, path: &Path) -> Option<IncludedFile> {
|
||||
if let Some(id) = self.files.get(path) {
|
||||
return Some(IncludedFile::Direct(*id));
|
||||
}
|
||||
|
||||
self.will_include_file_path_via_directory(path)
|
||||
}
|
||||
|
||||
/// Returns whether this file path is already included directly in the context, or if it will be
|
||||
/// included in the context via a directory.
|
||||
pub fn will_include_file_path(&self, path: &Path, cx: &AppContext) -> Option<FileInclusion> {
|
||||
if !self.files.is_empty() {
|
||||
let found_file_context = self.context.iter().find(|context| match &context {
|
||||
Context::File(file_context) => {
|
||||
let buffer = file_context.context_buffer.buffer.read(cx);
|
||||
if let Some(file_path) = buffer_path_log_err(buffer) {
|
||||
*file_path == *path
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
if let Some(context) = found_file_context {
|
||||
return Some(FileInclusion::Direct(context.id()));
|
||||
}
|
||||
}
|
||||
|
||||
self.will_include_file_path_via_directory(path)
|
||||
}
|
||||
|
||||
fn will_include_file_path_via_directory(&self, path: &Path) -> Option<FileInclusion> {
|
||||
if self.directories.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -373,282 +167,61 @@ impl ContextStore {
|
||||
|
||||
while buf.pop() {
|
||||
if let Some(_) = self.directories.get(&buf) {
|
||||
return Some(FileInclusion::InDirectory(buf));
|
||||
return Some(IncludedFile::InDirectory(buf));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn includes_directory(&self, path: &Path) -> Option<ContextId> {
|
||||
pub fn included_directory(&self, path: &Path) -> Option<ContextId> {
|
||||
self.directories.get(path).copied()
|
||||
}
|
||||
|
||||
pub fn includes_thread(&self, thread_id: &ThreadId) -> Option<ContextId> {
|
||||
pub fn included_thread(&self, thread_id: &ThreadId) -> Option<ContextId> {
|
||||
self.threads.get(thread_id).copied()
|
||||
}
|
||||
|
||||
pub fn includes_url(&self, url: &str) -> Option<ContextId> {
|
||||
pub fn included_url(&self, url: &str) -> Option<ContextId> {
|
||||
self.fetched_urls.get(url).copied()
|
||||
}
|
||||
|
||||
/// Replaces the context that matches the ID of the new context, if any match.
|
||||
fn replace_context(&mut self, new_context: Context) {
|
||||
let id = new_context.id();
|
||||
for context in self.context.iter_mut() {
|
||||
if context.id() == id {
|
||||
*context = new_context;
|
||||
break;
|
||||
pub fn duplicated_names(&self) -> HashSet<SharedString> {
|
||||
let mut seen = HashSet::default();
|
||||
let mut dupes = HashSet::default();
|
||||
|
||||
for context in self.context().iter() {
|
||||
if !seen.insert(&context.name) {
|
||||
dupes.insert(context.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_paths(&self, cx: &AppContext) -> HashSet<PathBuf> {
|
||||
self.context
|
||||
.iter()
|
||||
.filter_map(|context| match context {
|
||||
Context::File(file) => {
|
||||
let buffer = file.context_buffer.buffer.read(cx);
|
||||
buffer_path_log_err(buffer).map(|p| p.to_path_buf())
|
||||
}
|
||||
Context::Directory(_) | Context::FetchedUrl(_) | Context::Thread(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn thread_ids(&self) -> HashSet<ThreadId> {
|
||||
self.threads.keys().cloned().collect()
|
||||
dupes
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FileInclusion {
|
||||
pub enum IncludedFile {
|
||||
Direct(ContextId),
|
||||
InDirectory(PathBuf),
|
||||
}
|
||||
|
||||
// ContextBuffer without text.
|
||||
struct BufferInfo {
|
||||
buffer_model: Model<Buffer>,
|
||||
id: BufferId,
|
||||
version: clock::Global,
|
||||
}
|
||||
pub(crate) fn push_fenced_codeblock(path: &Path, content: String, buffer: &mut String) {
|
||||
buffer.reserve(content.len() + 64);
|
||||
|
||||
fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
|
||||
ContextBuffer {
|
||||
id: info.id,
|
||||
buffer: info.buffer_model,
|
||||
version: info.version,
|
||||
text,
|
||||
write!(buffer, "```").unwrap();
|
||||
|
||||
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
||||
write!(buffer, "{} ", extension).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_buffer_info_and_text(
|
||||
path: Arc<Path>,
|
||||
buffer_model: Model<Buffer>,
|
||||
buffer: &Buffer,
|
||||
cx: AsyncAppContext,
|
||||
) -> (BufferInfo, Task<SharedString>) {
|
||||
let buffer_info = BufferInfo {
|
||||
id: buffer.remote_id(),
|
||||
buffer_model,
|
||||
version: buffer.version(),
|
||||
};
|
||||
// Important to collect version at the same time as content so that staleness logic is correct.
|
||||
let content = buffer.as_rope().clone();
|
||||
let text_task = cx
|
||||
.background_executor()
|
||||
.spawn(async move { to_fenced_codeblock(&path, content) });
|
||||
(buffer_info, text_task)
|
||||
}
|
||||
|
||||
pub fn buffer_path_log_err(buffer: &Buffer) -> Option<Arc<Path>> {
|
||||
if let Some(file) = buffer.file() {
|
||||
Some(file.path().clone())
|
||||
} else {
|
||||
log::error!("Buffer that had a path unexpectedly no longer has a path.");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_fenced_codeblock(path: &Path, content: Rope) -> SharedString {
|
||||
let path_extension = path.extension().and_then(|ext| ext.to_str());
|
||||
let path_string = path.to_string_lossy();
|
||||
let capacity = 3
|
||||
+ path_extension.map_or(0, |extension| extension.len() + 1)
|
||||
+ path_string.len()
|
||||
+ 1
|
||||
+ content.len()
|
||||
+ 5;
|
||||
let mut buffer = String::with_capacity(capacity);
|
||||
|
||||
buffer.push_str("```");
|
||||
|
||||
if let Some(extension) = path_extension {
|
||||
buffer.push_str(extension);
|
||||
buffer.push(' ');
|
||||
}
|
||||
buffer.push_str(&path_string);
|
||||
write!(buffer, "{}", path.display()).unwrap();
|
||||
|
||||
buffer.push('\n');
|
||||
for chunk in content.chunks() {
|
||||
buffer.push_str(&chunk);
|
||||
}
|
||||
buffer.push_str(&content);
|
||||
|
||||
if !buffer.ends_with('\n') {
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer.push_str("```\n");
|
||||
|
||||
debug_assert!(
|
||||
buffer.len() == capacity - 1 || buffer.len() == capacity,
|
||||
"to_fenced_codeblock calculated capacity of {}, but length was {}",
|
||||
capacity,
|
||||
buffer.len(),
|
||||
);
|
||||
|
||||
buffer.into()
|
||||
}
|
||||
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
if entry.is_dir() {
|
||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||
} else if entry.is_file() {
|
||||
files.push(entry.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
pub fn refresh_context_store_text(
|
||||
context_store: Model<ContextStore>,
|
||||
cx: &AppContext,
|
||||
) -> impl Future<Output = ()> {
|
||||
let mut tasks = Vec::new();
|
||||
for context in &context_store.read(cx).context {
|
||||
match context {
|
||||
Context::File(file_context) => {
|
||||
let context_store = context_store.clone();
|
||||
if let Some(task) = refresh_file_text(context_store, file_context, cx) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
Context::Directory(directory_context) => {
|
||||
let context_store = context_store.clone();
|
||||
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
Context::Thread(thread_context) => {
|
||||
let context_store = context_store.clone();
|
||||
tasks.push(refresh_thread_text(context_store, thread_context, cx));
|
||||
}
|
||||
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful,
|
||||
// and doing the caching properly could be tricky (unless it's already handled by
|
||||
// the HttpClient?).
|
||||
Context::FetchedUrl(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
future::join_all(tasks).map(|_| ())
|
||||
}
|
||||
|
||||
fn refresh_file_text(
|
||||
context_store: Model<ContextStore>,
|
||||
file_context: &FileContext,
|
||||
cx: &AppContext,
|
||||
) -> Option<Task<()>> {
|
||||
let id = file_context.id;
|
||||
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
||||
if let Some(task) = task {
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let context_buffer = task.await;
|
||||
context_store
|
||||
.update(&mut cx, |context_store, _| {
|
||||
let new_file_context = FileContext { id, context_buffer };
|
||||
context_store.replace_context(Context::File(new_file_context));
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_directory_text(
|
||||
context_store: Model<ContextStore>,
|
||||
directory_context: &DirectoryContext,
|
||||
cx: &AppContext,
|
||||
) -> Option<Task<()>> {
|
||||
let mut stale = false;
|
||||
let futures = directory_context
|
||||
.context_buffers
|
||||
.iter()
|
||||
.map(|context_buffer| {
|
||||
if let Some(refresh_task) = refresh_context_buffer(context_buffer, cx) {
|
||||
stale = true;
|
||||
future::Either::Left(refresh_task)
|
||||
} else {
|
||||
future::Either::Right(future::ready((*context_buffer).clone()))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !stale {
|
||||
return None;
|
||||
}
|
||||
|
||||
let context_buffers = future::join_all(futures);
|
||||
|
||||
let id = directory_context.snapshot.id;
|
||||
let path = directory_context.path.clone();
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let context_buffers = context_buffers.await;
|
||||
context_store
|
||||
.update(&mut cx, |context_store, _| {
|
||||
let new_directory_context = DirectoryContext::new(id, &path, context_buffers);
|
||||
context_store.replace_context(Context::Directory(new_directory_context));
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
}
|
||||
|
||||
fn refresh_thread_text(
|
||||
context_store: Model<ContextStore>,
|
||||
thread_context: &ThreadContext,
|
||||
cx: &AppContext,
|
||||
) -> Task<()> {
|
||||
let id = thread_context.id;
|
||||
let thread = thread_context.thread.clone();
|
||||
cx.spawn(move |mut cx| async move {
|
||||
context_store
|
||||
.update(&mut cx, |context_store, cx| {
|
||||
let text = thread.read(cx).text().into();
|
||||
context_store.replace_context(Context::Thread(ThreadContext { id, thread, text }));
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh_context_buffer(
|
||||
context_buffer: &ContextBuffer,
|
||||
cx: &AppContext,
|
||||
) -> Option<impl Future<Output = ContextBuffer>> {
|
||||
let buffer = context_buffer.buffer.read(cx);
|
||||
let path = buffer_path_log_err(buffer)?;
|
||||
if buffer.version.changed_since(&context_buffer.version) {
|
||||
let (buffer_info, text_task) = collect_buffer_info_and_text(
|
||||
path,
|
||||
context_buffer.buffer.clone(),
|
||||
buffer,
|
||||
cx.to_async(),
|
||||
);
|
||||
Some(text_task.map(move |text| make_context_buffer(buffer_info, text)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use collections::HashSet;
|
||||
use editor::Editor;
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{
|
||||
AppContext, Bounds, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
|
||||
Subscription, View, WeakModel, WeakView,
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, Model, Subscription, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
@@ -18,29 +15,24 @@ use crate::context_store::ContextStore;
|
||||
use crate::thread::Thread;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::ContextPill;
|
||||
use crate::{
|
||||
AcceptSuggestedContext, AssistantPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||
RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
|
||||
};
|
||||
use crate::{AssistantPanel, RemoveAllContext, ToggleContextPicker};
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Model<ContextStore>,
|
||||
pub context_picker: View<ContextPicker>,
|
||||
context_picker: View<ContextPicker>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
focus_handle: FocusHandle,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
workspace: WeakView<Workspace>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
focused_index: Option<usize>,
|
||||
children_bounds: Option<Vec<Bounds<Pixels>>>,
|
||||
_context_picker_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl ContextStrip {
|
||||
pub fn new(
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
focus_handle: FocusHandle,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -50,19 +42,13 @@ impl ContextStrip {
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
context_store.downgrade(),
|
||||
editor.clone(),
|
||||
ConfirmBehavior::KeepOpen,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.subscribe(&context_picker, Self::handle_context_picker_event),
|
||||
cx.on_focus(&focus_handle, Self::handle_focus),
|
||||
cx.on_blur(&focus_handle, Self::handle_blur),
|
||||
];
|
||||
let context_picker_subscription =
|
||||
cx.subscribe(&context_picker, Self::handle_context_picker_event);
|
||||
|
||||
Self {
|
||||
context_store: context_store.clone(),
|
||||
@@ -71,9 +57,7 @@ impl ContextStrip {
|
||||
focus_handle,
|
||||
suggest_context_kind,
|
||||
workspace,
|
||||
_subscriptions: subscriptions,
|
||||
focused_index: None,
|
||||
children_bounds: None,
|
||||
_context_picker_subscription: context_picker_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,17 +73,11 @@ impl ContextStrip {
|
||||
let active_item = workspace.read(cx).active_item(cx)?;
|
||||
|
||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||
let active_buffer_model = editor.buffer().read(cx).as_singleton()?;
|
||||
let active_buffer = active_buffer_model.read(cx);
|
||||
let active_buffer = editor.buffer().read(cx).as_singleton()?;
|
||||
|
||||
let path = active_buffer.file()?.path();
|
||||
let path = active_buffer.read(cx).file()?.path();
|
||||
|
||||
if self
|
||||
.context_store
|
||||
.read(cx)
|
||||
.will_include_buffer(active_buffer.remote_id(), path)
|
||||
.is_some()
|
||||
{
|
||||
if self.context_store.read(cx).included_file(path).is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -108,20 +86,13 @@ impl ContextStrip {
|
||||
None => path.to_string_lossy().into_owned().into(),
|
||||
};
|
||||
|
||||
let icon_path = FileIcons::get_icon(path, cx);
|
||||
|
||||
Some(SuggestedContext::File {
|
||||
name,
|
||||
buffer: active_buffer_model.downgrade(),
|
||||
icon_path,
|
||||
buffer: active_buffer.downgrade(),
|
||||
})
|
||||
}
|
||||
|
||||
fn suggested_thread(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
if !self.context_picker.read(cx).allow_threads() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let active_thread = workspace
|
||||
.read(cx)
|
||||
@@ -135,14 +106,14 @@ impl ContextStrip {
|
||||
if self
|
||||
.context_store
|
||||
.read(cx)
|
||||
.includes_thread(active_thread.id())
|
||||
.included_thread(active_thread.id())
|
||||
.is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SuggestedContext::Thread {
|
||||
name: active_thread.summary_or_default(),
|
||||
name: active_thread.summary().unwrap_or("New Thread".into()),
|
||||
thread: weak_active_thread,
|
||||
})
|
||||
}
|
||||
@@ -155,242 +126,25 @@ impl ContextStrip {
|
||||
) {
|
||||
cx.emit(ContextStripEvent::PickerDismissed);
|
||||
}
|
||||
|
||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.focused_index = self.last_pill_index();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.focused_index = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_left(&mut self, _: &FocusLeft, cx: &mut ViewContext<Self>) {
|
||||
self.focused_index = match self.focused_index {
|
||||
Some(index) if index > 0 => Some(index - 1),
|
||||
_ => self.last_pill_index(),
|
||||
};
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_right(&mut self, _: &FocusRight, cx: &mut ViewContext<Self>) {
|
||||
let Some(last_index) = self.last_pill_index() else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.focused_index = match self.focused_index {
|
||||
Some(index) if index < last_index => Some(index + 1),
|
||||
_ => Some(0),
|
||||
};
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_up(&mut self, _: &FocusUp, cx: &mut ViewContext<Self>) {
|
||||
let Some(focused_index) = self.focused_index else {
|
||||
return;
|
||||
};
|
||||
|
||||
if focused_index == 0 {
|
||||
return cx.emit(ContextStripEvent::BlurredUp);
|
||||
}
|
||||
|
||||
let Some((focused, pills)) = self.focused_bounds(focused_index) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let iter = pills[..focused_index].iter().enumerate().rev();
|
||||
self.focused_index = Self::find_best_horizontal_match(focused, iter).or(Some(0));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_down(&mut self, _: &FocusDown, cx: &mut ViewContext<Self>) {
|
||||
let Some(focused_index) = self.focused_index else {
|
||||
return;
|
||||
};
|
||||
|
||||
let last_index = self.last_pill_index();
|
||||
|
||||
if self.focused_index == last_index {
|
||||
return cx.emit(ContextStripEvent::BlurredDown);
|
||||
}
|
||||
|
||||
let Some((focused, pills)) = self.focused_bounds(focused_index) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let iter = pills.iter().enumerate().skip(focused_index + 1);
|
||||
self.focused_index = Self::find_best_horizontal_match(focused, iter).or(last_index);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focused_bounds(&self, focused: usize) -> Option<(&Bounds<Pixels>, &[Bounds<Pixels>])> {
|
||||
let pill_bounds = self.pill_bounds()?;
|
||||
let focused = pill_bounds.get(focused)?;
|
||||
|
||||
Some((focused, pill_bounds))
|
||||
}
|
||||
|
||||
fn pill_bounds(&self) -> Option<&[Bounds<Pixels>]> {
|
||||
let bounds = self.children_bounds.as_ref()?;
|
||||
let eraser = if bounds.len() < 3 { 0 } else { 1 };
|
||||
let pills = &bounds[1..bounds.len() - eraser];
|
||||
|
||||
if pills.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(pills)
|
||||
}
|
||||
}
|
||||
|
||||
fn last_pill_index(&self) -> Option<usize> {
|
||||
Some(self.pill_bounds()?.len() - 1)
|
||||
}
|
||||
|
||||
fn find_best_horizontal_match<'a>(
|
||||
focused: &'a Bounds<Pixels>,
|
||||
iter: impl Iterator<Item = (usize, &'a Bounds<Pixels>)>,
|
||||
) -> Option<usize> {
|
||||
let mut best = None;
|
||||
|
||||
let focused_left = focused.left();
|
||||
let focused_right = focused.right();
|
||||
|
||||
for (index, probe) in iter {
|
||||
if probe.origin.y == focused.origin.y {
|
||||
continue;
|
||||
}
|
||||
|
||||
let overlap = probe.right().min(focused_right) - probe.left().max(focused_left);
|
||||
|
||||
best = match best {
|
||||
Some((_, prev_overlap, y)) if probe.origin.y != y || prev_overlap > overlap => {
|
||||
break;
|
||||
}
|
||||
Some(_) | None => Some((index, overlap, probe.origin.y)),
|
||||
};
|
||||
}
|
||||
|
||||
best.map(|(index, _, _)| index)
|
||||
}
|
||||
|
||||
fn remove_focused_context(&mut self, _: &RemoveFocusedContext, cx: &mut ViewContext<Self>) {
|
||||
if let Some(index) = self.focused_index {
|
||||
let mut is_empty = false;
|
||||
|
||||
self.context_store.update(cx, |this, _cx| {
|
||||
if let Some(item) = this.context().get(index) {
|
||||
this.remove_context(item.id());
|
||||
}
|
||||
|
||||
is_empty = this.context().is_empty();
|
||||
});
|
||||
|
||||
if is_empty {
|
||||
cx.emit(ContextStripEvent::BlurredEmpty);
|
||||
} else {
|
||||
self.focused_index = Some(index.saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suggested_focused<T>(&self, context: &Vec<T>) -> bool {
|
||||
// We only suggest one item after the actual context
|
||||
self.focused_index == Some(context.len())
|
||||
}
|
||||
|
||||
fn accept_suggested_context(&mut self, _: &AcceptSuggestedContext, cx: &mut ViewContext<Self>) {
|
||||
if let Some(suggested) = self.suggested_context(cx) {
|
||||
let context_store = self.context_store.read(cx);
|
||||
|
||||
if self.is_suggested_focused(context_store.context()) {
|
||||
self.add_suggested_context(&suggested, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_suggested_context(&mut self, suggested: &SuggestedContext, cx: &mut ViewContext<Self>) {
|
||||
let task = self.context_store.update(cx, |context_store, cx| {
|
||||
context_store.accept_suggested_context(&suggested, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
match task.await.notify_async_err(&mut cx) {
|
||||
None => {}
|
||||
Some(()) => {
|
||||
if let Some(this) = this.upgrade() {
|
||||
this.update(&mut cx, |_, cx| cx.notify())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ContextStrip {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextStrip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let context_store = self.context_store.read(cx);
|
||||
let context = context_store
|
||||
.context()
|
||||
.iter()
|
||||
.flat_map(|context| context.snapshot(cx))
|
||||
.collect::<Vec<_>>();
|
||||
let context = context_store.context().clone();
|
||||
let context_picker = self.context_picker.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
let suggested_context = self.suggested_context(cx);
|
||||
|
||||
let dupe_names = context
|
||||
.iter()
|
||||
.map(|context| context.name.clone())
|
||||
.sorted()
|
||||
.tuple_windows()
|
||||
.filter(|(a, b)| a == b)
|
||||
.map(|(a, _)| a)
|
||||
.collect::<HashSet<SharedString>>();
|
||||
let dupe_names = context_store.duplicated_names();
|
||||
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
.track_focus(&focus_handle)
|
||||
.key_context("ContextStrip")
|
||||
.on_action(cx.listener(Self::focus_up))
|
||||
.on_action(cx.listener(Self::focus_right))
|
||||
.on_action(cx.listener(Self::focus_down))
|
||||
.on_action(cx.listener(Self::focus_left))
|
||||
.on_action(cx.listener(Self::remove_focused_context))
|
||||
.on_action(cx.listener(Self::accept_suggested_context))
|
||||
.on_children_prepainted({
|
||||
let view = cx.view().downgrade();
|
||||
move |children_bounds, cx| {
|
||||
view.update(cx, |this, _| {
|
||||
this.children_bounds = Some(children_bounds);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.child(
|
||||
PopoverMenu::new("context-picker")
|
||||
.menu(move |cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.init(cx);
|
||||
});
|
||||
|
||||
Some(context_picker.clone())
|
||||
})
|
||||
.menu(move |_cx| Some(context_picker.clone()))
|
||||
.trigger(
|
||||
IconButton::new("add-context", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -412,7 +166,7 @@ impl Render for ContextStrip {
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-2.0),
|
||||
y: px(-16.0),
|
||||
})
|
||||
.with_handle(self.context_picker_menu_handle.clone()),
|
||||
)
|
||||
@@ -435,39 +189,37 @@ impl Render for ContextStrip {
|
||||
)
|
||||
}
|
||||
})
|
||||
.children(context.iter().enumerate().map(|(i, context)| {
|
||||
ContextPill::added(
|
||||
.children(context.iter().map(|context| {
|
||||
ContextPill::new_added(
|
||||
context.clone(),
|
||||
dupe_names.contains(&context.name),
|
||||
self.focused_index == Some(i),
|
||||
Some({
|
||||
let id = context.id;
|
||||
let context = context.clone();
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |this, _cx| {
|
||||
this.remove_context(id);
|
||||
this.remove_context(&context.id);
|
||||
});
|
||||
cx.notify();
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.on_click(Rc::new(cx.listener(move |this, _, cx| {
|
||||
this.focused_index = Some(i);
|
||||
cx.notify();
|
||||
})))
|
||||
}))
|
||||
.when_some(suggested_context, |el, suggested| {
|
||||
el.child(
|
||||
ContextPill::suggested(
|
||||
suggested.name().clone(),
|
||||
suggested.icon_path(),
|
||||
suggested.kind(),
|
||||
self.is_suggested_focused(&context),
|
||||
)
|
||||
.on_click(Rc::new(cx.listener(move |this, _event, cx| {
|
||||
this.add_suggested_context(&suggested, cx);
|
||||
}))),
|
||||
)
|
||||
el.child(ContextPill::new_suggested(
|
||||
suggested.name().clone(),
|
||||
suggested.kind(),
|
||||
{
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |context_store, cx| {
|
||||
suggested.accept(context_store, cx);
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}))
|
||||
},
|
||||
))
|
||||
})
|
||||
.when(!context.is_empty(), {
|
||||
move |parent| {
|
||||
@@ -499,9 +251,6 @@ impl Render for ContextStrip {
|
||||
|
||||
pub enum ContextStripEvent {
|
||||
PickerDismissed,
|
||||
BlurredEmpty,
|
||||
BlurredDown,
|
||||
BlurredUp,
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextStripEvent> for ContextStrip {}
|
||||
@@ -515,7 +264,6 @@ pub enum SuggestContextKind {
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakModel<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
@@ -532,10 +280,18 @@ impl SuggestedContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_path(&self) -> Option<SharedString> {
|
||||
pub fn accept(&self, context_store: &mut ContextStore, cx: &mut AppContext) {
|
||||
match self {
|
||||
Self::File { icon_path, .. } => icon_path.clone(),
|
||||
Self::Thread { .. } => None,
|
||||
Self::File { buffer, name: _ } => {
|
||||
if let Some(buffer) = buffer.upgrade() {
|
||||
context_store.insert_file(buffer.read(cx));
|
||||
};
|
||||
}
|
||||
Self::Thread { thread, name: _ } => {
|
||||
if let Some(thread) = thread.upgrade() {
|
||||
context_store.insert_thread(thread.read(cx));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::cmp;
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::AssistantPanel;
|
||||
use crate::{
|
||||
assistant_settings::AssistantSettings, prompts::PromptBuilder,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||
use editor::{
|
||||
@@ -17,8 +19,9 @@ use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||
GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
};
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagViewExt as _};
|
||||
use fs::Fs;
|
||||
use util::ResultExt;
|
||||
|
||||
use gpui::{
|
||||
point, AppContext, FocusableView, Global, HighlightStyle, Model, Subscription, Task,
|
||||
UpdateGlobal, View, ViewContext, WeakModel, WeakView, WindowContext,
|
||||
@@ -29,24 +32,16 @@ use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
use prompt_library::PromptBuilder;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{cmp, mem, ops::Range, rc::Rc, sync::Arc};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use ui::prelude::*;
|
||||
use util::RangeExt;
|
||||
use util::ResultExt;
|
||||
use workspace::{dock::Panel, ShowConfiguration};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
|
||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
||||
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::AssistantPanel;
|
||||
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -58,16 +53,7 @@ pub fn init(
|
||||
let workspace = cx.view().clone();
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
inline_assistant.register_workspace(&workspace, cx)
|
||||
});
|
||||
|
||||
cx.observe_flag::<Assistant2FeatureFlag, _>({
|
||||
|is_assistant2_enabled, _view, cx| {
|
||||
InlineAssistant::update_global(cx, |inline_assistant, _cx| {
|
||||
inline_assistant.is_assistant2_enabled = is_assistant2_enabled;
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -90,7 +76,6 @@ pub struct InlineAssistant {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
is_assistant2_enabled: bool,
|
||||
}
|
||||
|
||||
impl Global for InlineAssistant {}
|
||||
@@ -112,7 +97,6 @@ impl InlineAssistant {
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
fs,
|
||||
is_assistant2_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,38 +157,28 @@ impl InlineAssistant {
|
||||
item: &dyn ItemHandle,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let is_assistant2_enabled = self.is_assistant2_enabled;
|
||||
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
if is_assistant2_enabled {
|
||||
let thread_store = workspace
|
||||
.read(cx)
|
||||
.panel::<AssistantPanel>(cx)
|
||||
.map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade());
|
||||
let thread_store = workspace
|
||||
.read(cx)
|
||||
.panel::<AssistantPanel>(cx)
|
||||
.map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade());
|
||||
|
||||
editor.add_code_action_provider(
|
||||
Rc::new(AssistantCodeActionProvider {
|
||||
editor: cx.view().downgrade(),
|
||||
workspace: workspace.downgrade(),
|
||||
thread_store,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Remove the Assistant1 code action provider, as it still might be registered.
|
||||
editor.remove_code_action_provider("assistant".into(), cx);
|
||||
} else {
|
||||
editor
|
||||
.remove_code_action_provider(ASSISTANT_CODE_ACTION_PROVIDER_ID.into(), cx);
|
||||
}
|
||||
editor.push_code_action_provider(
|
||||
Rc::new(AssistantCodeActionProvider {
|
||||
editor: cx.view().downgrade(),
|
||||
workspace: workspace.downgrade(),
|
||||
thread_store,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline_assist(
|
||||
workspace: &mut Workspace,
|
||||
_action: &zed_actions::assistant::InlineAssist,
|
||||
_action: &zed_actions::InlineAssist,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
@@ -361,7 +335,7 @@ impl InlineAssistant {
|
||||
let mut assist_to_focus = None;
|
||||
for range in codegen_ranges {
|
||||
let assist_id = self.next_assist_id.post_inc();
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
BufferCodegen::new(
|
||||
editor.read(cx).buffer().clone(),
|
||||
@@ -471,7 +445,7 @@ impl InlineAssistant {
|
||||
range.end = range.end.bias_right(&snapshot);
|
||||
}
|
||||
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
|
||||
let codegen = cx.new_model(|cx| {
|
||||
BufferCodegen::new(
|
||||
@@ -1280,7 +1254,6 @@ impl InlineAssistant {
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_show_scrollbars(false, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_inline_completions(Some(false), cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
@@ -1600,13 +1573,7 @@ struct AssistantCodeActionProvider {
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
}
|
||||
|
||||
const ASSISTANT_CODE_ACTION_PROVIDER_ID: &str = "assistant2";
|
||||
|
||||
impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
fn id(&self) -> Arc<str> {
|
||||
ASSISTANT_CODE_ACTION_PROVIDER_ID.into()
|
||||
}
|
||||
|
||||
fn code_actions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
@@ -270,10 +270,9 @@ impl<T: 'static> PromptEditor<T> {
|
||||
PromptEditorMode::Terminal { .. } => "Generate",
|
||||
};
|
||||
|
||||
let assistant_panel_keybinding =
|
||||
ui::text_for_action(&zed_actions::assistant::ToggleFocus, cx)
|
||||
.map(|keybinding| format!("{keybinding} to chat ― "))
|
||||
.unwrap_or_default();
|
||||
let assistant_panel_keybinding = ui::text_for_action(&crate::ToggleFocus, cx)
|
||||
.map(|keybinding| format!("{keybinding} to chat ― "))
|
||||
.unwrap_or_default();
|
||||
|
||||
format!("{action}… ({assistant_panel_keybinding}↓↑ for history)")
|
||||
}
|
||||
@@ -416,8 +415,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cx.focus_view(&self.context_strip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,18 +738,11 @@ impl<T: 'static> PromptEditor<T> {
|
||||
fn handle_context_strip_event(
|
||||
&mut self,
|
||||
_context_strip: View<ContextStrip>,
|
||||
event: &ContextStripEvent,
|
||||
ContextStripEvent::PickerDismissed: &ContextStripEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ContextStripEvent::PickerDismissed
|
||||
| ContextStripEvent::BlurredEmpty
|
||||
| ContextStripEvent::BlurredUp => {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
ContextStripEvent::BlurredDown => {}
|
||||
}
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,8 +825,8 @@ impl PromptEditor<BufferCodegen> {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
prompt_editor.downgrade(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
@@ -852,12 +842,7 @@ impl PromptEditor<BufferCodegen> {
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
cx,
|
||||
)
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
@@ -987,8 +972,8 @@ impl PromptEditor<TerminalCodegen> {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
prompt_editor.downgrade(),
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
@@ -1004,12 +989,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
cx,
|
||||
)
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::actions::MoveUp;
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
pulsating_between, Animation, AnimationExt, AppContext, DismissEvent, FocusableView, Model,
|
||||
Subscription, TextStyle, View, WeakModel, WeakView,
|
||||
AppContext, DismissEvent, FocusableView, Model, Subscription, TextStyle, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use rope::Point;
|
||||
use settings::Settings;
|
||||
use std::time::Duration;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||
SwitchWithLabel,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::{refresh_context_store_text, ContextStore};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
use crate::{Chat, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
@@ -46,14 +47,14 @@ impl MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(10, cx);
|
||||
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
|
||||
editor.set_placeholder_text("Ask anything…", cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
|
||||
editor
|
||||
@@ -64,7 +65,6 @@ impl MessageEditor {
|
||||
workspace.clone(),
|
||||
Some(thread_store.clone()),
|
||||
context_store.downgrade(),
|
||||
editor.downgrade(),
|
||||
ConfirmBehavior::Close,
|
||||
cx,
|
||||
)
|
||||
@@ -74,8 +74,8 @@ impl MessageEditor {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
editor.downgrade(),
|
||||
Some(thread_store.clone()),
|
||||
editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::File,
|
||||
cx,
|
||||
@@ -100,12 +100,7 @@ impl MessageEditor {
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
editor.focus_handle(cx),
|
||||
cx,
|
||||
)
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
use_tools: false,
|
||||
@@ -117,11 +112,6 @@ impl MessageEditor {
|
||||
self.model_selector_menu_handle.toggle(cx)
|
||||
}
|
||||
|
||||
fn toggle_chat_mode(&mut self, _: &ChatMode, cx: &mut ViewContext<Self>) {
|
||||
self.use_tools = !self.use_tools;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
@@ -135,58 +125,53 @@ impl MessageEditor {
|
||||
self.send_to_model(RequestKind::Chat, cx);
|
||||
}
|
||||
|
||||
fn send_to_model(&mut self, request_kind: RequestKind, cx: &mut ViewContext<Self>) {
|
||||
fn send_to_model(
|
||||
&mut self,
|
||||
request_kind: RequestKind,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<()> {
|
||||
let provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
if provider
|
||||
.as_ref()
|
||||
.map_or(false, |provider| provider.must_accept_terms(cx))
|
||||
{
|
||||
cx.notify();
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let Some(model) = model_registry.active_model() else {
|
||||
return;
|
||||
};
|
||||
let model = model_registry.active_model()?;
|
||||
|
||||
let user_message = self.editor.update(cx, |editor, cx| {
|
||||
let text = editor.text(cx);
|
||||
editor.clear(cx);
|
||||
text
|
||||
});
|
||||
let context = self
|
||||
.context_store
|
||||
.update(cx, |this, _cx| this.context().clone());
|
||||
|
||||
let refresh_task = refresh_context_store_text(self.context_store.clone(), cx);
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message, context, cx);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
|
||||
let thread = self.thread.clone();
|
||||
let context_store = self.context_store.clone();
|
||||
let use_tools = self.use_tools;
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
refresh_task.await;
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| {
|
||||
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
|
||||
thread.insert_user_message(user_message, context, cx);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
if self.use_tools {
|
||||
request.tools = thread
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
if use_tools {
|
||||
request.tools = thread
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
thread.stream_completion(request, model, cx)
|
||||
});
|
||||
|
||||
thread.stream_completion(request, model, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_editor_event(
|
||||
@@ -226,28 +211,11 @@ impl MessageEditor {
|
||||
fn handle_context_strip_event(
|
||||
&mut self,
|
||||
_context_strip: View<ContextStrip>,
|
||||
event: &ContextStripEvent,
|
||||
ContextStripEvent::PickerDismissed: &ContextStripEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ContextStripEvent::PickerDismissed
|
||||
| ContextStripEvent::BlurredEmpty
|
||||
| ContextStripEvent::BlurredDown => {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
ContextStripEvent::BlurredUp => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
||||
if self.context_picker_menu_handle.is_deployed()
|
||||
|| self.inline_context_picker_menu_handle.is_deployed()
|
||||
{
|
||||
cx.propagate();
|
||||
} else {
|
||||
cx.focus_view(&self.context_strip);
|
||||
}
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,8 +232,6 @@ impl Render for MessageEditor {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let inline_context_picker = self.inline_context_picker.clone();
|
||||
let bg_color = cx.theme().colors().editor_background;
|
||||
let is_streaming_completion = self.thread.read(cx).is_streaming();
|
||||
let button_width = px(64.);
|
||||
|
||||
v_flex()
|
||||
.key_context("MessageEditor")
|
||||
@@ -273,8 +239,6 @@ impl Render for MessageEditor {
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::remove_all_context))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::toggle_chat_mode))
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
@@ -307,102 +271,46 @@ impl Render for MessageEditor {
|
||||
})
|
||||
.child(
|
||||
PopoverMenu::new("inline-context-picker")
|
||||
.menu(move |cx| {
|
||||
inline_context_picker.update(cx, |this, cx| {
|
||||
this.init(cx);
|
||||
});
|
||||
|
||||
Some(inline_context_picker.clone())
|
||||
})
|
||||
.menu(move |_cx| Some(inline_context_picker.clone()))
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-ThemeSettings::clamp_font_size(
|
||||
ThemeSettings::get_global(cx).ui_font_size,
|
||||
)
|
||||
.0 * 2.0)
|
||||
- px(4.0),
|
||||
y: px(-16.0),
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(SwitchWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools").size(LabelSize::Small),
|
||||
self.use_tools.into(),
|
||||
cx.listener(|this, selection, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||
false
|
||||
}
|
||||
};
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
Switch::new("use-tools", self.use_tools.into())
|
||||
.label("Tools")
|
||||
.on_click(cx.listener(|this, selection, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => false,
|
||||
};
|
||||
}))
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&ChatMode,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
if is_streaming_completion {
|
||||
ButtonLike::new("cancel-generation")
|
||||
.width(button_width.into())
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(
|
||||
Label::new("Cancel")
|
||||
.size(LabelSize::Small)
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(
|
||||
0.4, 0.8,
|
||||
)),
|
||||
|label, delta| label.alpha(delta),
|
||||
),
|
||||
)
|
||||
.children(
|
||||
KeyBinding::for_action_in(
|
||||
&editor::actions::Cancel,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle
|
||||
.dispatch_action(&editor::actions::Cancel, cx);
|
||||
})
|
||||
} else {
|
||||
ButtonLike::new("submit-message")
|
||||
.width(button_width.into())
|
||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new("Submit").size(LabelSize::Small))
|
||||
.children(
|
||||
KeyBinding::for_action_in(
|
||||
&Chat,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
),
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Submit").size(LabelSize::Small))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
})
|
||||
},
|
||||
)),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
312
crates/assistant2/src/prompts.rs
Normal file
312
crates/assistant2/src/prompts.rs
Normal file
@@ -0,0 +1,312 @@
|
||||
use anyhow::Result;
|
||||
use assets::Assets;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::AssetSource;
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use language::{BufferSnapshot, LanguageName, Point};
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||
use text::LineEnding;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ContentPromptDiagnosticContext {
|
||||
pub line_number: usize,
|
||||
pub error_message: String,
|
||||
pub code_content: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ContentPromptContext {
|
||||
pub content_type: String,
|
||||
pub language_name: Option<String>,
|
||||
pub is_insert: bool,
|
||||
pub is_truncated: bool,
|
||||
pub document_content: String,
|
||||
pub user_prompt: String,
|
||||
pub rewrite_section: Option<String>,
|
||||
pub diagnostic_errors: Vec<ContentPromptDiagnosticContext>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TerminalAssistantPromptContext {
|
||||
pub os: String,
|
||||
pub arch: String,
|
||||
pub shell: Option<String>,
|
||||
pub working_directory: Option<String>,
|
||||
pub latest_output: Vec<String>,
|
||||
pub user_prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ProjectSlashCommandPromptContext {
|
||||
pub context_buffer: String,
|
||||
}
|
||||
|
||||
pub struct PromptLoadingParams<'a> {
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub repo_path: Option<PathBuf>,
|
||||
pub cx: &'a gpui::AppContext,
|
||||
}
|
||||
|
||||
pub struct PromptBuilder {
|
||||
handlebars: Arc<Mutex<Handlebars<'static>>>,
|
||||
}
|
||||
|
||||
impl PromptBuilder {
|
||||
pub fn new(loading_params: Option<PromptLoadingParams>) -> Result<Self> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
Self::register_built_in_templates(&mut handlebars)?;
|
||||
|
||||
let handlebars = Arc::new(Mutex::new(handlebars));
|
||||
|
||||
if let Some(params) = loading_params {
|
||||
Self::watch_fs_for_template_overrides(params, handlebars.clone());
|
||||
}
|
||||
|
||||
Ok(Self { handlebars })
|
||||
}
|
||||
|
||||
/// Watches the filesystem for changes to prompt template overrides.
|
||||
///
|
||||
/// This function sets up a file watcher on the prompt templates directory. It performs
|
||||
/// an initial scan of the directory and registers any existing template overrides.
|
||||
/// Then it continuously monitors for changes, reloading templates as they are
|
||||
/// modified or added.
|
||||
///
|
||||
/// If the templates directory doesn't exist initially, it waits for it to be created.
|
||||
/// If the directory is removed, it restores the built-in templates and waits for the
|
||||
/// directory to be recreated.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `params` - A `PromptLoadingParams` struct containing the filesystem, repository path,
|
||||
/// and application context.
|
||||
/// * `handlebars` - An `Arc<Mutex<Handlebars>>` for registering and updating templates.
|
||||
fn watch_fs_for_template_overrides(
|
||||
params: PromptLoadingParams,
|
||||
handlebars: Arc<Mutex<Handlebars<'static>>>,
|
||||
) {
|
||||
let templates_dir = paths::prompt_overrides_dir(params.repo_path.as_deref());
|
||||
params.cx.background_executor()
|
||||
.spawn(async move {
|
||||
let Some(parent_dir) = templates_dir.parent() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut found_dir_once = false;
|
||||
loop {
|
||||
// Check if the templates directory exists and handle its status
|
||||
// If it exists, log its presence and check if it's a symlink
|
||||
// If it doesn't exist:
|
||||
// - Log that we're using built-in prompts
|
||||
// - Check if it's a broken symlink and log if so
|
||||
// - Set up a watcher to detect when it's created
|
||||
// After the first check, set the `found_dir_once` flag
|
||||
// This allows us to avoid logging when looping back around after deleting the prompt overrides directory.
|
||||
let dir_status = params.fs.is_dir(&templates_dir).await;
|
||||
let symlink_status = params.fs.read_link(&templates_dir).await.ok();
|
||||
if dir_status {
|
||||
let mut log_message = format!("Prompt template overrides directory found at {}", templates_dir.display());
|
||||
if let Some(target) = symlink_status {
|
||||
log_message.push_str(" -> ");
|
||||
log_message.push_str(&target.display().to_string());
|
||||
}
|
||||
log::info!("{}.", log_message);
|
||||
} else {
|
||||
if !found_dir_once {
|
||||
log::info!("No prompt template overrides directory found at {}. Using built-in prompts.", templates_dir.display());
|
||||
if let Some(target) = symlink_status {
|
||||
log::info!("Symlink found pointing to {}, but target is invalid.", target.display());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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(" -> ");
|
||||
log_message.push_str(&target.display().to_string());
|
||||
}
|
||||
log::info!("{}.", log_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
found_dir_once = true;
|
||||
|
||||
// Initial scan of the prompt overrides directory
|
||||
if let Ok(mut entries) = params.fs.read_dir(&templates_dir).await {
|
||||
while let Some(Ok(file_path)) = entries.next().await {
|
||||
if file_path.to_string_lossy().ends_with(".hbs") {
|
||||
if let Ok(content) = params.fs.load(&file_path).await {
|
||||
let file_name = file_path.file_stem().unwrap().to_string_lossy();
|
||||
log::debug!("Registering prompt template override: {}", file_name);
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watch both the parent directory and the template overrides directory:
|
||||
// - Monitor the parent directory to detect if the template overrides directory is deleted.
|
||||
// - Monitor the template overrides directory to re-register templates when they change.
|
||||
// Combine both watch streams into a single stream.
|
||||
let (parent_changes, parent_watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
|
||||
let (changes, watcher) = params.fs.watch(&templates_dir, Duration::from_secs(1)).await;
|
||||
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 !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();
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(watcher);
|
||||
drop(parent_watcher);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn register_built_in_templates(handlebars: &mut Handlebars) -> Result<()> {
|
||||
for path in Assets.list("prompts")? {
|
||||
if let Some(id) = path.split('/').last().and_then(|s| s.strip_suffix(".hbs")) {
|
||||
if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() {
|
||||
log::debug!("Registering built-in prompt template: {}", id);
|
||||
let prompt = String::from_utf8_lossy(prompt.as_ref());
|
||||
handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_inline_transformation_prompt(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
language_name: Option<&LanguageName>,
|
||||
buffer: BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
) -> Result<String, RenderError> {
|
||||
let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) {
|
||||
None | Some("Markdown" | "Plain Text") => "text",
|
||||
Some(_) => "code",
|
||||
};
|
||||
|
||||
const MAX_CTX: usize = 50000;
|
||||
let is_insert = range.is_empty();
|
||||
let mut is_truncated = false;
|
||||
|
||||
let before_range = 0..range.start;
|
||||
let truncated_before = if before_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
let start = buffer.clip_offset(range.start - MAX_CTX, text::Bias::Right);
|
||||
start..range.start
|
||||
} else {
|
||||
before_range
|
||||
};
|
||||
|
||||
let after_range = range.end..buffer.len();
|
||||
let truncated_after = if after_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
let end = buffer.clip_offset(range.end + MAX_CTX, text::Bias::Left);
|
||||
range.end..end
|
||||
} else {
|
||||
after_range
|
||||
};
|
||||
|
||||
let mut document_content = String::new();
|
||||
for chunk in buffer.text_for_range(truncated_before) {
|
||||
document_content.push_str(chunk);
|
||||
}
|
||||
if is_insert {
|
||||
document_content.push_str("<insert_here></insert_here>");
|
||||
} else {
|
||||
document_content.push_str("<rewrite_this>\n");
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
document_content.push_str(chunk);
|
||||
}
|
||||
document_content.push_str("\n</rewrite_this>");
|
||||
}
|
||||
for chunk in buffer.text_for_range(truncated_after) {
|
||||
document_content.push_str(chunk);
|
||||
}
|
||||
|
||||
let rewrite_section = if !is_insert {
|
||||
let mut section = String::new();
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
section.push_str(chunk);
|
||||
}
|
||||
Some(section)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let diagnostics = buffer.diagnostics_in_range::<_, Point>(range, false);
|
||||
let diagnostic_errors: Vec<ContentPromptDiagnosticContext> = diagnostics
|
||||
.map(|entry| {
|
||||
let start = entry.range.start;
|
||||
ContentPromptDiagnosticContext {
|
||||
line_number: (start.row + 1) as usize,
|
||||
error_message: entry.diagnostic.message.clone(),
|
||||
code_content: buffer.text_for_range(entry.range.clone()).collect(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let context = ContentPromptContext {
|
||||
content_type: content_type.to_string(),
|
||||
language_name: language_name.map(|s| s.to_string()),
|
||||
is_insert,
|
||||
is_truncated,
|
||||
document_content,
|
||||
user_prompt,
|
||||
rewrite_section,
|
||||
diagnostic_errors,
|
||||
};
|
||||
self.handlebars.lock().render("content_prompt", &context)
|
||||
}
|
||||
|
||||
pub fn generate_terminal_assistant_prompt(
|
||||
&self,
|
||||
user_prompt: &str,
|
||||
shell: Option<&str>,
|
||||
working_directory: Option<&str>,
|
||||
latest_output: &[String],
|
||||
) -> Result<String, RenderError> {
|
||||
let context = TerminalAssistantPromptContext {
|
||||
os: std::env::consts::OS.to_string(),
|
||||
arch: std::env::consts::ARCH.to_string(),
|
||||
shell: shell.map(|s| s.to_string()),
|
||||
working_directory: working_directory.map(|s| s.to_string()),
|
||||
latest_output: latest_output.to_vec(),
|
||||
user_prompt: user_prompt.to_string(),
|
||||
};
|
||||
|
||||
self.handlebars
|
||||
.lock()
|
||||
.render("terminal_assistant_prompt", &context)
|
||||
}
|
||||
}
|
||||
1102
crates/assistant2/src/streaming_diff.rs
Normal file
1102
crates/assistant2/src/streaming_diff.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ use crate::context_store::ContextStore;
|
||||
use crate::inline_prompt_editor::{
|
||||
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
||||
};
|
||||
use crate::prompts::PromptBuilder;
|
||||
use crate::terminal_codegen::{CodegenEvent, TerminalCodegen, CLEAR_INPUT};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use anyhow::{Context as _, Result};
|
||||
@@ -19,7 +20,6 @@ use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_models::report_assistant_event;
|
||||
use prompt_library::PromptBuilder;
|
||||
use std::sync::Arc;
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::TerminalView;
|
||||
@@ -78,7 +78,7 @@ impl TerminalInlineAssistant {
|
||||
let prompt_buffer = cx.new_model(|cx| {
|
||||
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
|
||||
});
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
@@ -245,10 +245,10 @@ impl TerminalInlineAssistant {
|
||||
cache: false,
|
||||
};
|
||||
|
||||
attach_context_to_message(
|
||||
&mut request_message,
|
||||
assist.context_store.read(cx).snapshot(cx),
|
||||
);
|
||||
let context = assist
|
||||
.context_store
|
||||
.update(cx, |this, _cx| this.context().clone());
|
||||
attach_context_to_message(&mut request_message, context);
|
||||
|
||||
request_message.content.push(prompt.into());
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
|
||||
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
||||
use crate::context::{attach_context_to_message, Context, ContextId};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RequestKind {
|
||||
@@ -64,7 +64,7 @@ pub struct Thread {
|
||||
pending_summary: Task<Option<()>>,
|
||||
messages: Vec<Message>,
|
||||
next_message_id: MessageId,
|
||||
context: BTreeMap<ContextId, ContextSnapshot>,
|
||||
context: HashMap<ContextId, Context>,
|
||||
context_by_message: HashMap<MessageId, Vec<ContextId>>,
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
@@ -83,7 +83,7 @@ impl Thread {
|
||||
pending_summary: Task::ready(None),
|
||||
messages: Vec::new(),
|
||||
next_message_id: MessageId(0),
|
||||
context: BTreeMap::default(),
|
||||
context: HashMap::default(),
|
||||
context_by_message: HashMap::default(),
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
@@ -114,11 +114,6 @@ impl Thread {
|
||||
self.summary.clone()
|
||||
}
|
||||
|
||||
pub fn summary_or_default(&self) -> SharedString {
|
||||
const DEFAULT: SharedString = SharedString::new_static("New Thread");
|
||||
self.summary.clone().unwrap_or(DEFAULT)
|
||||
}
|
||||
|
||||
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut ModelContext<Self>) {
|
||||
self.summary = Some(summary.into());
|
||||
cx.emit(ThreadEvent::SummaryChanged);
|
||||
@@ -132,15 +127,11 @@ impl Thread {
|
||||
self.messages.iter()
|
||||
}
|
||||
|
||||
pub fn is_streaming(&self) -> bool {
|
||||
!self.pending_completions.is_empty()
|
||||
}
|
||||
|
||||
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
|
||||
&self.tools
|
||||
}
|
||||
|
||||
pub fn context_for_message(&self, id: MessageId) -> Option<Vec<ContextSnapshot>> {
|
||||
pub fn context_for_message(&self, id: MessageId) -> Option<Vec<Context>> {
|
||||
let context = self.context_by_message.get(&id)?;
|
||||
Some(
|
||||
context
|
||||
@@ -158,7 +149,7 @@ impl Thread {
|
||||
pub fn insert_user_message(
|
||||
&mut self,
|
||||
text: impl Into<String>,
|
||||
context: Vec<ContextSnapshot>,
|
||||
context: Vec<Context>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let message_id = self.insert_message(Role::User, text, cx);
|
||||
@@ -308,13 +299,6 @@ impl Thread {
|
||||
last_message.id,
|
||||
chunk,
|
||||
));
|
||||
} else {
|
||||
// If we won't have an Assistant message yet, assume this chunk marks the beginning
|
||||
// of a new Assistant response.
|
||||
//
|
||||
// Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
|
||||
// will result in duplicating the text of the chunk in the rendered Markdown.
|
||||
thread.insert_message(Role::Assistant, chunk, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,7 +352,7 @@ impl Thread {
|
||||
let result = stream_completion.await;
|
||||
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| match result.as_ref() {
|
||||
.update(&mut cx, |_thread, cx| match result.as_ref() {
|
||||
Ok(stop_reason) => match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
cx.emit(ThreadEvent::UsePendingTools);
|
||||
@@ -391,8 +375,6 @@ impl Thread {
|
||||
SharedString::from(error_message.clone()),
|
||||
)));
|
||||
}
|
||||
|
||||
thread.cancel_last_completion();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
@@ -515,17 +497,6 @@ impl Thread {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancels the last pending completion, if there are any pending.
|
||||
///
|
||||
/// Returns whether a completion was canceled.
|
||||
pub fn cancel_last_completion(&mut self) -> bool {
|
||||
if let Some(_last_completion) = self.pending_completions.pop() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use gpui::{
|
||||
uniform_list, AppContext, FocusHandle, FocusableView, Model, ScrollStrategy,
|
||||
UniformListScrollHandle, WeakView,
|
||||
uniform_list, AppContext, FocusHandle, FocusableView, Model, UniformListScrollHandle, WeakView,
|
||||
};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
||||
|
||||
use crate::thread::Thread;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{AssistantPanel, RemoveSelectedThread};
|
||||
use crate::AssistantPanel;
|
||||
|
||||
pub struct ThreadHistory {
|
||||
focus_handle: FocusHandle,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl ThreadHistory {
|
||||
@@ -28,82 +26,6 @@ impl ThreadHistory {
|
||||
assistant_panel,
|
||||
thread_store,
|
||||
scroll_handle: UniformListScrollHandle::default(),
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||
|
||||
if count > 0 {
|
||||
if self.selected_index == 0 {
|
||||
self.set_selected_index(count - 1, cx);
|
||||
} else {
|
||||
self.set_selected_index(self.selected_index - 1, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
|
||||
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||
|
||||
if count > 0 {
|
||||
if self.selected_index == count - 1 {
|
||||
self.set_selected_index(0, cx);
|
||||
} else {
|
||||
self.set_selected_index(self.selected_index + 1, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
|
||||
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||
if count > 0 {
|
||||
self.set_selected_index(0, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
|
||||
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||
if count > 0 {
|
||||
self.set_selected_index(count - 1, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, index: usize, cx: &mut ViewContext<Self>) {
|
||||
self.selected_index = index;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Top);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||
|
||||
if let Some(thread) = threads.get(self.selected_index) {
|
||||
self.assistant_panel
|
||||
.update(cx, move |this, cx| {
|
||||
let thread_id = thread.read(cx).id().clone();
|
||||
this.open_thread(&thread_id, cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_selected_thread(&mut self, _: &RemoveSelectedThread, cx: &mut ViewContext<Self>) {
|
||||
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||
|
||||
if let Some(thread) = threads.get(self.selected_index) {
|
||||
self.assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
let thread_id = thread.read(cx).id().clone();
|
||||
this.delete_thread(&thread_id, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,21 +39,13 @@ impl FocusableView for ThreadHistory {
|
||||
impl Render for ThreadHistory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||
let selected_index = self.selected_index;
|
||||
|
||||
v_flex()
|
||||
.id("thread-history-container")
|
||||
.key_context("ThreadHistory")
|
||||
.track_focus(&self.focus_handle)
|
||||
.overflow_y_scroll()
|
||||
.size_full()
|
||||
.p_1()
|
||||
.on_action(cx.listener(Self::select_prev))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::remove_selected_thread))
|
||||
.map(|history| {
|
||||
if threads.is_empty() {
|
||||
history
|
||||
@@ -151,12 +65,10 @@ impl Render for ThreadHistory {
|
||||
move |history, range, _cx| {
|
||||
threads[range]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, thread)| {
|
||||
.map(|thread| {
|
||||
h_flex().w_full().pb_1().child(PastThread::new(
|
||||
thread.clone(),
|
||||
history.assistant_panel.clone(),
|
||||
selected_index == index,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
@@ -174,19 +86,13 @@ impl Render for ThreadHistory {
|
||||
pub struct PastThread {
|
||||
thread: Model<Thread>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl PastThread {
|
||||
pub fn new(
|
||||
thread: Model<Thread>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
selected: bool,
|
||||
) -> Self {
|
||||
pub fn new(thread: Model<Thread>, assistant_panel: WeakView<AssistantPanel>) -> Self {
|
||||
Self {
|
||||
thread,
|
||||
assistant_panel,
|
||||
selected,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,8 +100,12 @@ impl PastThread {
|
||||
impl RenderOnce for PastThread {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let (id, summary) = {
|
||||
const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
|
||||
let thread = self.thread.read(cx);
|
||||
(thread.id().clone(), thread.summary_or_default())
|
||||
(
|
||||
thread.id().clone(),
|
||||
thread.summary().unwrap_or(DEFAULT_SUMMARY),
|
||||
)
|
||||
};
|
||||
|
||||
let thread_timestamp = time_format::format_localized_timestamp(
|
||||
@@ -210,7 +120,6 @@ impl RenderOnce for PastThread {
|
||||
|
||||
ListItem::new(("past-thread", self.thread.entity_id()))
|
||||
.outlined()
|
||||
.toggle_state(self.selected)
|
||||
.start_slot(
|
||||
Icon::new(IconName::MessageCircle)
|
||||
.size(IconSize::Small)
|
||||
|
||||
@@ -52,14 +52,6 @@ impl ThreadStore {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the number of non-empty threads.
|
||||
pub fn non_empty_len(&self, cx: &AppContext) -> usize {
|
||||
self.threads
|
||||
.iter()
|
||||
.filter(|thread| !thread.read(cx).is_empty())
|
||||
.count()
|
||||
}
|
||||
|
||||
pub fn threads(&self, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
|
||||
let mut threads = self
|
||||
.threads
|
||||
|
||||
@@ -3,67 +3,41 @@ use std::rc::Rc;
|
||||
use gpui::ClickEvent;
|
||||
use ui::{prelude::*, IconButtonShape, Tooltip};
|
||||
|
||||
use crate::context::{ContextKind, ContextSnapshot};
|
||||
use crate::context::{Context, ContextKind};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub enum ContextPill {
|
||||
Added {
|
||||
context: ContextSnapshot,
|
||||
context: Context,
|
||||
dupe_name: bool,
|
||||
focused: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
},
|
||||
Suggested {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
kind: ContextKind,
|
||||
focused: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ContextPill {
|
||||
pub fn added(
|
||||
context: ContextSnapshot,
|
||||
pub fn new_added(
|
||||
context: Context,
|
||||
dupe_name: bool,
|
||||
focused: bool,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
) -> Self {
|
||||
Self::Added {
|
||||
context,
|
||||
dupe_name,
|
||||
on_remove,
|
||||
focused,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suggested(
|
||||
pub fn new_suggested(
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
kind: ContextKind,
|
||||
focused: bool,
|
||||
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
|
||||
) -> Self {
|
||||
Self::Suggested {
|
||||
name,
|
||||
icon_path,
|
||||
kind,
|
||||
focused,
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
|
||||
match &mut self {
|
||||
ContextPill::Added { on_click, .. } => {
|
||||
*on_click = Some(listener);
|
||||
}
|
||||
ContextPill::Suggested { on_click, .. } => {
|
||||
*on_click = Some(listener);
|
||||
}
|
||||
}
|
||||
self
|
||||
Self::Suggested { name, kind, on_add }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ElementId {
|
||||
@@ -75,27 +49,23 @@ impl ContextPill {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(&self) -> Icon {
|
||||
pub fn kind(&self) -> &ContextKind {
|
||||
match self {
|
||||
Self::Added { context, .. } => match &context.icon_path {
|
||||
Some(icon_path) => Icon::from_path(icon_path),
|
||||
None => Icon::new(context.kind.icon()),
|
||||
},
|
||||
Self::Suggested {
|
||||
icon_path: Some(icon_path),
|
||||
..
|
||||
} => Icon::from_path(icon_path),
|
||||
Self::Suggested {
|
||||
kind,
|
||||
icon_path: None,
|
||||
..
|
||||
} => Icon::new(kind.icon()),
|
||||
Self::Added { context, .. } => &context.kind,
|
||||
Self::Suggested { kind, .. } => kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ContextPill {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let icon = match &self.kind() {
|
||||
ContextKind::File => IconName::File,
|
||||
ContextKind::Directory => IconName::Folder,
|
||||
ContextKind::FetchedUrl => IconName::Globe,
|
||||
ContextKind::Thread => IconName::MessageCircle,
|
||||
};
|
||||
|
||||
let color = cx.theme().colors();
|
||||
|
||||
let base_pill = h_flex()
|
||||
@@ -105,22 +75,16 @@ impl RenderOnce for ContextPill {
|
||||
.border_1()
|
||||
.rounded_md()
|
||||
.gap_1()
|
||||
.child(self.icon().size(IconSize::XSmall).color(Color::Muted));
|
||||
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted));
|
||||
|
||||
match &self {
|
||||
ContextPill::Added {
|
||||
context,
|
||||
dupe_name,
|
||||
on_remove,
|
||||
focused,
|
||||
on_click,
|
||||
} => base_pill
|
||||
.bg(color.element_background)
|
||||
.border_color(if *focused {
|
||||
color.border_focused
|
||||
} else {
|
||||
color.border.opacity(0.5)
|
||||
})
|
||||
.border_color(color.border.opacity(0.5))
|
||||
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -153,25 +117,11 @@ impl RenderOnce for ContextPill {
|
||||
move |event, cx| on_remove(event, cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(on_click.as_ref(), |element, on_click| {
|
||||
let on_click = on_click.clone();
|
||||
element.on_click(move |event, cx| on_click(event, cx))
|
||||
}),
|
||||
ContextPill::Suggested {
|
||||
name,
|
||||
icon_path: _,
|
||||
kind,
|
||||
focused,
|
||||
on_click,
|
||||
} => base_pill
|
||||
ContextPill::Suggested { name, kind, on_add } => base_pill
|
||||
.cursor_pointer()
|
||||
.pr_1()
|
||||
.border_color(if *focused {
|
||||
color.border_focused
|
||||
} else {
|
||||
color.border_variant.opacity(0.5)
|
||||
})
|
||||
.border_color(color.border_variant.opacity(0.5))
|
||||
.hover(|style| style.bg(color.element_hover.opacity(0.5)))
|
||||
.child(
|
||||
Label::new(name.clone())
|
||||
@@ -179,16 +129,14 @@ impl RenderOnce for ContextPill {
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
div().px_0p5().child(
|
||||
Label::new(match kind {
|
||||
ContextKind::File => "Active Tab",
|
||||
ContextKind::Thread
|
||||
| ContextKind::Directory
|
||||
| ContextKind::FetchedUrl => "Active",
|
||||
})
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
Label::new(match kind {
|
||||
ContextKind::File => "Open File",
|
||||
ContextKind::Thread | ContextKind::Directory | ContextKind::FetchedUrl => {
|
||||
"Active"
|
||||
}
|
||||
})
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::Plus)
|
||||
@@ -196,9 +144,9 @@ impl RenderOnce for ContextPill {
|
||||
.into_any_element(),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
|
||||
.when_some(on_click.as_ref(), |element, on_click| {
|
||||
let on_click = on_click.clone();
|
||||
element.on_click(move |event, cx| on_click(event, cx))
|
||||
.on_click({
|
||||
let on_add = on_add.clone();
|
||||
move |event, cx| on_add(event, cx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
[package]
|
||||
name = "assistant_context_editor"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/assistant_context_editor.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
context_server.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
open_ai.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_library.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
strum.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
rand.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user