Compare commits
3 Commits
git-panel-
...
linux-scre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a03d59be7c | ||
|
|
42a125aa19 | ||
|
|
3e0630e227 |
4
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -26,8 +26,8 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: If applicable, add screenshots or screencasts of the incorrect state / behavior
|
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||||
description: Drag images / videos into the text input below
|
description: Drag issues into the text input below
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,3 @@
|
|||||||
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
|
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Language Request
|
- name: Language Request
|
||||||
|
|||||||
9
.github/workflows/ci.yml
vendored
@@ -129,9 +129,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo build --workspace --bins --all-features
|
cargo build --workspace --bins --all-features
|
||||||
cargo check -p gpui --features "macos-blade"
|
cargo check -p gpui --features "macos-blade"
|
||||||
cargo check -p workspace
|
cargo check -p workspace --features "livekit-cross-platform"
|
||||||
cargo build -p remote_server
|
cargo build -p remote_server
|
||||||
script/check-rust-livekit-macos
|
|
||||||
|
|
||||||
linux_tests:
|
linux_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@@ -163,10 +162,8 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
|
|
||||||
- name: Build other binaries and features
|
- name: Build Zed
|
||||||
run: |
|
run: cargo build -p zed
|
||||||
cargo build -p zed
|
|
||||||
cargo check -p workspace
|
|
||||||
|
|
||||||
build_remote_server:
|
build_remote_server:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
update_top_ranking_issues:
|
update_top_ranking_issues:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'zed-industries/zed'
|
if: github.repository_owner == 'zed-industries'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
update_top_ranking_issues:
|
update_top_ranking_issues:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'zed-industries/zed'
|
if: github.repository_owner == 'zed-industries'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
|
|||||||
8
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,28 +37,28 @@ jobs:
|
|||||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||||
|
|
||||||
- name: Deploy Docs
|
- name: Deploy Docs
|
||||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: pages deploy target/deploy --project-name=docs
|
command: pages deploy target/deploy --project-name=docs
|
||||||
|
|
||||||
- name: Deploy Install
|
- name: Deploy Install
|
||||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||||
|
|
||||||
- name: Deploy Docs Workers
|
- name: Deploy Docs Workers
|
||||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||||
|
|
||||||
- name: Deploy Install Workers
|
- name: Deploy Install Workers
|
||||||
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
|||||||
2
.github/workflows/release_nightly.yml
vendored
@@ -140,7 +140,7 @@ jobs:
|
|||||||
name: Create a Linux *.tar.gz bundle for ARM
|
name: Create a Linux *.tar.gz bundle for ARM
|
||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204-arm
|
- hosted-linux-arm-1
|
||||||
needs: tests
|
needs: tests
|
||||||
env:
|
env:
|
||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
|
|||||||
1632
Cargo.lock
generated
@@ -141,8 +141,6 @@ members = [
|
|||||||
"crates/worktree",
|
"crates/worktree",
|
||||||
"crates/zed",
|
"crates/zed",
|
||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
"crates/zeta",
|
|
||||||
"crates/git_ui",
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Extensions
|
# Extensions
|
||||||
@@ -228,7 +226,6 @@ fs = { path = "crates/fs" }
|
|||||||
fsevent = { path = "crates/fsevent" }
|
fsevent = { path = "crates/fsevent" }
|
||||||
fuzzy = { path = "crates/fuzzy" }
|
fuzzy = { path = "crates/fuzzy" }
|
||||||
git = { path = "crates/git" }
|
git = { path = "crates/git" }
|
||||||
git_ui = { path = "crates/git_ui" }
|
|
||||||
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||||
go_to_line = { path = "crates/go_to_line" }
|
go_to_line = { path = "crates/go_to_line" }
|
||||||
google_ai = { path = "crates/google_ai" }
|
google_ai = { path = "crates/google_ai" }
|
||||||
@@ -328,7 +325,6 @@ workspace = { path = "crates/workspace" }
|
|||||||
worktree = { path = "crates/worktree" }
|
worktree = { path = "crates/worktree" }
|
||||||
zed = { path = "crates/zed" }
|
zed = { path = "crates/zed" }
|
||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
zeta = { path = "crates/zeta" }
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# External crates
|
# External crates
|
||||||
@@ -362,6 +358,7 @@ cargo_metadata = "0.19"
|
|||||||
cargo_toml = "0.20"
|
cargo_toml = "0.20"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
|
clickhouse = "0.11.6"
|
||||||
cocoa = "0.26"
|
cocoa = "0.26"
|
||||||
cocoa-foundation = "0.2.0"
|
cocoa-foundation = "0.2.0"
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
@@ -444,6 +441,7 @@ rustc-demangle = "0.1.23"
|
|||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||||
rustls = "0.21.12"
|
rustls = "0.21.12"
|
||||||
rustls-native-certs = "0.8.0"
|
rustls-native-certs = "0.8.0"
|
||||||
|
scap = "0.0.7"
|
||||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
@@ -509,7 +507,7 @@ unindent = "0.1.7"
|
|||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
unicode-script = "0.5.7"
|
unicode-script = "0.5.7"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||||
wasmparser = "0.215"
|
wasmparser = "0.215"
|
||||||
wasm-encoder = "0.215"
|
wasm-encoder = "0.215"
|
||||||
wasmtime = { version = "24", default-features = false, features = [
|
wasmtime = { version = "24", default-features = false, features = [
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eraser">
|
|
||||||
<path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/>
|
|
||||||
<path d="M22 21H7"/><path d="m5 11 9 9"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 365 B |
@@ -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-file-diff"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M9 10h6"/><path d="M12 13V7"/><path d="M9 17h6"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 348 B |
@@ -59,11 +59,6 @@
|
|||||||
"gitignore": "vcs",
|
"gitignore": "vcs",
|
||||||
"gitkeep": "vcs",
|
"gitkeep": "vcs",
|
||||||
"gitmodules": "vcs",
|
"gitmodules": "vcs",
|
||||||
"TAG_EDITMSG": "vcs",
|
|
||||||
"MERGE_MSG": "vcs",
|
|
||||||
"COMMIT_EDITMSG": "vcs",
|
|
||||||
"NOTES_EDITMSG": "vcs",
|
|
||||||
"EDIT_DESCRIPTION": "vcs",
|
|
||||||
"gleam": "gleam",
|
"gleam": "gleam",
|
||||||
"go": "go",
|
"go": "go",
|
||||||
"gql": "graphql",
|
"gql": "graphql",
|
||||||
@@ -113,7 +108,6 @@
|
|||||||
"mdf": "storage",
|
"mdf": "storage",
|
||||||
"mdx": "document",
|
"mdx": "document",
|
||||||
"metadata": "code",
|
"metadata": "code",
|
||||||
"metal": "metal",
|
|
||||||
"mjs": "javascript",
|
"mjs": "javascript",
|
||||||
"mka": "audio",
|
"mka": "audio",
|
||||||
"mkv": "video",
|
"mkv": "video",
|
||||||
@@ -323,9 +317,6 @@
|
|||||||
"lua": {
|
"lua": {
|
||||||
"icon": "icons/file_icons/lua.svg"
|
"icon": "icons/file_icons/lua.svg"
|
||||||
},
|
},
|
||||||
"metal": {
|
|
||||||
"icon": "icons/file_icons/metal.svg"
|
|
||||||
},
|
|
||||||
"nim": {
|
"nim": {
|
||||||
"icon": "icons/file_icons/nim.svg"
|
"icon": "icons/file_icons/nim.svg"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.56 4.502 3.25 3.027V11.5h1.5V6.973l2.69 3.025 1.31 1.475V7.918l3.306 3.582h2.042L8.55 5.491 7.25 4.081V7.528L4.56 4.502Z" fill="#000"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 269 B |
@@ -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-git-branch"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 348 B |
@@ -1,12 +0,0 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_2131_1193)">
|
|
||||||
<circle cx="7" cy="7" r="6" stroke="black" stroke-width="1.5"/>
|
|
||||||
<path d="M6 10H7M8 10H7M7 10V7.1C7 7.04477 6.95523 7 6.9 7H6" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
<circle cx="7" cy="4.5" r="1" fill="black"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_2131_1193">
|
|
||||||
<rect width="14" height="14" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 479 B |
@@ -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-panel-left"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 289 B |
@@ -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-panel-right"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 291 B |
@@ -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-square-dot"><rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="12" cy="12" r="1"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 301 B |
@@ -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-square-minus"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M8 12h8"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 291 B |
@@ -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-square-plus"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M8 12h8"/><path d="M12 8v8"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 309 B |
@@ -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-thumbs-down"><path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 405 B |
@@ -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-thumbs-up"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 404 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M8.5 3L3 12H14L8.5 3Z" fill="black"/>
|
<path d="M8 4L4 12H12L8 4Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 150 B After Width: | Height: | Size: 155 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M5 4.5L12 11.5M12 4.5L5 11.5" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 177 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8 8.9V11C5.93097 11 5.06903 11 3 11V10.4L8 5.6V5H3V7.1" stroke="black" stroke-width="1.5"/>
|
|
||||||
<path d="M11 5L13 8L11 11" stroke="black" stroke-width="1.5"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 268 B |
@@ -468,21 +468,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && showing_completions",
|
"context": "Editor && showing_completions",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"enter": "editor::ConfirmCompletion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && !inline_completion && showing_completions",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"enter": "editor::ConfirmCompletion",
|
||||||
"tab": "editor::ComposeCompletion"
|
"tab": "editor::ComposeCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && inline_completion",
|
"context": "Editor && inline_completion && !showing_completions",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "editor::AcceptInlineCompletion"
|
"tab": "editor::AcceptInlineCompletion"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Standard macOS bindings
|
// Standard macOS bindings
|
||||||
{
|
{
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"up": "menu::SelectPrev",
|
"up": "menu::SelectPrev",
|
||||||
"shift-tab": "menu::SelectPrev",
|
"shift-tab": "menu::SelectPrev",
|
||||||
@@ -41,7 +40,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "editor::Cancel",
|
"escape": "editor::Cancel",
|
||||||
"backspace": "editor::Backspace",
|
"backspace": "editor::Backspace",
|
||||||
@@ -66,7 +64,6 @@
|
|||||||
"cmd-v": "editor::Paste",
|
"cmd-v": "editor::Paste",
|
||||||
"cmd-z": "editor::Undo",
|
"cmd-z": "editor::Undo",
|
||||||
"cmd-shift-z": "editor::Redo",
|
"cmd-shift-z": "editor::Redo",
|
||||||
"ctrl-shift-z": "zeta::RateCompletions",
|
|
||||||
"up": "editor::MoveUp",
|
"up": "editor::MoveUp",
|
||||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||||
"pageup": "editor::MovePageUp",
|
"pageup": "editor::MovePageUp",
|
||||||
@@ -134,7 +131,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"enter": "editor::Newline",
|
||||||
"shift-enter": "editor::Newline",
|
"shift-enter": "editor::Newline",
|
||||||
@@ -152,23 +148,20 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full && inline_completion",
|
"context": "Editor && mode == full && inline_completion",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-tab": "editor::NextInlineCompletion",
|
"alt-]": "editor::NextInlineCompletion",
|
||||||
"alt-shift-tab": "editor::PreviousInlineCompletion",
|
"alt-[": "editor::PreviousInlineCompletion",
|
||||||
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
"ctrl-right": "editor::AcceptPartialInlineCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && !inline_completion",
|
"context": "Editor && !inline_completion",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-tab": "editor::ShowInlineCompletion"
|
"alt-\\": "editor::ShowInlineCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == auto_height",
|
"context": "Editor && mode == auto_height",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "editor::Newline",
|
"ctrl-enter": "editor::Newline",
|
||||||
"shift-enter": "editor::Newline",
|
"shift-enter": "editor::Newline",
|
||||||
@@ -177,14 +170,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Markdown",
|
"context": "Markdown",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-c": "markdown::Copy"
|
"cmd-c": "markdown::Copy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && jupyter && !ContextEditor",
|
"context": "Editor && jupyter && !ContextEditor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-enter": "repl::Run",
|
"ctrl-shift-enter": "repl::Run",
|
||||||
"ctrl-alt-enter": "repl::RunInPlace"
|
"ctrl-alt-enter": "repl::RunInPlace"
|
||||||
@@ -192,7 +183,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "AssistantPanel",
|
"context": "AssistantPanel",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-k c": "assistant::CopyCode",
|
"cmd-k c": "assistant::CopyCode",
|
||||||
"cmd-g": "search::SelectNextMatch",
|
"cmd-g": "search::SelectNextMatch",
|
||||||
@@ -205,7 +195,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ContextEditor > Editor",
|
"context": "ContextEditor > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "assistant::Assist",
|
"cmd-enter": "assistant::Assist",
|
||||||
"cmd-shift-enter": "assistant::Edit",
|
"cmd-shift-enter": "assistant::Edit",
|
||||||
@@ -220,7 +209,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "AssistantPanel2",
|
"context": "AssistantPanel2",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-n": "assistant2::NewThread",
|
"cmd-n": "assistant2::NewThread",
|
||||||
"cmd-shift-h": "assistant2::OpenHistory"
|
"cmd-shift-h": "assistant2::OpenHistory"
|
||||||
@@ -228,14 +216,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "MessageEditor > Editor",
|
"context": "MessageEditor > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "assistant2::Chat"
|
"cmd-enter": "assistant2::Chat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "PromptLibrary",
|
"context": "PromptLibrary",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-n": "prompt_library::NewPrompt",
|
"cmd-n": "prompt_library::NewPrompt",
|
||||||
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
|
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
|
||||||
@@ -244,7 +230,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "buffer_search::Dismiss",
|
"escape": "buffer_search::Dismiss",
|
||||||
"tab": "buffer_search::FocusEditor",
|
"tab": "buffer_search::FocusEditor",
|
||||||
@@ -258,7 +243,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && in_replace > Editor",
|
"context": "BufferSearchBar && in_replace > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "search::ReplaceNext",
|
"enter": "search::ReplaceNext",
|
||||||
"cmd-enter": "search::ReplaceAll"
|
"cmd-enter": "search::ReplaceAll"
|
||||||
@@ -266,7 +250,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && !in_replace > Editor",
|
"context": "BufferSearchBar && !in_replace > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"up": "search::PreviousHistoryQuery",
|
"up": "search::PreviousHistoryQuery",
|
||||||
"down": "search::NextHistoryQuery"
|
"down": "search::NextHistoryQuery"
|
||||||
@@ -274,7 +257,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar",
|
"context": "ProjectSearchBar",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "project_search::ToggleFocus",
|
"escape": "project_search::ToggleFocus",
|
||||||
"cmd-shift-j": "project_search::ToggleFilters",
|
"cmd-shift-j": "project_search::ToggleFilters",
|
||||||
@@ -286,7 +268,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar > Editor",
|
"context": "ProjectSearchBar > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"up": "search::PreviousHistoryQuery",
|
"up": "search::PreviousHistoryQuery",
|
||||||
"down": "search::NextHistoryQuery"
|
"down": "search::NextHistoryQuery"
|
||||||
@@ -294,7 +275,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar && in_replace > Editor",
|
"context": "ProjectSearchBar && in_replace > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "search::ReplaceNext",
|
"enter": "search::ReplaceNext",
|
||||||
"cmd-enter": "search::ReplaceAll"
|
"cmd-enter": "search::ReplaceAll"
|
||||||
@@ -302,7 +282,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchView",
|
"context": "ProjectSearchView",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "project_search::ToggleFocus",
|
"escape": "project_search::ToggleFocus",
|
||||||
"cmd-shift-j": "project_search::ToggleFilters",
|
"cmd-shift-j": "project_search::ToggleFilters",
|
||||||
@@ -313,7 +292,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-{": "pane::ActivatePrevItem",
|
"cmd-{": "pane::ActivatePrevItem",
|
||||||
"cmd-}": "pane::ActivateNextItem",
|
"cmd-}": "pane::ActivateNextItem",
|
||||||
@@ -342,7 +320,6 @@
|
|||||||
// Bindings from VS Code
|
// Bindings from VS Code
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-[": "editor::Outdent",
|
"cmd-[": "editor::Outdent",
|
||||||
"cmd-]": "editor::Indent",
|
"cmd-]": "editor::Indent",
|
||||||
@@ -406,7 +383,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-o": "outline::Toggle",
|
"cmd-shift-o": "outline::Toggle",
|
||||||
"ctrl-g": "go_to_line::Toggle"
|
"ctrl-g": "go_to_line::Toggle"
|
||||||
@@ -414,7 +390,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-1": ["pane::ActivateItem", 0],
|
"ctrl-1": ["pane::ActivateItem", 0],
|
||||||
"ctrl-2": ["pane::ActivateItem", 1],
|
"ctrl-2": ["pane::ActivateItem", 1],
|
||||||
@@ -434,7 +409,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// Change the default action on `menu::Confirm` by setting the parameter
|
// Change the default action on `menu::Confirm` by setting the parameter
|
||||||
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
|
||||||
@@ -490,7 +464,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace && !Terminal",
|
"context": "Workspace && !Terminal",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-r": "task::Spawn",
|
"cmd-shift-r": "task::Spawn",
|
||||||
"cmd-alt-r": "task::Rerun",
|
"cmd-alt-r": "task::Rerun",
|
||||||
@@ -501,7 +474,6 @@
|
|||||||
// Bindings from Sublime Text
|
// Bindings from Sublime Text
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-j": "editor::JoinLines",
|
"ctrl-j": "editor::JoinLines",
|
||||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||||
@@ -521,7 +493,6 @@
|
|||||||
// Bindings from Atom
|
// Bindings from Atom
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-k up": "pane::SplitUp",
|
"cmd-k up": "pane::SplitUp",
|
||||||
"cmd-k down": "pane::SplitDown",
|
"cmd-k down": "pane::SplitDown",
|
||||||
@@ -532,42 +503,31 @@
|
|||||||
// Bindings that should be unified with bindings for more general actions
|
// Bindings that should be unified with bindings for more general actions
|
||||||
{
|
{
|
||||||
"context": "Editor && renaming",
|
"context": "Editor && renaming",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::ConfirmRename"
|
"enter": "editor::ConfirmRename"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && showing_completions",
|
"context": "Editor && showing_completions",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"enter": "editor::ConfirmCompletion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && !inline_completion && showing_completions",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"enter": "editor::ConfirmCompletion",
|
||||||
"tab": "editor::ComposeCompletion"
|
"tab": "editor::ComposeCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && inline_completion",
|
"context": "Editor && inline_completion && !showing_completions",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "editor::AcceptInlineCompletion"
|
"tab": "editor::AcceptInlineCompletion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && showing_code_actions",
|
"context": "Editor && showing_code_actions",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::ConfirmCodeAction"
|
"enter": "editor::ConfirmCodeAction"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"up": "editor::ContextMenuPrev",
|
"up": "editor::ContextMenuPrev",
|
||||||
"ctrl-p": "editor::ContextMenuPrev",
|
"ctrl-p": "editor::ContextMenuPrev",
|
||||||
@@ -579,7 +539,6 @@
|
|||||||
},
|
},
|
||||||
// Custom bindings
|
// Custom bindings
|
||||||
{
|
{
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||||
// TODO: Move this to a dock open action
|
// TODO: Move this to a dock open action
|
||||||
@@ -590,7 +549,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
"shift-enter": "editor::ExpandExcerpts",
|
"shift-enter": "editor::ExpandExcerpts",
|
||||||
@@ -602,7 +560,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProposedChangesEditor",
|
"context": "ProposedChangesEditor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-y": "editor::ApplyDiffHunk",
|
"cmd-shift-y": "editor::ApplyDiffHunk",
|
||||||
"cmd-shift-a": "editor::ApplyAllDiffHunks"
|
"cmd-shift-a": "editor::ApplyAllDiffHunks"
|
||||||
@@ -610,7 +567,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "PromptEditor",
|
"context": "PromptEditor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||||
@@ -618,14 +574,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectSearchBar && !in_replace",
|
"context": "ProjectSearchBar && !in_replace",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "project_search::SearchInNew"
|
"cmd-enter": "project_search::SearchInNew"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "OutlinePanel && not_editing",
|
"context": "OutlinePanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"left": "outline_panel::CollapseSelectedEntry",
|
"left": "outline_panel::CollapseSelectedEntry",
|
||||||
@@ -642,7 +596,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"left": "project_panel::CollapseSelectedEntry",
|
"left": "project_panel::CollapseSelectedEntry",
|
||||||
"right": "project_panel::ExpandSelectedEntry",
|
"right": "project_panel::ExpandSelectedEntry",
|
||||||
@@ -672,14 +625,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel && not_editing",
|
"context": "ProjectPanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"space": "project_panel::Open"
|
"space": "project_panel::Open"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-backspace": "collab_panel::Remove",
|
"ctrl-backspace": "collab_panel::Remove",
|
||||||
"space": "menu::Confirm"
|
"space": "menu::Confirm"
|
||||||
@@ -687,21 +638,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "(CollabPanel && editing) > Editor",
|
"context": "(CollabPanel && editing) > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"space": "collab_panel::InsertSpace"
|
"space": "collab_panel::InsertSpace"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ChannelModal",
|
"context": "ChannelModal",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "channel_modal::ToggleMode"
|
"tab": "channel_modal::ToggleMode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Picker > Editor",
|
"context": "Picker > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "picker::ConfirmCompletion",
|
"tab": "picker::ConfirmCompletion",
|
||||||
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
|
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
|
||||||
@@ -710,21 +658,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ChannelModal > Picker > Editor",
|
"context": "ChannelModal > Picker > Editor",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "channel_modal::ToggleMode"
|
"tab": "channel_modal::ToggleMode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "FileFinder",
|
"context": "FileFinder",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd": "file_finder::ToggleMenu"
|
"cmd": "file_finder::ToggleMenu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "FileFinder && !menu_open",
|
"context": "FileFinder && !menu_open",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-p": "file_finder::SelectPrev",
|
"cmd-shift-p": "file_finder::SelectPrev",
|
||||||
"cmd-j": "pane::SplitDown",
|
"cmd-j": "pane::SplitDown",
|
||||||
@@ -735,7 +680,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "FileFinder && menu_open",
|
"context": "FileFinder && menu_open",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"j": "pane::SplitDown",
|
"j": "pane::SplitDown",
|
||||||
"k": "pane::SplitUp",
|
"k": "pane::SplitUp",
|
||||||
@@ -745,7 +689,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "TabSwitcher",
|
"context": "TabSwitcher",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-up": "menu::SelectPrev",
|
"ctrl-up": "menu::SelectPrev",
|
||||||
"ctrl-down": "menu::SelectNext",
|
"ctrl-down": "menu::SelectNext",
|
||||||
@@ -755,7 +698,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Terminal",
|
"context": "Terminal",
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-cmd-space": "terminal::ShowCharacterPalette",
|
"ctrl-cmd-space": "terminal::ShowCharacterPalette",
|
||||||
"cmd-c": "terminal::Copy",
|
"cmd-c": "terminal::Copy",
|
||||||
@@ -795,24 +737,5 @@
|
|||||||
"ctrl-k left": "pane::SplitLeft",
|
"ctrl-k left": "pane::SplitLeft",
|
||||||
"ctrl-k right": "pane::SplitRight"
|
"ctrl-k right": "pane::SplitRight"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "RateCompletionModal",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"cmd-enter": "zeta::ThumbsUp",
|
|
||||||
"shift-down": "zeta::NextEdit",
|
|
||||||
"shift-up": "zeta::PreviousEdit",
|
|
||||||
"right": "zeta::PreviewCompletion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "RateCompletionModal > Editor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"escape": "zeta::FocusCompletions",
|
|
||||||
"cmd-shift-enter": "zeta::ThumbsUpActiveCompletion",
|
|
||||||
"cmd-shift-backspace": "zeta::ThumbsDownActiveCompletion"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
"ctrl-b": "editor::MoveLeft",
|
"ctrl-b": "editor::MoveLeft",
|
||||||
"ctrl-n": "editor::MoveDown",
|
"ctrl-n": "editor::MoveDown",
|
||||||
"ctrl-p": "editor::MoveUp",
|
"ctrl-p": "editor::MoveUp",
|
||||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
"ctrl-e": "editor::MoveToEndOfLine",
|
||||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
|
||||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
|
||||||
"alt-f": "editor::MoveToNextSubwordEnd",
|
"alt-f": "editor::MoveToNextSubwordEnd",
|
||||||
"alt-b": "editor::MoveToPreviousSubwordStart",
|
"alt-b": "editor::MoveToPreviousSubwordStart",
|
||||||
"ctrl-d": "editor::Delete",
|
"ctrl-d": "editor::Delete",
|
||||||
@@ -55,14 +53,6 @@
|
|||||||
"shift shift": "file_finder::Toggle"
|
"shift shift": "file_finder::Toggle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "BufferSearchBar > Editor",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-s": "search::SelectNextMatch",
|
|
||||||
"ctrl-r": "search::SelectPrevMatch",
|
|
||||||
"ctrl-g": "buffer_search::Dismiss"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"ctrl->": "zed::IncreaseBufferFontSize",
|
"ctrl->": "zed::IncreaseBufferFontSize",
|
||||||
"ctrl-<": "zed::DecreaseBufferFontSize",
|
"ctrl-<": "zed::DecreaseBufferFontSize",
|
||||||
"ctrl-shift-j": "editor::JoinLines",
|
"ctrl-shift-j": "editor::JoinLines",
|
||||||
"ctrl-d": "editor::DuplicateSelection",
|
"ctrl-d": "editor::DuplicateLineDown",
|
||||||
"ctrl-y": "editor::DeleteLine",
|
"ctrl-y": "editor::DeleteLine",
|
||||||
"ctrl-m": "editor::ScrollCursorCenter",
|
"ctrl-m": "editor::ScrollCursorCenter",
|
||||||
"ctrl-pagedown": "editor::MovePageDown",
|
"ctrl-pagedown": "editor::MovePageDown",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||||
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
|
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
|
||||||
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
||||||
"ctrl-shift-d": "editor::DuplicateSelection",
|
"ctrl-shift-d": "editor::DuplicateLineDown",
|
||||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||||
"f12": "editor::GoToDefinition",
|
"f12": "editor::GoToDefinition",
|
||||||
"ctrl-f12": "editor::GoToDefinitionSplit",
|
"ctrl-f12": "editor::GoToDefinitionSplit",
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
"ctrl-b": "editor::MoveLeft",
|
"ctrl-b": "editor::MoveLeft",
|
||||||
"ctrl-n": "editor::MoveDown",
|
"ctrl-n": "editor::MoveDown",
|
||||||
"ctrl-p": "editor::MoveUp",
|
"ctrl-p": "editor::MoveUp",
|
||||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
"ctrl-e": "editor::MoveToEndOfLine",
|
||||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
|
||||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
|
||||||
"alt-f": "editor::MoveToNextSubwordEnd",
|
"alt-f": "editor::MoveToNextSubwordEnd",
|
||||||
"alt-b": "editor::MoveToPreviousSubwordStart",
|
"alt-b": "editor::MoveToPreviousSubwordStart",
|
||||||
"ctrl-d": "editor::Delete",
|
"ctrl-d": "editor::Delete",
|
||||||
@@ -55,14 +53,6 @@
|
|||||||
"shift shift": "file_finder::Toggle"
|
"shift shift": "file_finder::Toggle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "BufferSearchBar > Editor",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-s": "search::SelectNextMatch",
|
|
||||||
"ctrl-r": "search::SelectPrevMatch",
|
|
||||||
"ctrl-g": "buffer_search::Dismiss"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"ctrl->": "zed::IncreaseBufferFontSize",
|
"ctrl->": "zed::IncreaseBufferFontSize",
|
||||||
"ctrl-<": "zed::DecreaseBufferFontSize",
|
"ctrl-<": "zed::DecreaseBufferFontSize",
|
||||||
"ctrl-shift-j": "editor::JoinLines",
|
"ctrl-shift-j": "editor::JoinLines",
|
||||||
"cmd-d": "editor::DuplicateSelection",
|
"cmd-d": "editor::DuplicateLineDown",
|
||||||
"cmd-backspace": "editor::DeleteLine",
|
"cmd-backspace": "editor::DeleteLine",
|
||||||
"cmd-pagedown": "editor::MovePageDown",
|
"cmd-pagedown": "editor::MovePageDown",
|
||||||
"cmd-pageup": "editor::MovePageUp",
|
"cmd-pageup": "editor::MovePageUp",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||||
"cmd-shift-l": "editor::SplitSelectionIntoLines",
|
"cmd-shift-l": "editor::SplitSelectionIntoLines",
|
||||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||||
"cmd-shift-d": "editor::DuplicateSelection",
|
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||||
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
||||||
"shift-f12": "editor::FindAllReferences",
|
"shift-f12": "editor::FindAllReferences",
|
||||||
"alt-cmd-down": "editor::GoToDefinition",
|
"alt-cmd-down": "editor::GoToDefinition",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"context": "VimControl && !menu",
|
"context": "VimControl && !menu",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||||
@@ -187,6 +188,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == normal",
|
"context": "vim_mode == normal",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "editor::Cancel",
|
"escape": "editor::Cancel",
|
||||||
"ctrl-[": "editor::Cancel",
|
"ctrl-[": "editor::Cancel",
|
||||||
@@ -241,6 +243,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "VimControl && VimCount",
|
"context": "VimControl && VimCount",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"0": ["vim::Number", 0],
|
"0": ["vim::Number", 0],
|
||||||
":": "vim::CountCommand"
|
":": "vim::CountCommand"
|
||||||
@@ -248,6 +251,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == visual",
|
"context": "vim_mode == visual",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
":": "vim::VisualCommand",
|
":": "vim::VisualCommand",
|
||||||
"u": "vim::ConvertToLowerCase",
|
"u": "vim::ConvertToLowerCase",
|
||||||
@@ -297,6 +301,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == insert",
|
"context": "vim_mode == insert",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "vim::NormalBefore",
|
"escape": "vim::NormalBefore",
|
||||||
"ctrl-c": "vim::NormalBefore",
|
"ctrl-c": "vim::NormalBefore",
|
||||||
@@ -326,7 +331,6 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"i": "vim::InsertBefore",
|
"i": "vim::InsertBefore",
|
||||||
"a": "vim::InsertAfter",
|
"a": "vim::InsertAfter",
|
||||||
"d": "vim::HelixDelete",
|
|
||||||
"w": "vim::NextWordStart",
|
"w": "vim::NextWordStart",
|
||||||
"e": "vim::NextWordEnd",
|
"e": "vim::NextWordEnd",
|
||||||
"b": "vim::PreviousWordStart",
|
"b": "vim::PreviousWordStart",
|
||||||
@@ -340,6 +344,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::ShowCompletions",
|
"ctrl-p": "editor::ShowCompletions",
|
||||||
"ctrl-n": "editor::ShowCompletions"
|
"ctrl-n": "editor::ShowCompletions"
|
||||||
@@ -347,6 +352,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == replace",
|
"context": "vim_mode == replace",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "vim::NormalBefore",
|
"escape": "vim::NormalBefore",
|
||||||
"ctrl-c": "vim::NormalBefore",
|
"ctrl-c": "vim::NormalBefore",
|
||||||
@@ -364,6 +370,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == waiting",
|
"context": "vim_mode == waiting",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "vim::Tab",
|
"tab": "vim::Tab",
|
||||||
"enter": "vim::Enter",
|
"enter": "vim::Enter",
|
||||||
@@ -377,6 +384,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == operator",
|
"context": "vim_mode == operator",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "vim::ClearOperators",
|
"escape": "vim::ClearOperators",
|
||||||
"ctrl-c": "vim::ClearOperators",
|
"ctrl-c": "vim::ClearOperators",
|
||||||
@@ -386,6 +394,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"w": "vim::Word",
|
"w": "vim::Word",
|
||||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||||
@@ -416,6 +425,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == c",
|
"context": "vim_operator == c",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"c": "vim::CurrentLine",
|
"c": "vim::CurrentLine",
|
||||||
"d": "editor::Rename", // zed specific
|
"d": "editor::Rename", // zed specific
|
||||||
@@ -424,6 +434,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == d",
|
"context": "vim_operator == d",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"d": "vim::CurrentLine",
|
"d": "vim::CurrentLine",
|
||||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||||
@@ -433,6 +444,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gu",
|
"context": "vim_operator == gu",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g u": "vim::CurrentLine",
|
"g u": "vim::CurrentLine",
|
||||||
"u": "vim::CurrentLine"
|
"u": "vim::CurrentLine"
|
||||||
@@ -440,6 +452,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gU",
|
"context": "vim_operator == gU",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g shift-u": "vim::CurrentLine",
|
"g shift-u": "vim::CurrentLine",
|
||||||
"shift-u": "vim::CurrentLine"
|
"shift-u": "vim::CurrentLine"
|
||||||
@@ -447,6 +460,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == g~",
|
"context": "vim_operator == g~",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g ~": "vim::CurrentLine",
|
"g ~": "vim::CurrentLine",
|
||||||
"~": "vim::CurrentLine"
|
"~": "vim::CurrentLine"
|
||||||
@@ -454,6 +468,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gq",
|
"context": "vim_operator == gq",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g q": "vim::CurrentLine",
|
"g q": "vim::CurrentLine",
|
||||||
"q": "vim::CurrentLine",
|
"q": "vim::CurrentLine",
|
||||||
@@ -463,6 +478,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == y",
|
"context": "vim_operator == y",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"y": "vim::CurrentLine",
|
"y": "vim::CurrentLine",
|
||||||
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
|
||||||
@@ -470,36 +486,42 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == ys",
|
"context": "vim_operator == ys",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"s": "vim::CurrentLine"
|
"s": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == >",
|
"context": "vim_operator == >",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
">": "vim::CurrentLine"
|
">": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == <",
|
"context": "vim_operator == <",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"<": "vim::CurrentLine"
|
"<": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == eq",
|
"context": "vim_operator == eq",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"=": "vim::CurrentLine"
|
"=": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gc",
|
"context": "vim_operator == gc",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"c": "vim::CurrentLine"
|
"c": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == literal",
|
"context": "vim_mode == literal",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
|
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
|
||||||
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
|
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
|
||||||
@@ -543,6 +565,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && !in_replace",
|
"context": "BufferSearchBar && !in_replace",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "vim::SearchSubmit",
|
"enter": "vim::SearchSubmit",
|
||||||
"escape": "buffer_search::Dismiss"
|
"escape": "buffer_search::Dismiss"
|
||||||
@@ -550,6 +573,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// window related commands (ctrl-w X)
|
// window related commands (ctrl-w X)
|
||||||
"ctrl-w": null,
|
"ctrl-w": null,
|
||||||
@@ -606,6 +630,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
":": "command_palette::Toggle",
|
":": "command_palette::Toggle",
|
||||||
"g /": "pane::DeploySearch"
|
"g /": "pane::DeploySearch"
|
||||||
@@ -614,6 +639,7 @@
|
|||||||
{
|
{
|
||||||
// netrw compatibility
|
// netrw compatibility
|
||||||
"context": "ProjectPanel && not_editing",
|
"context": "ProjectPanel && not_editing",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
":": "command_palette::Toggle",
|
":": "command_palette::Toggle",
|
||||||
"%": "project_panel::NewFile",
|
"%": "project_panel::NewFile",
|
||||||
@@ -647,6 +673,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "OutlinePanel && not_editing",
|
"context": "OutlinePanel && not_editing",
|
||||||
|
"use_layout_keys": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"j": "menu::SelectNext",
|
"j": "menu::SelectNext",
|
||||||
"k": "menu::SelectPrev",
|
"k": "menu::SelectPrev",
|
||||||
|
|||||||
@@ -144,15 +144,15 @@
|
|||||||
// 4. Highlight the full line (default):
|
// 4. Highlight the full line (default):
|
||||||
// "all"
|
// "all"
|
||||||
"current_line_highlight": "all",
|
"current_line_highlight": "all",
|
||||||
// The debounce delay before querying highlights from the language
|
|
||||||
// server based on the current cursor location.
|
|
||||||
"lsp_highlight_debounce": 75,
|
|
||||||
// Whether to pop the completions menu while typing in an editor without
|
// Whether to pop the completions menu while typing in an editor without
|
||||||
// explicitly requesting it.
|
// explicitly requesting it.
|
||||||
"show_completions_on_input": true,
|
"show_completions_on_input": true,
|
||||||
// Whether to display inline and alongside documentation for items in the
|
// Whether to display inline and alongside documentation for items in the
|
||||||
// completions menu
|
// completions menu
|
||||||
"show_completion_documentation": true,
|
"show_completion_documentation": true,
|
||||||
|
// The debounce delay before re-querying the language server for completion
|
||||||
|
// documentation when not included in original completion list.
|
||||||
|
"completion_documentation_secondary_query_debounce": 300,
|
||||||
// Show method signatures in the editor, when inside parentheses.
|
// Show method signatures in the editor, when inside parentheses.
|
||||||
"auto_signature_help": false,
|
"auto_signature_help": false,
|
||||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||||
@@ -474,14 +474,6 @@
|
|||||||
// Default width of the chat panel.
|
// Default width of the chat panel.
|
||||||
"default_width": 240
|
"default_width": 240
|
||||||
},
|
},
|
||||||
"git_panel": {
|
|
||||||
// Whether to show the git panel button in the status bar.
|
|
||||||
"button": true,
|
|
||||||
// Where to the git panel. Can be 'left' or 'right'.
|
|
||||||
"dock": "left",
|
|
||||||
// Default width of the git panel.
|
|
||||||
"default_width": 360
|
|
||||||
},
|
|
||||||
"message_editor": {
|
"message_editor": {
|
||||||
// Whether to automatically replace emoji shortcodes with emoji characters.
|
// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||||
// For example: typing `:wave:` gets replaced with `👋`.
|
// For example: typing `:wave:` gets replaced with `👋`.
|
||||||
@@ -572,15 +564,12 @@
|
|||||||
// What to do after closing the current tab.
|
// What to do after closing the current tab.
|
||||||
//
|
//
|
||||||
// 1. Activate the tab that was open previously (default)
|
// 1. Activate the tab that was open previously (default)
|
||||||
// "history"
|
// "History"
|
||||||
// 2. Activate the right neighbour tab if present
|
// 2. Activate the neighbour tab (prefers the right one, if present)
|
||||||
// "neighbour"
|
// "Neighbour"
|
||||||
// 3. Activate the left neighbour tab if present
|
|
||||||
// "left_neighbour"
|
|
||||||
"activate_on_close": "history",
|
"activate_on_close": "history",
|
||||||
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||||
/// Diagnostics are only shown when file icons are also active.
|
/// This setting can take the following three values:
|
||||||
/// This setting only works when can take the following three values:
|
|
||||||
///
|
///
|
||||||
/// 1. Do not mark any files:
|
/// 1. Do not mark any files:
|
||||||
/// "off"
|
/// "off"
|
||||||
@@ -588,7 +577,7 @@
|
|||||||
/// "errors"
|
/// "errors"
|
||||||
/// 3. Mark files with errors and warnings:
|
/// 3. Mark files with errors and warnings:
|
||||||
/// "all"
|
/// "all"
|
||||||
"show_diagnostics": "off"
|
"show_diagnostics": "all"
|
||||||
},
|
},
|
||||||
// Settings related to preview tabs.
|
// Settings related to preview tabs.
|
||||||
"preview_tabs": {
|
"preview_tabs": {
|
||||||
@@ -695,7 +684,6 @@
|
|||||||
"**/.git",
|
"**/.git",
|
||||||
"**/.svn",
|
"**/.svn",
|
||||||
"**/.hg",
|
"**/.hg",
|
||||||
"**/.jj",
|
|
||||||
"**/CVS",
|
"**/CVS",
|
||||||
"**/.DS_Store",
|
"**/.DS_Store",
|
||||||
"**/Thumbs.db",
|
"**/Thumbs.db",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#1e2025ff",
|
"tab.active_background": "#1e2025ff",
|
||||||
"search.match_background": "#11a79366",
|
"search.match_background": "#11a79366",
|
||||||
"panel.background": "#21242bff",
|
"panel.background": "#21242bff",
|
||||||
"panel.focused_border": "#10a793ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#f7f7f84c",
|
"scrollbar.thumb.background": "#f7f7f84c",
|
||||||
"scrollbar.thumb.hover_background": "#252931ff",
|
"scrollbar.thumb.hover_background": "#252931ff",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#19171cff",
|
"tab.active_background": "#19171cff",
|
||||||
"search.match_background": "#576dda66",
|
"search.match_background": "#576dda66",
|
||||||
"panel.background": "#221f26ff",
|
"panel.background": "#221f26ff",
|
||||||
"panel.focused_border": "#566ddaff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#efecf44c",
|
"scrollbar.thumb.background": "#efecf44c",
|
||||||
"scrollbar.thumb.hover_background": "#332f38ff",
|
"scrollbar.thumb.hover_background": "#332f38ff",
|
||||||
@@ -431,7 +431,7 @@
|
|||||||
"tab.active_background": "#efecf4ff",
|
"tab.active_background": "#efecf4ff",
|
||||||
"search.match_background": "#586dda66",
|
"search.match_background": "#586dda66",
|
||||||
"panel.background": "#e6e3ebff",
|
"panel.background": "#e6e3ebff",
|
||||||
"panel.focused_border": "#586cdaff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#19171c4c",
|
"scrollbar.thumb.background": "#19171c4c",
|
||||||
"scrollbar.thumb.hover_background": "#cbc8d1ff",
|
"scrollbar.thumb.hover_background": "#cbc8d1ff",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#0d1016ff",
|
"tab.active_background": "#0d1016ff",
|
||||||
"search.match_background": "#5ac2fe66",
|
"search.match_background": "#5ac2fe66",
|
||||||
"panel.background": "#1f2127ff",
|
"panel.background": "#1f2127ff",
|
||||||
"panel.focused_border": "#5ac1feff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#bfbdb64c",
|
"scrollbar.thumb.background": "#bfbdb64c",
|
||||||
"scrollbar.thumb.hover_background": "#2d2f34ff",
|
"scrollbar.thumb.hover_background": "#2d2f34ff",
|
||||||
@@ -416,7 +416,7 @@
|
|||||||
"tab.active_background": "#fcfcfcff",
|
"tab.active_background": "#fcfcfcff",
|
||||||
"search.match_background": "#3b9ee566",
|
"search.match_background": "#3b9ee566",
|
||||||
"panel.background": "#ececedff",
|
"panel.background": "#ececedff",
|
||||||
"panel.focused_border": "#3b9ee5ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#5c61664c",
|
"scrollbar.thumb.background": "#5c61664c",
|
||||||
"scrollbar.thumb.hover_background": "#dfe0e1ff",
|
"scrollbar.thumb.hover_background": "#dfe0e1ff",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
"tab.active_background": "#282828ff",
|
"tab.active_background": "#282828ff",
|
||||||
"search.match_background": "#83a59866",
|
"search.match_background": "#83a59866",
|
||||||
"panel.background": "#3a3735ff",
|
"panel.background": "#3a3735ff",
|
||||||
"panel.focused_border": "#83a598ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#fbf1c74c",
|
"scrollbar.thumb.background": "#fbf1c74c",
|
||||||
"scrollbar.thumb.hover_background": "#494340ff",
|
"scrollbar.thumb.hover_background": "#494340ff",
|
||||||
@@ -439,7 +439,7 @@
|
|||||||
"tab.active_background": "#1d2021ff",
|
"tab.active_background": "#1d2021ff",
|
||||||
"search.match_background": "#83a59866",
|
"search.match_background": "#83a59866",
|
||||||
"panel.background": "#393634ff",
|
"panel.background": "#393634ff",
|
||||||
"panel.focused_border": "#83a598ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#fbf1c74c",
|
"scrollbar.thumb.background": "#fbf1c74c",
|
||||||
"scrollbar.thumb.hover_background": "#494340ff",
|
"scrollbar.thumb.hover_background": "#494340ff",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#191724ff",
|
"tab.active_background": "#191724ff",
|
||||||
"search.match_background": "#57949f66",
|
"search.match_background": "#57949f66",
|
||||||
"panel.background": "#1c1b2aff",
|
"panel.background": "#1c1b2aff",
|
||||||
"panel.focused_border": "#9bced6ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#e0def44c",
|
"scrollbar.thumb.background": "#e0def44c",
|
||||||
"scrollbar.thumb.hover_background": "#232132ff",
|
"scrollbar.thumb.hover_background": "#232132ff",
|
||||||
@@ -426,7 +426,7 @@
|
|||||||
"tab.active_background": "#faf4edff",
|
"tab.active_background": "#faf4edff",
|
||||||
"search.match_background": "#9cced766",
|
"search.match_background": "#9cced766",
|
||||||
"panel.background": "#fef9f2ff",
|
"panel.background": "#fef9f2ff",
|
||||||
"panel.focused_border": "#57949fff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#5752794c",
|
"scrollbar.thumb.background": "#5752794c",
|
||||||
"scrollbar.thumb.hover_background": "#e5e0dfff",
|
"scrollbar.thumb.hover_background": "#e5e0dfff",
|
||||||
@@ -806,7 +806,7 @@
|
|||||||
"tab.active_background": "#232136ff",
|
"tab.active_background": "#232136ff",
|
||||||
"search.match_background": "#9cced766",
|
"search.match_background": "#9cced766",
|
||||||
"panel.background": "#28253cff",
|
"panel.background": "#28253cff",
|
||||||
"panel.focused_border": "#9bced6ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#e0def44c",
|
"scrollbar.thumb.background": "#e0def44c",
|
||||||
"scrollbar.thumb.hover_background": "#322f48ff",
|
"scrollbar.thumb.hover_background": "#322f48ff",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#282c33ff",
|
"tab.active_background": "#282c33ff",
|
||||||
"search.match_background": "#528b8b66",
|
"search.match_background": "#528b8b66",
|
||||||
"panel.background": "#2b3038ff",
|
"panel.background": "#2b3038ff",
|
||||||
"panel.focused_border": "#518b8bff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#fdf4c14c",
|
"scrollbar.thumb.background": "#fdf4c14c",
|
||||||
"scrollbar.thumb.hover_background": "#313741ff",
|
"scrollbar.thumb.hover_background": "#313741ff",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#002a35ff",
|
"tab.active_background": "#002a35ff",
|
||||||
"search.match_background": "#288bd166",
|
"search.match_background": "#288bd166",
|
||||||
"panel.background": "#04313bff",
|
"panel.background": "#04313bff",
|
||||||
"panel.focused_border": "#278ad1ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#fdf6e34c",
|
"scrollbar.thumb.background": "#fdf6e34c",
|
||||||
"scrollbar.thumb.hover_background": "#053541ff",
|
"scrollbar.thumb.hover_background": "#053541ff",
|
||||||
@@ -416,7 +416,7 @@
|
|||||||
"tab.active_background": "#fdf6e3ff",
|
"tab.active_background": "#fdf6e3ff",
|
||||||
"search.match_background": "#298bd166",
|
"search.match_background": "#298bd166",
|
||||||
"panel.background": "#f3eddaff",
|
"panel.background": "#f3eddaff",
|
||||||
"panel.focused_border": "#288bd1ff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#002a354c",
|
"scrollbar.thumb.background": "#002a354c",
|
||||||
"scrollbar.thumb.hover_background": "#dcdacbff",
|
"scrollbar.thumb.hover_background": "#dcdacbff",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"tab.active_background": "#1b1810ff",
|
"tab.active_background": "#1b1810ff",
|
||||||
"search.match_background": "#499bef66",
|
"search.match_background": "#499bef66",
|
||||||
"panel.background": "#231f16ff",
|
"panel.background": "#231f16ff",
|
||||||
"panel.focused_border": "#499befff",
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
"scrollbar.thumb.background": "#f8f5de4c",
|
"scrollbar.thumb.background": "#f8f5de4c",
|
||||||
"scrollbar.thumb.hover_background": "#29251bff",
|
"scrollbar.thumb.hover_background": "#29251bff",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ use language_model::{
|
|||||||
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
|
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
|
||||||
ZED_CLOUD_PROVIDER_ID,
|
ZED_CLOUD_PROVIDER_ID,
|
||||||
};
|
};
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::lsp_store::LocalLspAdapterDelegate;
|
use project::lsp_store::LocalLspAdapterDelegate;
|
||||||
@@ -143,7 +143,7 @@ pub struct AssistantPanel {
|
|||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||||
model_summary_editor: View<Editor>,
|
model_summary_editor: View<Editor>,
|
||||||
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
|
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
|
||||||
configuration_subscription: Option<Subscription>,
|
configuration_subscription: Option<Subscription>,
|
||||||
@@ -305,7 +305,7 @@ impl PickerDelegate for SavedContextPickerDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.child(item),
|
.child(item),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -341,12 +341,11 @@ impl AssistantPanel {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||||
let model_summary_editor = cx.new_view(Editor::single_line);
|
let model_summary_editor = cx.new_view(Editor::single_line);
|
||||||
let context_editor_toolbar = cx.new_view(|cx| {
|
let context_editor_toolbar = cx.new_view(|_| {
|
||||||
ContextEditorToolbarItem::new(
|
ContextEditorToolbarItem::new(
|
||||||
workspace,
|
workspace,
|
||||||
model_selector_menu_handle.clone(),
|
model_selector_menu_handle.clone(),
|
||||||
model_summary_editor.clone(),
|
model_summary_editor.clone(),
|
||||||
cx,
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -442,7 +441,7 @@ impl AssistantPanel {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.toggle_state(
|
.selected(
|
||||||
pane.active_item()
|
pane.active_item()
|
||||||
.map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
|
.map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
|
||||||
);
|
);
|
||||||
@@ -4456,36 +4455,23 @@ impl FollowableItem for ContextEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ContextEditorToolbarItem {
|
pub struct ContextEditorToolbarItem {
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
active_context_editor: Option<WeakView<ContextEditor>>,
|
active_context_editor: Option<WeakView<ContextEditor>>,
|
||||||
model_summary_editor: View<Editor>,
|
model_summary_editor: View<Editor>,
|
||||||
language_model_selector: View<LanguageModelSelector>,
|
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextEditorToolbarItem {
|
impl ContextEditorToolbarItem {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
|
||||||
model_summary_editor: View<Editor>,
|
model_summary_editor: View<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
fs: workspace.app_state().fs.clone(),
|
||||||
active_context_editor: None,
|
active_context_editor: None,
|
||||||
model_summary_editor,
|
model_summary_editor,
|
||||||
language_model_selector: cx.new_view(|cx| {
|
model_selector_menu_handle,
|
||||||
let fs = workspace.app_state().fs.clone();
|
|
||||||
LanguageModelSelector::new(
|
|
||||||
move |model, cx| {
|
|
||||||
update_settings_file::<AssistantSettings>(
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
move |settings, _| settings.set_model(model.clone()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
language_model_selector_menu_handle: model_selector_menu_handle,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4574,8 +4560,17 @@ impl Render for ContextEditorToolbarItem {
|
|||||||
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
|
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
|
||||||
// })
|
// })
|
||||||
.child(
|
.child(
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
LanguageModelSelector::new(
|
||||||
self.language_model_selector.clone(),
|
{
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
move |model, cx| {
|
||||||
|
update_settings_file::<AssistantSettings>(
|
||||||
|
fs.clone(),
|
||||||
|
cx,
|
||||||
|
move |settings, _| settings.set_model(model.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.child(
|
.child(
|
||||||
@@ -4621,7 +4616,7 @@ impl Render for ContextEditorToolbarItem {
|
|||||||
Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
|
Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with_handle(self.language_model_selector_menu_handle.clone()),
|
.with_handle(self.model_selector_menu_handle.clone()),
|
||||||
)
|
)
|
||||||
.children(self.render_remaining_tokens(cx));
|
.children(self.render_remaining_tokens(cx));
|
||||||
|
|
||||||
@@ -4956,7 +4951,7 @@ fn render_slash_command_output_toggle(
|
|||||||
("slash-command-output-fold-indicator", row.0 as u64),
|
("slash-command-output-fold-indicator", row.0 as u64),
|
||||||
!is_folded,
|
!is_folded,
|
||||||
)
|
)
|
||||||
.toggle_state(is_folded)
|
.selected(is_folded)
|
||||||
.on_click(move |_e, cx| fold(!is_folded, cx))
|
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
@@ -4971,7 +4966,7 @@ fn fold_toggle(
|
|||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
move |row, is_folded, fold, _cx| {
|
move |row, is_folded, fold, _cx| {
|
||||||
Disclosure::new((name, row.0 as u64), !is_folded)
|
Disclosure::new((name, row.0 as u64), !is_folded)
|
||||||
.toggle_state(is_folded)
|
.selected(is_folded)
|
||||||
.on_click(move |_e, cx| fold(!is_folded, cx))
|
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
@@ -5013,7 +5008,7 @@ fn render_quote_selection_output_toggle(
|
|||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
|
Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
|
||||||
.toggle_state(is_folded)
|
.selected(is_folded)
|
||||||
.on_click(move |_e, cx| fold(!is_folded, cx))
|
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
@@ -5036,7 +5031,7 @@ fn render_pending_slash_command_gutter_decoration(
|
|||||||
icon = icon.icon_color(Color::Muted);
|
icon = icon.icon_color(Color::Muted);
|
||||||
}
|
}
|
||||||
PendingSlashCommandStatus::Running { .. } => {
|
PendingSlashCommandStatus::Running { .. } => {
|
||||||
icon = icon.toggle_state(true);
|
icon = icon.selected(true);
|
||||||
}
|
}
|
||||||
PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
|
PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
|
||||||
}
|
}
|
||||||
@@ -5118,11 +5113,9 @@ fn make_lsp_adapter_delegate(
|
|||||||
return Ok(None::<Arc<dyn LspAdapterDelegate>>);
|
return Ok(None::<Arc<dyn LspAdapterDelegate>>);
|
||||||
};
|
};
|
||||||
let http_client = project.client().http_client().clone();
|
let http_client = project.client().http_client().clone();
|
||||||
project.lsp_store().update(cx, |_, cx| {
|
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||||
Ok(Some(LocalLspAdapterDelegate::new(
|
Ok(Some(LocalLspAdapterDelegate::new(
|
||||||
project.languages().clone(),
|
lsp_store,
|
||||||
project.environment(),
|
|
||||||
cx.weak_model(),
|
|
||||||
&worktree,
|
&worktree,
|
||||||
http_client,
|
http_client,
|
||||||
project.fs().clone(),
|
project.fs().clone(),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use futures::{
|
|||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
stream::{self, StreamExt},
|
stream::{self, StreamExt},
|
||||||
};
|
};
|
||||||
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
||||||
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
||||||
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -35,7 +35,7 @@ use std::{
|
|||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
||||||
use ui::{IconName, WindowContext};
|
use ui::{Context as _, IconName, WindowContext};
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::{
|
use util::{
|
||||||
test::{generate_marked_text, marked_text_ranges},
|
test::{generate_marked_text, marked_text_ranges},
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use language_model::{
|
|||||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||||
LanguageModelTextStream, Role,
|
LanguageModelTextStream, Role,
|
||||||
};
|
};
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::LanguageModelSelector;
|
||||||
use language_models::report_assistant_event;
|
use language_models::report_assistant_event;
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -1358,8 +1358,8 @@ enum PromptEditorEvent {
|
|||||||
|
|
||||||
struct PromptEditor {
|
struct PromptEditor {
|
||||||
id: InlineAssistId,
|
id: InlineAssistId,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
language_model_selector: View<LanguageModelSelector>,
|
|
||||||
edited_since_done: bool,
|
edited_since_done: bool,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
@@ -1500,27 +1500,43 @@ impl Render for PromptEditor {
|
|||||||
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(LanguageModelSelectorPopoverMenu::new(
|
.child(
|
||||||
self.language_model_selector.clone(),
|
LanguageModelSelector::new(
|
||||||
IconButton::new("context", IconName::SettingsAlt)
|
{
|
||||||
.shape(IconButtonShape::Square)
|
let fs = self.fs.clone();
|
||||||
.icon_size(IconSize::Small)
|
move |model, cx| {
|
||||||
.icon_color(Color::Muted)
|
update_settings_file::<AssistantSettings>(
|
||||||
.tooltip(move |cx| {
|
fs.clone(),
|
||||||
Tooltip::with_meta(
|
cx,
|
||||||
format!(
|
move |settings, _| settings.set_model(model.clone()),
|
||||||
"Using {}",
|
);
|
||||||
LanguageModelRegistry::read_global(cx)
|
}
|
||||||
.active_model()
|
},
|
||||||
.map(|model| model.name().0)
|
IconButton::new("context", IconName::SettingsAlt)
|
||||||
.unwrap_or_else(|| "No model selected".into()),
|
.shape(IconButtonShape::Square)
|
||||||
),
|
.icon_size(IconSize::Small)
|
||||||
None,
|
.icon_color(Color::Muted)
|
||||||
"Change Model",
|
.tooltip(move |cx| {
|
||||||
cx,
|
Tooltip::with_meta(
|
||||||
)
|
format!(
|
||||||
}),
|
"Using {}",
|
||||||
))
|
LanguageModelRegistry::read_global(cx)
|
||||||
|
.active_model()
|
||||||
|
.map(|model| model.name().0)
|
||||||
|
.unwrap_or_else(|| "No model selected".into()),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
"Change Model",
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.info_text(
|
||||||
|
"Inline edits use context\n\
|
||||||
|
from the currently selected\n\
|
||||||
|
assistant panel tab.",
|
||||||
|
),
|
||||||
|
)
|
||||||
.map(|el| {
|
.map(|el| {
|
||||||
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
|
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
|
||||||
return el;
|
return el;
|
||||||
@@ -1534,7 +1550,7 @@ impl Render for PromptEditor {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("rate-limit-error", IconName::XCircle)
|
IconButton::new("rate-limit-error", IconName::XCircle)
|
||||||
.toggle_state(self.show_rate_limit_notice)
|
.selected(self.show_rate_limit_notice)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
|
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
|
||||||
@@ -1626,19 +1642,6 @@ impl PromptEditor {
|
|||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
id,
|
id,
|
||||||
editor: prompt_editor,
|
editor: prompt_editor,
|
||||||
language_model_selector: cx.new_view(|cx| {
|
|
||||||
let fs = fs.clone();
|
|
||||||
LanguageModelSelector::new(
|
|
||||||
move |model, cx| {
|
|
||||||
update_settings_file::<AssistantSettings>(
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
move |settings, _| settings.set_model(model.clone()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
edited_since_done: false,
|
edited_since_done: false,
|
||||||
gutter_dimensions,
|
gutter_dimensions,
|
||||||
prompt_history,
|
prompt_history,
|
||||||
@@ -1647,6 +1650,7 @@ impl PromptEditor {
|
|||||||
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
|
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
|
||||||
editor_subscriptions: Vec::new(),
|
editor_subscriptions: Vec::new(),
|
||||||
codegen,
|
codegen,
|
||||||
|
fs,
|
||||||
pending_token_count: Task::ready(Ok(())),
|
pending_token_count: Task::ready(Ok(())),
|
||||||
token_counts: None,
|
token_counts: None,
|
||||||
_token_count_subscriptions: token_count_subscriptions,
|
_token_count_subscriptions: token_count_subscriptions,
|
||||||
@@ -2133,15 +2137,15 @@ impl PromptEditor {
|
|||||||
"dont-show-again",
|
"dont-show-again",
|
||||||
Label::new("Don't show again"),
|
Label::new("Don't show again"),
|
||||||
if dismissed_rate_limit_notice() {
|
if dismissed_rate_limit_notice() {
|
||||||
ui::ToggleState::Selected
|
ui::Selection::Selected
|
||||||
} else {
|
} else {
|
||||||
ui::ToggleState::Unselected
|
ui::Selection::Unselected
|
||||||
},
|
},
|
||||||
|selection, cx| {
|
|selection, cx| {
|
||||||
let is_dismissed = match selection {
|
let is_dismissed = match selection {
|
||||||
ui::ToggleState::Unselected => false,
|
ui::Selection::Unselected => false,
|
||||||
ui::ToggleState::Indeterminate => return,
|
ui::Selection::Indeterminate => return,
|
||||||
ui::ToggleState::Selected => true,
|
ui::Selection::Selected => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
set_rate_limit_notice_dismissed(is_dismissed, cx)
|
set_rate_limit_notice_dismissed(is_dismissed, cx)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use futures::{
|
|||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, size, transparent_black, Action, AppContext, BackgroundExecutor, Bounds,
|
actions, point, size, transparent_black, Action, AppContext, BackgroundExecutor, Bounds,
|
||||||
EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TextStyle, TitlebarOptions,
|
EventEmitter, Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
|
||||||
UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use heed::{
|
use heed::{
|
||||||
types::{SerdeBincode, SerdeJson, Str},
|
types::{SerdeBincode, SerdeJson, Str},
|
||||||
@@ -232,13 +232,13 @@ impl PickerDelegate for PromptPickerDelegate {
|
|||||||
let element = ListItem::new(ix)
|
let element = ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
|
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
|
||||||
prompt.title.clone().unwrap_or("Untitled".into()),
|
prompt.title.clone().unwrap_or("Untitled".into()),
|
||||||
)))
|
)))
|
||||||
.end_slot::<IconButton>(default.then(|| {
|
.end_slot::<IconButton>(default.then(|| {
|
||||||
IconButton::new("toggle-default-prompt", IconName::SparkleFilled)
|
IconButton::new("toggle-default-prompt", IconName::SparkleFilled)
|
||||||
.toggle_state(true)
|
.selected(true)
|
||||||
.icon_color(Color::Accent)
|
.icon_color(Color::Accent)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
|
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
|
||||||
@@ -274,7 +274,7 @@ impl PickerDelegate for PromptPickerDelegate {
|
|||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
||||||
.toggle_state(default)
|
.selected(default)
|
||||||
.selected_icon(IconName::SparkleFilled)
|
.selected_icon(IconName::SparkleFilled)
|
||||||
.icon_color(if default { Color::Accent } else { Color::Muted })
|
.icon_color(if default { Color::Accent } else { Color::Muted })
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
@@ -928,8 +928,10 @@ impl PromptLibrary {
|
|||||||
status: cx.theme().status().clone(),
|
status: cx.theme().status().clone(),
|
||||||
inlay_hints_style:
|
inlay_hints_style:
|
||||||
editor::make_inlay_hints_style(cx),
|
editor::make_inlay_hints_style(cx),
|
||||||
inline_completion_styles:
|
suggestions_style: HighlightStyle {
|
||||||
editor::make_suggestion_styles(cx),
|
color: Some(cx.theme().status().predictive),
|
||||||
|
..HighlightStyle::default()
|
||||||
|
},
|
||||||
..EditorStyle::default()
|
..EditorStyle::default()
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
@@ -1053,7 +1055,7 @@ impl PromptLibrary {
|
|||||||
IconName::Sparkle,
|
IconName::Sparkle,
|
||||||
)
|
)
|
||||||
.style(ButtonStyle::Transparent)
|
.style(ButtonStyle::Transparent)
|
||||||
.toggle_state(prompt_metadata.default)
|
.selected(prompt_metadata.default)
|
||||||
.selected_icon(IconName::SparkleFilled)
|
.selected_icon(IconName::SparkleFilled)
|
||||||
.icon_color(if prompt_metadata.default {
|
.icon_color(if prompt_metadata.default {
|
||||||
Color::Accent
|
Color::Accent
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Dense)
|
.spacing(ListItemSpacing::Dense)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let description = info.description.clone();
|
let description = info.description.clone();
|
||||||
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
|
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
|
||||||
@@ -229,7 +229,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Dense)
|
.spacing(ListItemSpacing::Dense)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.child(renderer(cx)),
|
.child(renderer(cx)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use language::Buffer;
|
|||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||||
};
|
};
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::LanguageModelSelector;
|
||||||
use language_models::report_assistant_event;
|
use language_models::report_assistant_event;
|
||||||
use settings::{update_settings_file, Settings};
|
use settings::{update_settings_file, Settings};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -476,9 +476,9 @@ enum PromptEditorEvent {
|
|||||||
|
|
||||||
struct PromptEditor {
|
struct PromptEditor {
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
height_in_lines: u8,
|
height_in_lines: u8,
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
language_model_selector: View<LanguageModelSelector>,
|
|
||||||
edited_since_done: bool,
|
edited_since_done: bool,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
prompt_history_ix: Option<usize>,
|
prompt_history_ix: Option<usize>,
|
||||||
@@ -614,8 +614,17 @@ impl Render for PromptEditor {
|
|||||||
.w_12()
|
.w_12()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(LanguageModelSelectorPopoverMenu::new(
|
.child(LanguageModelSelector::new(
|
||||||
self.language_model_selector.clone(),
|
{
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
move |model, cx| {
|
||||||
|
update_settings_file::<AssistantSettings>(
|
||||||
|
fs.clone(),
|
||||||
|
cx,
|
||||||
|
move |settings, _| settings.set_model(model.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
IconButton::new("context", IconName::SettingsAlt)
|
IconButton::new("context", IconName::SettingsAlt)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
@@ -709,19 +718,6 @@ impl PromptEditor {
|
|||||||
id,
|
id,
|
||||||
height_in_lines: 1,
|
height_in_lines: 1,
|
||||||
editor: prompt_editor,
|
editor: prompt_editor,
|
||||||
language_model_selector: cx.new_view(|cx| {
|
|
||||||
let fs = fs.clone();
|
|
||||||
LanguageModelSelector::new(
|
|
||||||
move |model, cx| {
|
|
||||||
update_settings_file::<AssistantSettings>(
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
move |settings, _| settings.set_model(model.clone()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
edited_since_done: false,
|
edited_since_done: false,
|
||||||
prompt_history,
|
prompt_history,
|
||||||
prompt_history_ix: None,
|
prompt_history_ix: None,
|
||||||
@@ -729,6 +725,7 @@ impl PromptEditor {
|
|||||||
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
|
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
|
||||||
editor_subscriptions: Vec::new(),
|
editor_subscriptions: Vec::new(),
|
||||||
codegen,
|
codegen,
|
||||||
|
fs,
|
||||||
pending_token_count: Task::ready(Ok(())),
|
pending_token_count: Task::ready(Ok(())),
|
||||||
token_count: None,
|
token_count: None,
|
||||||
_token_count_subscriptions: token_count_subscriptions,
|
_token_count_subscriptions: token_count_subscriptions,
|
||||||
|
|||||||
@@ -13,55 +13,30 @@ path = "src/assistant.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anthropic = { workspace = true, features = ["schemars"] }
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
assets.workspace = true
|
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
async-watch.workspace = true
|
|
||||||
client.workspace = true
|
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
context_server.workspace = true
|
context_server.workspace = true
|
||||||
db.workspace = true
|
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
fs.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
handlebars.workspace = true
|
|
||||||
html_to_markdown.workspace = true
|
|
||||||
http_client.workspace = true
|
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
language_model_selector.workspace = true
|
language_model_selector.workspace = true
|
||||||
language_models.workspace = true
|
language_models.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp.workspace = true
|
|
||||||
markdown.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
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
proto.workspace = true
|
proto.workspace = true
|
||||||
rope.workspace = true
|
|
||||||
schemars.workspace = true
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_json_lenient.workspace = true
|
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
similar.workspace = true
|
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
telemetry_events.workspace = true
|
|
||||||
terminal_view.workspace = true
|
|
||||||
text.workspace = true
|
|
||||||
terminal.workspace = true
|
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
time_format.workspace = true
|
time_format.workspace = true
|
||||||
@@ -70,8 +45,3 @@ unindent.workspace = true
|
|||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rand.workspace = true
|
|
||||||
indoc.workspace = true
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use ui::prelude::*;
|
|||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||||
use crate::ui::ContextPill;
|
|
||||||
|
|
||||||
pub struct ActiveThread {
|
pub struct ActiveThread {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
@@ -203,8 +202,6 @@ impl ActiveThread {
|
|||||||
return Empty.into_any();
|
return Empty.into_any();
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = self.thread.read(cx).context_for_message(message_id);
|
|
||||||
|
|
||||||
let (role_icon, role_name) = match message.role {
|
let (role_icon, role_name) = match message.role {
|
||||||
Role::User => (IconName::Person, "You"),
|
Role::User => (IconName::Person, "You"),
|
||||||
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
|
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
|
||||||
@@ -232,16 +229,7 @@ impl ActiveThread {
|
|||||||
.child(Label::new(role_name).size(LabelSize::Small)),
|
.child(Label::new(role_name).size(LabelSize::Small)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(v_flex().p_1p5().text_ui(cx).child(markdown.clone()))
|
.child(v_flex().p_1p5().text_ui(cx).child(markdown.clone())),
|
||||||
.when_some(context, |parent, context| {
|
|
||||||
parent.child(
|
|
||||||
h_flex().flex_wrap().gap_2().p_1p5().children(
|
|
||||||
context
|
|
||||||
.iter()
|
|
||||||
.map(|context| ContextPill::new(context.clone())),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,14 @@
|
|||||||
mod active_thread;
|
mod active_thread;
|
||||||
mod assistant_panel;
|
mod assistant_panel;
|
||||||
mod assistant_settings;
|
|
||||||
mod context;
|
|
||||||
mod context_picker;
|
mod context_picker;
|
||||||
mod inline_assistant;
|
|
||||||
mod message_editor;
|
mod message_editor;
|
||||||
mod prompts;
|
|
||||||
mod streaming_diff;
|
|
||||||
mod terminal_inline_assistant;
|
|
||||||
mod thread;
|
mod thread;
|
||||||
mod thread_history;
|
mod thread_history;
|
||||||
mod thread_store;
|
mod thread_store;
|
||||||
mod ui;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use assistant_settings::AssistantSettings;
|
|
||||||
use client::Client;
|
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||||
use fs::Fs;
|
|
||||||
use gpui::{actions, AppContext};
|
use gpui::{actions, AppContext};
|
||||||
use prompts::PromptLoadingParams;
|
|
||||||
use settings::Settings as _;
|
|
||||||
use util::ResultExt;
|
|
||||||
|
|
||||||
pub use crate::assistant_panel::AssistantPanel;
|
pub use crate::assistant_panel::AssistantPanel;
|
||||||
|
|
||||||
@@ -34,43 +19,15 @@ actions!(
|
|||||||
NewThread,
|
NewThread,
|
||||||
ToggleModelSelector,
|
ToggleModelSelector,
|
||||||
OpenHistory,
|
OpenHistory,
|
||||||
Chat,
|
Chat
|
||||||
ToggleInlineAssist,
|
|
||||||
CycleNextInlineAssist,
|
|
||||||
CyclePreviousInlineAssist
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const NAMESPACE: &str = "assistant2";
|
const NAMESPACE: &str = "assistant2";
|
||||||
|
|
||||||
/// Initializes the `assistant2` crate.
|
/// Initializes the `assistant2` crate.
|
||||||
pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, stdout_is_a_pty: bool, cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
AssistantSettings::register(cx);
|
|
||||||
assistant_panel::init(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(),
|
|
||||||
client.telemetry().clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
terminal_inline_assistant::init(
|
|
||||||
fs.clone(),
|
|
||||||
prompt_builder.clone(),
|
|
||||||
client.telemetry().clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
feature_gate_assistant2_actions(cx);
|
feature_gate_assistant2_actions(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use gpui::{
|
|||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
|
use language_model::LanguageModelRegistry;
|
||||||
|
use language_model_selector::LanguageModelSelector;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
use ui::{prelude::*, Divider, IconButtonShape, KeyBinding, Tab, Tooltip};
|
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, KeyBinding, Tab, Tooltip};
|
||||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ use crate::message_editor::MessageEditor;
|
|||||||
use crate::thread::{ThreadError, ThreadId};
|
use crate::thread::{ThreadError, ThreadId};
|
||||||
use crate::thread_history::{PastThread, ThreadHistory};
|
use crate::thread_history::{PastThread, ThreadHistory};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::{NewThread, OpenHistory, ToggleFocus};
|
use crate::{NewThread, OpenHistory, ToggleFocus, ToggleModelSelector};
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(
|
cx.observe_new_views(
|
||||||
@@ -88,13 +90,13 @@ impl AssistantPanel {
|
|||||||
thread: cx.new_view(|cx| {
|
thread: cx.new_view(|cx| {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
workspace.clone(),
|
workspace,
|
||||||
language_registry,
|
language_registry,
|
||||||
tools.clone(),
|
tools.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
message_editor: cx.new_view(|cx| MessageEditor::new(workspace, thread.clone(), cx)),
|
message_editor: cx.new_view(|cx| MessageEditor::new(thread.clone(), cx)),
|
||||||
tools,
|
tools,
|
||||||
local_timezone: UtcOffset::from_whole_seconds(
|
local_timezone: UtcOffset::from_whole_seconds(
|
||||||
chrono::Local::now().offset().local_minus_utc(),
|
chrono::Local::now().offset().local_minus_utc(),
|
||||||
@@ -123,8 +125,7 @@ impl AssistantPanel {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
self.message_editor =
|
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
|
||||||
cx.new_view(|cx| MessageEditor::new(self.workspace.clone(), thread, cx));
|
|
||||||
self.message_editor.focus_handle(cx).focus(cx);
|
self.message_editor.focus_handle(cx).focus(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +147,7 @@ impl AssistantPanel {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
self.message_editor =
|
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
|
||||||
cx.new_view(|cx| MessageEditor::new(self.workspace.clone(), thread, cx));
|
|
||||||
self.message_editor.focus_handle(cx).focus(cx);
|
self.message_editor.focus_handle(cx).focus(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +225,7 @@ impl AssistantPanel {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap(DynamicSpacing::Base08.rems(cx))
|
.gap(DynamicSpacing::Base08.rems(cx))
|
||||||
|
.child(self.render_language_model_selector(cx))
|
||||||
.child(Divider::vertical())
|
.child(Divider::vertical())
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("new-thread", IconName::Plus)
|
IconButton::new("new-thread", IconName::Plus)
|
||||||
@@ -279,6 +280,57 @@ impl AssistantPanel {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||||
|
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||||
|
|
||||||
|
LanguageModelSelector::new(
|
||||||
|
|model, _cx| {
|
||||||
|
println!("Selected {:?}", model.name());
|
||||||
|
},
|
||||||
|
ButtonLike::new("active-model")
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.gap_0p5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.overflow_x_hidden()
|
||||||
|
.flex_grow()
|
||||||
|
.whitespace_nowrap()
|
||||||
|
.child(match (active_provider, active_model) {
|
||||||
|
(Some(provider), Some(model)) => h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Icon::new(
|
||||||
|
model.icon().unwrap_or_else(|| provider.icon()),
|
||||||
|
)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(IconSize::XSmall),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(model.name().0)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
_ => Label::new("No model selected")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.into_any_element(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::ChevronDown)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(IconSize::XSmall),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||||
if self.thread.read(cx).is_empty() {
|
if self.thread.read(cx).is_empty() {
|
||||||
return self.render_thread_empty_state(cx).into_any_element();
|
return self.render_thread_empty_state(cx).into_any_element();
|
||||||
@@ -306,6 +358,46 @@ impl AssistantPanel {
|
|||||||
.mb_4(),
|
.mb_4(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(v_flex())
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.justify_center()
|
||||||
|
.child(Label::new("Context Examples:").size(LabelSize::Small)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.justify_center()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.p_0p5()
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Terminal)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Disabled),
|
||||||
|
)
|
||||||
|
.child(Label::new("Terminal").size(LabelSize::Small)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.p_0p5()
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Folder)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Disabled),
|
||||||
|
)
|
||||||
|
.child(Label::new("/src/components").size(LabelSize::Small)),
|
||||||
|
),
|
||||||
|
)
|
||||||
.when(!recent_threads.is_empty(), |parent| {
|
.when(!recent_threads.is_empty(), |parent| {
|
||||||
parent
|
parent
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -1,485 +0,0 @@
|
|||||||
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_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,27 +0,0 @@
|
|||||||
use gpui::SharedString;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use util::post_inc;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub struct ContextId(pub(crate) usize);
|
|
||||||
|
|
||||||
impl ContextId {
|
|
||||||
pub fn post_inc(&mut self) -> Self {
|
|
||||||
Self(post_inc(&mut self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Some context attached to a message in a thread.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Context {
|
|
||||||
pub id: ContextId,
|
|
||||||
pub name: SharedString,
|
|
||||||
pub kind: ContextKind,
|
|
||||||
pub text: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum ContextKind {
|
|
||||||
File,
|
|
||||||
FetchedUrl,
|
|
||||||
}
|
|
||||||
@@ -1,101 +1,15 @@
|
|||||||
mod fetch_context_picker;
|
|
||||||
mod file_context_picker;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{DismissEvent, SharedString, Task, WeakView};
|
||||||
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, SharedString, Task, View,
|
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||||
WeakView,
|
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
|
||||||
};
|
|
||||||
use picker::{Picker, PickerDelegate};
|
|
||||||
use ui::{prelude::*, ListItem, ListItemSpacing, Tooltip};
|
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
|
||||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
|
||||||
use crate::message_editor::MessageEditor;
|
use crate::message_editor::MessageEditor;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(IntoElement)]
|
||||||
enum ContextPickerMode {
|
pub(super) struct ContextPicker<T: PopoverTrigger> {
|
||||||
Default,
|
message_editor: WeakView<MessageEditor>,
|
||||||
File(View<FileContextPicker>),
|
trigger: T,
|
||||||
Fetch(View<FetchContextPicker>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct ContextPicker {
|
|
||||||
mode: ContextPickerMode,
|
|
||||||
picker: View<Picker<ContextPickerDelegate>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextPicker {
|
|
||||||
pub fn new(
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let delegate = ContextPickerDelegate {
|
|
||||||
context_picker: cx.view().downgrade(),
|
|
||||||
workspace: workspace.clone(),
|
|
||||||
message_editor: message_editor.clone(),
|
|
||||||
entries: vec![
|
|
||||||
ContextPickerEntry {
|
|
||||||
name: "directory".into(),
|
|
||||||
description: "Insert any directory".into(),
|
|
||||||
icon: IconName::Folder,
|
|
||||||
},
|
|
||||||
ContextPickerEntry {
|
|
||||||
name: "file".into(),
|
|
||||||
description: "Insert any file".into(),
|
|
||||||
icon: IconName::File,
|
|
||||||
},
|
|
||||||
ContextPickerEntry {
|
|
||||||
name: "fetch".into(),
|
|
||||||
description: "Fetch content from URL".into(),
|
|
||||||
icon: IconName::Globe,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
selected_ix: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let picker = cx.new_view(|cx| {
|
|
||||||
Picker::nonsearchable_uniform_list(delegate, cx).max_height(Some(rems(20.).into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
ContextPicker {
|
|
||||||
mode: ContextPickerMode::Default,
|
|
||||||
picker,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_mode(&mut self) {
|
|
||||||
self.mode = ContextPickerMode::Default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for ContextPicker {}
|
|
||||||
|
|
||||||
impl FocusableView for ContextPicker {
|
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
|
||||||
match &self.mode {
|
|
||||||
ContextPickerMode::Default => self.picker.focus_handle(cx),
|
|
||||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
|
||||||
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for ContextPicker {
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.w(px(400.))
|
|
||||||
.min_w(px(400.))
|
|
||||||
.map(|parent| match &self.mode {
|
|
||||||
ContextPickerMode::Default => parent.child(self.picker.clone()),
|
|
||||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
|
||||||
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -106,18 +20,26 @@ struct ContextPickerEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ContextPickerDelegate {
|
pub(crate) struct ContextPickerDelegate {
|
||||||
context_picker: WeakView<ContextPicker>,
|
all_entries: Vec<ContextPickerEntry>,
|
||||||
workspace: WeakView<Workspace>,
|
filtered_entries: Vec<ContextPickerEntry>,
|
||||||
message_editor: WeakView<MessageEditor>,
|
message_editor: WeakView<MessageEditor>,
|
||||||
entries: Vec<ContextPickerEntry>,
|
|
||||||
selected_ix: usize,
|
selected_ix: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: PopoverTrigger> ContextPicker<T> {
|
||||||
|
pub(crate) fn new(message_editor: WeakView<MessageEditor>, trigger: T) -> Self {
|
||||||
|
ContextPicker {
|
||||||
|
message_editor,
|
||||||
|
trigger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PickerDelegate for ContextPickerDelegate {
|
impl PickerDelegate for ContextPickerDelegate {
|
||||||
type ListItem = ListItem;
|
type ListItem = ListItem;
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
self.entries.len()
|
self.filtered_entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
fn selected_index(&self) -> usize {
|
||||||
@@ -125,7 +47,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
self.selected_ix = ix.min(self.entries.len().saturating_sub(1));
|
self.selected_ix = ix.min(self.filtered_entries.len().saturating_sub(1));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,51 +55,52 @@ impl PickerDelegate for ContextPickerDelegate {
|
|||||||
"Select a context source…".into()
|
"Select a context source…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
Task::ready(())
|
let all_commands = self.all_entries.clone();
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let filtered_commands = cx
|
||||||
|
.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
if query.is_empty() {
|
||||||
|
all_commands
|
||||||
|
} else {
|
||||||
|
all_commands
|
||||||
|
.into_iter()
|
||||||
|
.filter(|model_info| {
|
||||||
|
model_info
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.contains(&query.to_lowercase())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.delegate.filtered_entries = filtered_commands;
|
||||||
|
this.delegate.set_selected_index(0, cx);
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
if let Some(entry) = self.entries.get(self.selected_ix) {
|
if let Some(entry) = self.filtered_entries.get(self.selected_ix) {
|
||||||
self.context_picker
|
self.message_editor
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |_message_editor, _cx| {
|
||||||
match entry.name.to_string().as_str() {
|
println!("Insert context from {}", entry.name);
|
||||||
"file" => {
|
|
||||||
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
|
||||||
FileContextPicker::new(
|
|
||||||
self.context_picker.clone(),
|
|
||||||
self.workspace.clone(),
|
|
||||||
self.message_editor.clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"fetch" => {
|
|
||||||
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
|
||||||
FetchContextPicker::new(
|
|
||||||
self.context_picker.clone(),
|
|
||||||
self.workspace.clone(),
|
|
||||||
self.message_editor.clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.focus_self();
|
|
||||||
})
|
})
|
||||||
.log_err();
|
.ok();
|
||||||
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||||
self.context_picker
|
|
||||||
.update(cx, |this, cx| match this.mode {
|
fn editor_position(&self) -> PickerEditorPosition {
|
||||||
ContextPickerMode::Default => cx.emit(DismissEvent),
|
PickerEditorPosition::End
|
||||||
ContextPickerMode::File(_) | ContextPickerMode::Fetch(_) => {}
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
@@ -186,13 +109,13 @@ impl PickerDelegate for ContextPickerDelegate {
|
|||||||
selected: bool,
|
selected: bool,
|
||||||
_cx: &mut ViewContext<Picker<Self>>,
|
_cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let entry = &self.entries[ix];
|
let entry = self.filtered_entries.get(ix)?;
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Dense)
|
.spacing(ListItemSpacing::Dense)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let description = entry.description.clone();
|
let description = entry.description.clone();
|
||||||
move |cx| cx.new_view(|_cx| Tooltip::new(description.clone())).into()
|
move |cx| cx.new_view(|_cx| Tooltip::new(description.clone())).into()
|
||||||
@@ -225,3 +148,50 @@ impl PickerDelegate for ContextPickerDelegate {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: PopoverTrigger> RenderOnce for ContextPicker<T> {
|
||||||
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let entries = vec![
|
||||||
|
ContextPickerEntry {
|
||||||
|
name: "directory".into(),
|
||||||
|
description: "Insert any directory".into(),
|
||||||
|
icon: IconName::Folder,
|
||||||
|
},
|
||||||
|
ContextPickerEntry {
|
||||||
|
name: "file".into(),
|
||||||
|
description: "Insert any file".into(),
|
||||||
|
icon: IconName::File,
|
||||||
|
},
|
||||||
|
ContextPickerEntry {
|
||||||
|
name: "web".into(),
|
||||||
|
description: "Fetch content from URL".into(),
|
||||||
|
icon: IconName::Globe,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let delegate = ContextPickerDelegate {
|
||||||
|
all_entries: entries.clone(),
|
||||||
|
message_editor: self.message_editor.clone(),
|
||||||
|
filtered_entries: entries,
|
||||||
|
selected_ix: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let picker =
|
||||||
|
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
|
||||||
|
|
||||||
|
let handle = self
|
||||||
|
.message_editor
|
||||||
|
.update(cx, |this, _| this.context_picker_handle.clone())
|
||||||
|
.ok();
|
||||||
|
PopoverMenu::new("context-picker")
|
||||||
|
.menu(move |_cx| Some(picker.clone()))
|
||||||
|
.trigger(self.trigger)
|
||||||
|
.attach(gpui::AnchorCorner::TopLeft)
|
||||||
|
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||||
|
.offset(gpui::Point {
|
||||||
|
x: px(0.0),
|
||||||
|
y: px(-16.0),
|
||||||
|
})
|
||||||
|
.when_some(handle, |this, handle| this.with_handle(handle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,218 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
|
||||||
use futures::AsyncReadExt as _;
|
|
||||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakView};
|
|
||||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
|
||||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
|
||||||
use picker::{Picker, PickerDelegate};
|
|
||||||
use ui::{prelude::*, ListItem, ListItemSpacing, ViewContext};
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
use crate::context::ContextKind;
|
|
||||||
use crate::context_picker::ContextPicker;
|
|
||||||
use crate::message_editor::MessageEditor;
|
|
||||||
|
|
||||||
pub struct FetchContextPicker {
|
|
||||||
picker: View<Picker<FetchContextPickerDelegate>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FetchContextPicker {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakView<ContextPicker>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, message_editor);
|
|
||||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
|
||||||
|
|
||||||
Self { picker }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusableView for FetchContextPicker {
|
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
|
||||||
self.picker.focus_handle(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for FetchContextPicker {
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
self.picker.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
enum ContentType {
|
|
||||||
Html,
|
|
||||||
Plaintext,
|
|
||||||
Json,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FetchContextPickerDelegate {
|
|
||||||
context_picker: WeakView<ContextPicker>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FetchContextPickerDelegate {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakView<ContextPicker>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
) -> Self {
|
|
||||||
FetchContextPickerDelegate {
|
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
message_editor,
|
|
||||||
url: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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?;
|
|
||||||
|
|
||||||
let mut body = Vec::new();
|
|
||||||
response
|
|
||||||
.body_mut()
|
|
||||||
.read_to_end(&mut body)
|
|
||||||
.await
|
|
||||||
.context("error reading response body")?;
|
|
||||||
|
|
||||||
if response.status().is_client_error() {
|
|
||||||
let text = String::from_utf8_lossy(body.as_slice());
|
|
||||||
bail!(
|
|
||||||
"status error {}, response: {text:?}",
|
|
||||||
response.status().as_u16()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(content_type) = response.headers().get("content-type") else {
|
|
||||||
bail!("missing Content-Type header");
|
|
||||||
};
|
|
||||||
let content_type = content_type
|
|
||||||
.to_str()
|
|
||||||
.context("invalid Content-Type header")?;
|
|
||||||
let content_type = match content_type {
|
|
||||||
"text/html" => ContentType::Html,
|
|
||||||
"text/plain" => ContentType::Plaintext,
|
|
||||||
"application/json" => ContentType::Json,
|
|
||||||
_ => ContentType::Html,
|
|
||||||
};
|
|
||||||
|
|
||||||
match content_type {
|
|
||||||
ContentType::Html => {
|
|
||||||
let mut handlers: Vec<TagHandler> = vec![
|
|
||||||
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
|
||||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
|
||||||
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
|
||||||
];
|
|
||||||
if url.contains("wikipedia.org") {
|
|
||||||
use html_to_markdown::structure::wikipedia;
|
|
||||||
|
|
||||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
|
||||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
|
||||||
handlers.push(Rc::new(
|
|
||||||
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
|
||||||
}
|
|
||||||
|
|
||||||
convert_html_to_markdown(&body[..], &mut handlers)
|
|
||||||
}
|
|
||||||
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
|
||||||
ContentType::Json => {
|
|
||||||
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
|
||||||
|
|
||||||
Ok(format!(
|
|
||||||
"```json\n{}\n```",
|
|
||||||
serde_json::to_string_pretty(&json)?
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PickerDelegate for FetchContextPickerDelegate {
|
|
||||||
type ListItem = ListItem;
|
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
|
|
||||||
|
|
||||||
fn placeholder_text(&self, _cx: &mut ui::WindowContext) -> Arc<str> {
|
|
||||||
"Enter a URL…".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
|
||||||
self.url = query;
|
|
||||||
|
|
||||||
Task::ready(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let http_client = workspace.read(cx).client().http_client().clone();
|
|
||||||
let url = self.url.clone();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let text = Self::build_message(http_client, &url).await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.delegate
|
|
||||||
.message_editor
|
|
||||||
.update(cx, |message_editor, _cx| {
|
|
||||||
message_editor.insert_context(ContextKind::FetchedUrl, url, text);
|
|
||||||
})
|
|
||||||
})??;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
|
||||||
self.context_picker
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.reset_mode();
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
|
||||||
&self,
|
|
||||||
ix: usize,
|
|
||||||
selected: bool,
|
|
||||||
_cx: &mut ViewContext<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
|
||||||
Some(
|
|
||||||
ListItem::new(ix)
|
|
||||||
.inset(true)
|
|
||||||
.spacing(ListItemSpacing::Sparse)
|
|
||||||
.toggle_state(selected)
|
|
||||||
.child(self.url.clone()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
use std::fmt::Write as _;
|
|
||||||
use std::ops::RangeInclusive;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use fuzzy::PathMatch;
|
|
||||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakView};
|
|
||||||
use picker::{Picker, PickerDelegate};
|
|
||||||
use project::{PathMatchCandidateSet, WorktreeId};
|
|
||||||
use ui::{prelude::*, ListItem, ListItemSpacing};
|
|
||||||
use util::ResultExt as _;
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
use crate::context::ContextKind;
|
|
||||||
use crate::context_picker::ContextPicker;
|
|
||||||
use crate::message_editor::MessageEditor;
|
|
||||||
|
|
||||||
pub struct FileContextPicker {
|
|
||||||
picker: View<Picker<FileContextPickerDelegate>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileContextPicker {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakView<ContextPicker>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let delegate = FileContextPickerDelegate::new(context_picker, workspace, message_editor);
|
|
||||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
|
||||||
|
|
||||||
Self { picker }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusableView for FileContextPicker {
|
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
|
||||||
self.picker.focus_handle(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for FileContextPicker {
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
self.picker.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FileContextPickerDelegate {
|
|
||||||
context_picker: WeakView<ContextPicker>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
matches: Vec<PathMatch>,
|
|
||||||
selected_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileContextPickerDelegate {
|
|
||||||
pub fn new(
|
|
||||||
context_picker: WeakView<ContextPicker>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
message_editor: WeakView<MessageEditor>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
context_picker,
|
|
||||||
workspace,
|
|
||||||
message_editor,
|
|
||||||
matches: Vec::new(),
|
|
||||||
selected_index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
workspace: &View<Workspace>,
|
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
|
||||||
) -> Task<Vec<PathMatch>> {
|
|
||||||
if query.is_empty() {
|
|
||||||
let workspace = workspace.read(cx);
|
|
||||||
let project = workspace.project().read(cx);
|
|
||||||
let entries = workspace.recent_navigation_history(Some(10), cx);
|
|
||||||
|
|
||||||
let entries = entries
|
|
||||||
.into_iter()
|
|
||||||
.map(|entries| entries.0)
|
|
||||||
.chain(project.worktrees(cx).flat_map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let id = worktree.id();
|
|
||||||
worktree
|
|
||||||
.child_entries(Path::new(""))
|
|
||||||
.filter(|entry| entry.kind.is_file())
|
|
||||||
.map(move |entry| project::ProjectPath {
|
|
||||||
worktree_id: id,
|
|
||||||
path: entry.path.clone(),
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let path_prefix: Arc<str> = Arc::default();
|
|
||||||
Task::ready(
|
|
||||||
entries
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|entry| {
|
|
||||||
let worktree = project.worktree_for_id(entry.worktree_id, cx)?;
|
|
||||||
let mut full_path = PathBuf::from(worktree.read(cx).root_name());
|
|
||||||
full_path.push(&entry.path);
|
|
||||||
Some(PathMatch {
|
|
||||||
score: 0.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: entry.worktree_id.to_usize(),
|
|
||||||
path: full_path.into(),
|
|
||||||
path_prefix: path_prefix.clone(),
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
is_dir: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
|
||||||
let candidate_sets = worktrees
|
|
||||||
.into_iter()
|
|
||||||
.map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
|
|
||||||
PathMatchCandidateSet {
|
|
||||||
snapshot: worktree.snapshot(),
|
|
||||||
include_ignored: worktree
|
|
||||||
.root_entry()
|
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
|
||||||
include_root_name: true,
|
|
||||||
candidates: project::Candidates::Files,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
cx.foreground_executor().spawn(async move {
|
|
||||||
fuzzy::match_path_sets(
|
|
||||||
candidate_sets.as_slice(),
|
|
||||||
query.as_str(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
100,
|
|
||||||
&cancellation_flag,
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PickerDelegate for FileContextPickerDelegate {
|
|
||||||
type ListItem = ListItem;
|
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
|
||||||
self.matches.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
|
||||||
self.selected_index
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
|
||||||
self.selected_index = ix;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
|
||||||
"Search files…".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
|
||||||
return Task::ready(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
// TODO: This should be probably be run in the background.
|
|
||||||
let paths = search_task.await;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
|
||||||
this.delegate.matches = paths;
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
|
||||||
let mat = &self.matches[self.selected_index];
|
|
||||||
|
|
||||||
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 worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let Some(open_buffer_task) = project
|
|
||||||
.update(&mut cx, |project, cx| {
|
|
||||||
project.open_buffer((worktree_id, path.clone()), cx)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
else {
|
|
||||||
return anyhow::Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let buffer = open_buffer_task.await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.delegate
|
|
||||||
.message_editor
|
|
||||||
.update(cx, |message_editor, cx| {
|
|
||||||
let mut text = String::new();
|
|
||||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
|
||||||
text.push_str(&buffer.read(cx).text());
|
|
||||||
if !text.ends_with('\n') {
|
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
text.push_str("```\n");
|
|
||||||
|
|
||||||
message_editor.insert_context(
|
|
||||||
ContextKind::File,
|
|
||||||
path.to_string_lossy().to_string(),
|
|
||||||
text,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
})??;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
|
||||||
self.context_picker
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.reset_mode();
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
|
||||||
&self,
|
|
||||||
ix: usize,
|
|
||||||
selected: bool,
|
|
||||||
_cx: &mut ViewContext<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
|
||||||
let mat = &self.matches[ix];
|
|
||||||
|
|
||||||
Some(
|
|
||||||
ListItem::new(ix)
|
|
||||||
.inset(true)
|
|
||||||
.spacing(ListItemSpacing::Sparse)
|
|
||||||
.toggle_state(selected)
|
|
||||||
.child(mat.path.to_string_lossy().to_string()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<RangeInclusive<u32>>) -> String {
|
|
||||||
let mut text = String::new();
|
|
||||||
write!(text, "```").unwrap();
|
|
||||||
|
|
||||||
if let Some(path) = path {
|
|
||||||
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
|
||||||
write!(text, "{} ", extension).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(text, "{}", path.display()).unwrap();
|
|
||||||
} else {
|
|
||||||
write!(text, "untitled").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(row_range) = row_range {
|
|
||||||
write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
text.push('\n');
|
|
||||||
text
|
|
||||||
}
|
|
||||||
@@ -1,79 +1,40 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use editor::{Editor, EditorElement, EditorStyle};
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakView};
|
use gpui::{AppContext, FocusableView, Model, TextStyle, View};
|
||||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use picker::Picker;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, IconButtonShape, KeyBinding,
|
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, IconButtonShape, KeyBinding,
|
||||||
PopoverMenu, PopoverMenuHandle, Tooltip,
|
PopoverMenuHandle,
|
||||||
};
|
};
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
use crate::context::{Context, ContextId, ContextKind};
|
use crate::context_picker::{ContextPicker, ContextPickerDelegate};
|
||||||
use crate::context_picker::ContextPicker;
|
|
||||||
use crate::thread::{RequestKind, Thread};
|
use crate::thread::{RequestKind, Thread};
|
||||||
use crate::ui::ContextPill;
|
use crate::Chat;
|
||||||
use crate::{Chat, ToggleModelSelector};
|
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
thread: Model<Thread>,
|
thread: Model<Thread>,
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
context: Vec<Context>,
|
pub(crate) context_picker_handle: PopoverMenuHandle<Picker<ContextPickerDelegate>>,
|
||||||
next_context_id: ContextId,
|
|
||||||
context_picker: View<ContextPicker>,
|
|
||||||
pub(crate) context_picker_handle: PopoverMenuHandle<ContextPicker>,
|
|
||||||
language_model_selector: View<LanguageModelSelector>,
|
|
||||||
use_tools: bool,
|
use_tools: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageEditor {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(thread: Model<Thread>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
thread: Model<Thread>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let weak_self = cx.view().downgrade();
|
|
||||||
Self {
|
Self {
|
||||||
thread,
|
thread,
|
||||||
editor: cx.new_view(|cx| {
|
editor: cx.new_view(|cx| {
|
||||||
let mut editor = Editor::auto_height(80, cx);
|
let mut editor = Editor::auto_height(80, cx);
|
||||||
editor.set_placeholder_text("Ask anything or type @ to add context", cx);
|
editor.set_placeholder_text("Ask anything…", cx);
|
||||||
|
|
||||||
editor
|
editor
|
||||||
}),
|
}),
|
||||||
context: Vec::new(),
|
|
||||||
next_context_id: ContextId(0),
|
|
||||||
context_picker: cx.new_view(|cx| ContextPicker::new(workspace.clone(), weak_self, cx)),
|
|
||||||
context_picker_handle: PopoverMenuHandle::default(),
|
context_picker_handle: PopoverMenuHandle::default(),
|
||||||
language_model_selector: cx.new_view(|cx| {
|
|
||||||
LanguageModelSelector::new(
|
|
||||||
|model, _cx| {
|
|
||||||
println!("Selected {:?}", model.name());
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
use_tools: false,
|
use_tools: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_context(
|
|
||||||
&mut self,
|
|
||||||
kind: ContextKind,
|
|
||||||
name: impl Into<SharedString>,
|
|
||||||
text: impl Into<SharedString>,
|
|
||||||
) {
|
|
||||||
self.context.push(Context {
|
|
||||||
id: self.next_context_id.post_inc(),
|
|
||||||
name: name.into(),
|
|
||||||
kind,
|
|
||||||
text: text.into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||||
self.send_to_model(RequestKind::Chat, cx);
|
self.send_to_model(RequestKind::Chat, cx);
|
||||||
}
|
}
|
||||||
@@ -100,10 +61,9 @@ impl MessageEditor {
|
|||||||
editor.clear(cx);
|
editor.clear(cx);
|
||||||
text
|
text
|
||||||
});
|
});
|
||||||
let context = self.context.drain(..).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
self.thread.update(cx, |thread, cx| {
|
self.thread.update(cx, |thread, cx| {
|
||||||
thread.insert_user_message(user_message, context, cx);
|
thread.insert_user_message(user_message, cx);
|
||||||
let mut request = thread.to_completion_request(request_kind, cx);
|
let mut request = thread.to_completion_request(request_kind, cx);
|
||||||
|
|
||||||
if self.use_tools {
|
if self.use_tools {
|
||||||
@@ -124,55 +84,6 @@ impl MessageEditor {
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
|
||||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
|
||||||
|
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
|
||||||
self.language_model_selector.clone(),
|
|
||||||
ButtonLike::new("active-model")
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.overflow_x_hidden()
|
|
||||||
.flex_grow()
|
|
||||||
.whitespace_nowrap()
|
|
||||||
.child(match (active_provider, active_model) {
|
|
||||||
(Some(provider), Some(model)) => h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Icon::new(
|
|
||||||
model.icon().unwrap_or_else(|| provider.icon()),
|
|
||||||
)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new(model.name().0)
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
_ => Label::new("No model selected")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.into_any_element(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::ChevronDown)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusableView for MessageEditor {
|
impl FocusableView for MessageEditor {
|
||||||
@@ -186,7 +97,6 @@ impl Render for MessageEditor {
|
|||||||
let font_size = TextSize::Default.rems(cx);
|
let font_size = TextSize::Default.rems(cx);
|
||||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
let context_picker = self.context_picker.clone();
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("MessageEditor")
|
.key_context("MessageEditor")
|
||||||
@@ -196,46 +106,12 @@ impl Render for MessageEditor {
|
|||||||
.p_2()
|
.p_2()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex().gap_2().child(ContextPicker::new(
|
||||||
.flex_wrap()
|
cx.view().downgrade(),
|
||||||
.gap_2()
|
IconButton::new("add-context", IconName::Plus)
|
||||||
.child(
|
.shape(IconButtonShape::Square)
|
||||||
PopoverMenu::new("context-picker")
|
.icon_size(IconSize::Small),
|
||||||
.menu(move |_cx| Some(context_picker.clone()))
|
)),
|
||||||
.trigger(
|
|
||||||
IconButton::new("add-context", IconName::Plus)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.icon_size(IconSize::Small),
|
|
||||||
)
|
|
||||||
.attach(gpui::AnchorCorner::TopLeft)
|
|
||||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
|
||||||
.offset(gpui::Point {
|
|
||||||
x: px(0.0),
|
|
||||||
y: px(-16.0),
|
|
||||||
})
|
|
||||||
.with_handle(self.context_picker_handle.clone()),
|
|
||||||
)
|
|
||||||
.children(self.context.iter().map(|context| {
|
|
||||||
ContextPill::new(context.clone()).on_remove({
|
|
||||||
let context = context.clone();
|
|
||||||
Rc::new(cx.listener(move |this, _event, cx| {
|
|
||||||
this.context.retain(|other| other.id != context.id);
|
|
||||||
cx.notify();
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.when(!self.context.is_empty(), |parent| {
|
|
||||||
parent.child(
|
|
||||||
IconButton::new("remove-all-context", IconName::Eraser)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
|
|
||||||
.on_click(cx.listener(|this, _event, cx| {
|
|
||||||
this.context.clear();
|
|
||||||
cx.notify();
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.child({
|
.child({
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
@@ -268,20 +144,21 @@ impl Render for MessageEditor {
|
|||||||
self.use_tools.into(),
|
self.use_tools.into(),
|
||||||
cx.listener(|this, selection, _cx| {
|
cx.listener(|this, selection, _cx| {
|
||||||
this.use_tools = match selection {
|
this.use_tools = match selection {
|
||||||
ToggleState::Selected => true,
|
Selection::Selected => true,
|
||||||
ToggleState::Unselected | ToggleState::Indeterminate => false,
|
Selection::Unselected | Selection::Indeterminate => false,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
)))
|
)))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(self.render_language_model_selector(cx))
|
.child(Button::new("codebase", "Codebase").style(ButtonStyle::Filled))
|
||||||
|
.child(Label::new("or"))
|
||||||
.child(
|
.child(
|
||||||
ButtonLike::new("chat")
|
ButtonLike::new("chat")
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.layer(ElevationIndex::ModalSurface)
|
||||||
.child(Label::new("Submit"))
|
.child(Label::new("Chat"))
|
||||||
.children(
|
.children(
|
||||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||||
.map(|binding| binding.into_any_element()),
|
.map(|binding| binding.into_any_element()),
|
||||||
|
|||||||
@@ -1,312 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use util::{post_inc, TryFutureExt as _};
|
use util::{post_inc, TryFutureExt as _};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::context::{Context, ContextKind};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum RequestKind {
|
pub enum RequestKind {
|
||||||
Chat,
|
Chat,
|
||||||
@@ -64,7 +62,6 @@ pub struct Thread {
|
|||||||
pending_summary: Task<Option<()>>,
|
pending_summary: Task<Option<()>>,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
next_message_id: MessageId,
|
next_message_id: MessageId,
|
||||||
context_by_message: HashMap<MessageId, Vec<Context>>,
|
|
||||||
completion_count: usize,
|
completion_count: usize,
|
||||||
pending_completions: Vec<PendingCompletion>,
|
pending_completions: Vec<PendingCompletion>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
@@ -82,7 +79,6 @@ impl Thread {
|
|||||||
pending_summary: Task::ready(None),
|
pending_summary: Task::ready(None),
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
next_message_id: MessageId(0),
|
next_message_id: MessageId(0),
|
||||||
context_by_message: HashMap::default(),
|
|
||||||
completion_count: 0,
|
completion_count: 0,
|
||||||
pending_completions: Vec::new(),
|
pending_completions: Vec::new(),
|
||||||
tools,
|
tools,
|
||||||
@@ -129,22 +125,12 @@ impl Thread {
|
|||||||
&self.tools
|
&self.tools
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_for_message(&self, id: MessageId) -> Option<&Vec<Context>> {
|
|
||||||
self.context_by_message.get(&id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||||
self.pending_tool_uses_by_id.values().collect()
|
self.pending_tool_uses_by_id.values().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_user_message(
|
pub fn insert_user_message(&mut self, text: impl Into<String>, cx: &mut ModelContext<Self>) {
|
||||||
&mut self,
|
self.insert_message(Role::User, text, cx)
|
||||||
text: impl Into<String>,
|
|
||||||
context: Vec<Context>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
let message_id = self.insert_message(Role::User, text, cx);
|
|
||||||
self.context_by_message.insert(message_id, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_message(
|
pub fn insert_message(
|
||||||
@@ -152,7 +138,7 @@ impl Thread {
|
|||||||
role: Role,
|
role: Role,
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> MessageId {
|
) {
|
||||||
let id = self.next_message_id.post_inc();
|
let id = self.next_message_id.post_inc();
|
||||||
self.messages.push(Message {
|
self.messages.push(Message {
|
||||||
id,
|
id,
|
||||||
@@ -161,7 +147,6 @@ impl Thread {
|
|||||||
});
|
});
|
||||||
self.touch_updated_at();
|
self.touch_updated_at();
|
||||||
cx.emit(ThreadEvent::MessageAdded(id));
|
cx.emit(ThreadEvent::MessageAdded(id));
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
@@ -191,41 +176,6 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(context) = self.context_for_message(message.id) {
|
|
||||||
let mut file_context = String::new();
|
|
||||||
let mut fetch_context = String::new();
|
|
||||||
|
|
||||||
for context in context.iter() {
|
|
||||||
match context.kind {
|
|
||||||
ContextKind::File => {
|
|
||||||
file_context.push_str(&context.text);
|
|
||||||
file_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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 !fetch_context.is_empty() {
|
|
||||||
context_text.push_str("The following fetched results are available\n");
|
|
||||||
context_text.push_str(&fetch_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
request_message
|
|
||||||
.content
|
|
||||||
.push(MessageContent::Text(context_text))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !message.text.is_empty() {
|
if !message.text.is_empty() {
|
||||||
request_message
|
request_message
|
||||||
.content
|
.content
|
||||||
|
|||||||
@@ -159,9 +159,9 @@ impl ThreadStore {
|
|||||||
self.threads.push(cx.new_model(|cx| {
|
self.threads.push(cx.new_model(|cx| {
|
||||||
let mut thread = Thread::new(self.tools.clone(), cx);
|
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||||
thread.set_summary("Introduction to quantum computing", cx);
|
thread.set_summary("Introduction to quantum computing", cx);
|
||||||
thread.insert_user_message("Hello! Can you help me understand quantum computing?", Vec::new(), cx);
|
thread.insert_user_message("Hello! Can you help me understand quantum computing?", cx);
|
||||||
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", cx);
|
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", cx);
|
||||||
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", Vec::new(), cx);
|
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", cx);
|
||||||
thread.insert_message(Role::Assistant, "Certainly! Quantum entanglement is a key principle used in quantum computing. When two qubits become entangled, the state of one qubit is directly related to the state of the other, regardless of the distance between them. This property is used in quantum computing to create complex quantum states and to perform operations on multiple qubits simultaneously. Entanglement allows quantum computers to process information in ways that classical computers cannot, potentially solving certain problems much more efficiently. For example, it's crucial in quantum error correction and in algorithms like quantum teleportation, which is important for quantum communication.", cx);
|
thread.insert_message(Role::Assistant, "Certainly! Quantum entanglement is a key principle used in quantum computing. When two qubits become entangled, the state of one qubit is directly related to the state of the other, regardless of the distance between them. This property is used in quantum computing to create complex quantum states and to perform operations on multiple qubits simultaneously. Entanglement allows quantum computers to process information in ways that classical computers cannot, potentially solving certain problems much more efficiently. For example, it's crucial in quantum error correction and in algorithms like quantum teleportation, which is important for quantum communication.", cx);
|
||||||
thread
|
thread
|
||||||
}));
|
}));
|
||||||
@@ -169,7 +169,7 @@ impl ThreadStore {
|
|||||||
self.threads.push(cx.new_model(|cx| {
|
self.threads.push(cx.new_model(|cx| {
|
||||||
let mut thread = Thread::new(self.tools.clone(), cx);
|
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||||
thread.set_summary("Rust web development and async programming", cx);
|
thread.set_summary("Rust web development and async programming", cx);
|
||||||
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", Vec::new(), cx);
|
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", cx);
|
||||||
thread.insert_message(Role::Assistant, "Certainly! Here's an example of a simple web server in Rust using the `actix-web` framework:
|
thread.insert_message(Role::Assistant, "Certainly! Here's an example of a simple web server in Rust using the `actix-web` framework:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -206,7 +206,7 @@ impl ThreadStore {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then you can run the server with `cargo run` and access it at `http://localhost:8080`.".unindent(), cx);
|
Then you can run the server with `cargo run` and access it at `http://localhost:8080`.".unindent(), cx);
|
||||||
thread.insert_user_message("That's great! Can you explain more about async functions in Rust?", Vec::new(), cx);
|
thread.insert_user_message("That's great! Can you explain more about async functions in Rust?", cx);
|
||||||
thread.insert_message(Role::Assistant, "Certainly! Async functions are a key feature in Rust for writing efficient, non-blocking code, especially for I/O-bound operations. Here's an overview:
|
thread.insert_message(Role::Assistant, "Certainly! Async functions are a key feature in Rust for writing efficient, non-blocking code, especially for I/O-bound operations. Here's an overview:
|
||||||
|
|
||||||
1. **Syntax**: Async functions are declared using the `async` keyword:
|
1. **Syntax**: Async functions are declared using the `async` keyword:
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
mod context_pill;
|
|
||||||
|
|
||||||
pub use context_pill::*;
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::ClickEvent;
|
|
||||||
use ui::{prelude::*, IconButtonShape};
|
|
||||||
|
|
||||||
use crate::context::Context;
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct ContextPill {
|
|
||||||
context: Context,
|
|
||||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextPill {
|
|
||||||
pub fn new(context: Context) -> Self {
|
|
||||||
Self {
|
|
||||||
context,
|
|
||||||
on_remove: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_remove(mut self, on_remove: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
|
|
||||||
self.on_remove = Some(on_remove);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for ContextPill {
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.px_1()
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.rounded_md()
|
|
||||||
.child(Label::new(self.context.name.clone()).size(LabelSize::Small))
|
|
||||||
.when_some(self.on_remove, |parent, on_remove| {
|
|
||||||
parent.child(
|
|
||||||
IconButton::new("remove", IconName::Close)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.icon_size(IconSize::XSmall)
|
|
||||||
.on_click({
|
|
||||||
let on_remove = on_remove.clone();
|
|
||||||
move |event, cx| on_remove(event, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,10 +18,11 @@ test-support = [
|
|||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
"gpui/test-support",
|
"gpui/test-support",
|
||||||
"livekit_client/test-support",
|
"livekit_client/test-support",
|
||||||
"livekit_client_macos/test-support",
|
|
||||||
"project/test-support",
|
"project/test-support",
|
||||||
"util/test-support"
|
"util/test-support"
|
||||||
]
|
]
|
||||||
|
livekit-macos = ["livekit_client_macos"]
|
||||||
|
livekit-cross-platform = ["livekit_client"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@@ -41,12 +42,8 @@ serde.workspace = true
|
|||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
livekit_client_macos = { workspace = true, optional = true }
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
livekit_client = { workspace = true, optional = true }
|
||||||
livekit_client_macos = { workspace = true }
|
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
|
||||||
livekit_client = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { workspace = true, features = ["test-support"] }
|
client = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
pub mod call_settings;
|
pub mod call_settings;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(
|
||||||
|
all(target_os = "macos", feature = "livekit-macos"),
|
||||||
|
all(
|
||||||
|
not(target_os = "macos"),
|
||||||
|
feature = "livekit-macos",
|
||||||
|
not(feature = "livekit-cross-platform")
|
||||||
|
)
|
||||||
|
))]
|
||||||
mod macos;
|
mod macos;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(
|
||||||
|
all(target_os = "macos", feature = "livekit-macos"),
|
||||||
|
all(
|
||||||
|
not(target_os = "macos"),
|
||||||
|
feature = "livekit-macos",
|
||||||
|
not(feature = "livekit-cross-platform")
|
||||||
|
)
|
||||||
|
))]
|
||||||
pub use macos::*;
|
pub use macos::*;
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(any(
|
||||||
|
all(
|
||||||
|
target_os = "macos",
|
||||||
|
feature = "livekit-cross-platform",
|
||||||
|
not(feature = "livekit-macos"),
|
||||||
|
),
|
||||||
|
all(not(target_os = "macos"), feature = "livekit-cross-platform"),
|
||||||
|
))]
|
||||||
mod cross_platform;
|
mod cross_platform;
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(any(
|
||||||
|
all(
|
||||||
|
target_os = "macos",
|
||||||
|
feature = "livekit-cross-platform",
|
||||||
|
not(feature = "livekit-macos"),
|
||||||
|
),
|
||||||
|
all(not(target_os = "macos"), feature = "livekit-cross-platform"),
|
||||||
|
))]
|
||||||
pub use cross_platform::*;
|
pub use cross_platform::*;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub struct CallSettingsContent {
|
|||||||
|
|
||||||
/// Whether your current project should be shared when joining an empty channel.
|
/// Whether your current project should be shared when joining an empty channel.
|
||||||
///
|
///
|
||||||
/// Default: false
|
/// Default: true
|
||||||
pub share_on_join: Option<bool>,
|
pub share_on_join: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1288,12 +1288,6 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn muted_by_user(&self) -> bool {
|
|
||||||
self.live_kit
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |live_kit| live_kit.muted_by_user)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_speaking(&self) -> bool {
|
pub fn is_speaking(&self) -> bool {
|
||||||
self.live_kit
|
self.live_kit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -1307,12 +1307,6 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn muted_by_user(&self) -> bool {
|
|
||||||
self.live_kit
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |live_kit| live_kit.muted_by_user)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_speaking(&self) -> bool {
|
pub fn is_speaking(&self) -> bool {
|
||||||
self.live_kit
|
self.live_kit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ use std::time::Instant;
|
|||||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use telemetry_events::{
|
use telemetry_events::{
|
||||||
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event,
|
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event,
|
||||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, InlineCompletionRating,
|
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, ReplEvent, SettingEvent,
|
||||||
InlineCompletionRatingEvent, ReplEvent, SettingEvent,
|
|
||||||
};
|
};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||||
@@ -356,24 +355,6 @@ impl Telemetry {
|
|||||||
self.report_event(event)
|
self.report_event(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_inline_completion_rating_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
rating: InlineCompletionRating,
|
|
||||||
input_events: Arc<str>,
|
|
||||||
input_excerpt: Arc<str>,
|
|
||||||
output_excerpt: Arc<str>,
|
|
||||||
feedback: String,
|
|
||||||
) {
|
|
||||||
let event = Event::InlineCompletionRating(InlineCompletionRatingEvent {
|
|
||||||
rating,
|
|
||||||
input_events,
|
|
||||||
input_excerpt,
|
|
||||||
output_excerpt,
|
|
||||||
feedback,
|
|
||||||
});
|
|
||||||
self.report_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
|
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
|
||||||
self.report_event(Event::Assistant(event));
|
self.report_event(Event::Assistant(event));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ LLM_DATABASE_URL = "postgres://postgres@localhost/zed_llm"
|
|||||||
LLM_DATABASE_MAX_CONNECTIONS = 5
|
LLM_DATABASE_MAX_CONNECTIONS = 5
|
||||||
LLM_API_SECRET = "llm-secret"
|
LLM_API_SECRET = "llm-secret"
|
||||||
|
|
||||||
|
# CLICKHOUSE_URL = ""
|
||||||
|
# CLICKHOUSE_USER = "default"
|
||||||
|
# CLICKHOUSE_PASSWORD = ""
|
||||||
|
# CLICKHOUSE_DATABASE = "default"
|
||||||
|
|
||||||
# SLACK_PANICS_WEBHOOK = ""
|
# SLACK_PANICS_WEBHOOK = ""
|
||||||
|
|
||||||
# RUST_LOG=info
|
# RUST_LOG=info
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ axum = { version = "0.6", features = ["json", "headers", "ws"] }
|
|||||||
axum-extra = { version = "0.4", features = ["erased-json"] }
|
axum-extra = { version = "0.4", features = ["erased-json"] }
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
clickhouse.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
dashmap.workspace = true
|
dashmap.workspace = true
|
||||||
@@ -76,6 +77,12 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "re
|
|||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
livekit_client_macos = { workspace = true, features = ["test-support"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||||
|
livekit_client = { workspace = true, features = ["test-support"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assistant = { workspace = true, features = ["test-support"] }
|
assistant = { workspace = true, features = ["test-support"] }
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
|
|||||||
@@ -149,21 +149,6 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: google-ai
|
name: google-ai
|
||||||
key: api_key
|
key: api_key
|
||||||
- name: PREDICTION_API_URL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: prediction
|
|
||||||
key: api_url
|
|
||||||
- name: PREDICTION_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: prediction
|
|
||||||
key: api_key
|
|
||||||
- name: PREDICTION_MODEL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: prediction
|
|
||||||
key: model
|
|
||||||
- name: BLOB_STORE_ACCESS_KEY
|
- name: BLOB_STORE_ACCESS_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
@@ -214,6 +199,26 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: blob-store
|
name: blob-store
|
||||||
key: bucket
|
key: bucket
|
||||||
|
- name: CLICKHOUSE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: clickhouse
|
||||||
|
key: url
|
||||||
|
- name: CLICKHOUSE_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: clickhouse
|
||||||
|
key: user
|
||||||
|
- name: CLICKHOUSE_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: clickhouse
|
||||||
|
key: password
|
||||||
|
- name: CLICKHOUSE_DATABASE
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: clickhouse
|
||||||
|
key: database
|
||||||
- name: SLACK_PANICS_WEBHOOK
|
- name: SLACK_PANICS_WEBHOOK
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use collections::HashSet;
|
|||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use sea_orm::ActiveValue;
|
use sea_orm::ActiveValue;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
|
||||||
use std::{str::FromStr, sync::Arc, time::Duration};
|
use std::{str::FromStr, sync::Arc, time::Duration};
|
||||||
use stripe::{
|
use stripe::{
|
||||||
BillingPortalSession, CreateBillingPortalSession, CreateBillingPortalSessionFlowData,
|
BillingPortalSession, CreateBillingPortalSession, CreateBillingPortalSessionFlowData,
|
||||||
@@ -20,7 +19,6 @@ use stripe::{
|
|||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
use crate::api::events::SnowflakeRow;
|
|
||||||
use crate::llm::{DEFAULT_MAX_MONTHLY_SPEND, FREE_TIER_MONTHLY_SPENDING_LIMIT};
|
use crate::llm::{DEFAULT_MAX_MONTHLY_SPEND, FREE_TIER_MONTHLY_SPENDING_LIMIT};
|
||||||
use crate::rpc::{ResultExt as _, Server};
|
use crate::rpc::{ResultExt as _, Server};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -102,9 +100,6 @@ async fn update_billing_preferences(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("user not found"))?;
|
.ok_or_else(|| anyhow!("user not found"))?;
|
||||||
|
|
||||||
let max_monthly_llm_usage_spending_in_cents =
|
|
||||||
body.max_monthly_llm_usage_spending_in_cents.max(0);
|
|
||||||
|
|
||||||
let billing_preferences =
|
let billing_preferences =
|
||||||
if let Some(_billing_preferences) = app.db.get_billing_preferences(user.id).await? {
|
if let Some(_billing_preferences) = app.db.get_billing_preferences(user.id).await? {
|
||||||
app.db
|
app.db
|
||||||
@@ -112,7 +107,7 @@ async fn update_billing_preferences(
|
|||||||
user.id,
|
user.id,
|
||||||
&UpdateBillingPreferencesParams {
|
&UpdateBillingPreferencesParams {
|
||||||
max_monthly_llm_usage_spending_in_cents: ActiveValue::set(
|
max_monthly_llm_usage_spending_in_cents: ActiveValue::set(
|
||||||
max_monthly_llm_usage_spending_in_cents,
|
body.max_monthly_llm_usage_spending_in_cents,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -122,26 +117,13 @@ async fn update_billing_preferences(
|
|||||||
.create_billing_preferences(
|
.create_billing_preferences(
|
||||||
user.id,
|
user.id,
|
||||||
&crate::db::CreateBillingPreferencesParams {
|
&crate::db::CreateBillingPreferencesParams {
|
||||||
max_monthly_llm_usage_spending_in_cents,
|
max_monthly_llm_usage_spending_in_cents: body
|
||||||
|
.max_monthly_llm_usage_spending_in_cents,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
SnowflakeRow::new(
|
|
||||||
"Spend Limit Updated",
|
|
||||||
user.metrics_id,
|
|
||||||
user.admin,
|
|
||||||
None,
|
|
||||||
json!({
|
|
||||||
"user_id": user.id,
|
|
||||||
"max_monthly_llm_usage_spending_in_cents": billing_preferences.max_monthly_llm_usage_spending_in_cents,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.write(&app.kinesis_client, &app.config.kinesis_stream)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
rpc_server.refresh_llm_tokens_for_user(user.id).await;
|
rpc_server.refresh_llm_tokens_for_user(user.id).await;
|
||||||
|
|
||||||
Ok(Json(BillingPreferencesResponse {
|
Ok(Json(BillingPreferencesResponse {
|
||||||
|
|||||||
28
crates/collab/src/clickhouse.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
/// Writes the given rows to the specified Clickhouse table.
|
||||||
|
pub async fn write_to_table<T: clickhouse::Row + Serialize + std::fmt::Debug>(
|
||||||
|
table: &str,
|
||||||
|
rows: &[T],
|
||||||
|
clickhouse_client: &clickhouse::Client,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if rows.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut insert = clickhouse_client.insert(table)?;
|
||||||
|
|
||||||
|
for event in rows {
|
||||||
|
insert.write(event).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert.end().await?;
|
||||||
|
|
||||||
|
let event_count = rows.len();
|
||||||
|
log::info!(
|
||||||
|
"wrote {event_count} {event_specifier} to '{table}'",
|
||||||
|
event_specifier = if event_count == 1 { "event" } else { "events" }
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
mod cents;
|
mod cents;
|
||||||
|
pub mod clickhouse;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod env;
|
pub mod env;
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
@@ -150,6 +151,10 @@ pub struct Config {
|
|||||||
pub seed_path: Option<PathBuf>,
|
pub seed_path: Option<PathBuf>,
|
||||||
pub database_max_connections: u32,
|
pub database_max_connections: u32,
|
||||||
pub api_token: String,
|
pub api_token: String,
|
||||||
|
pub clickhouse_url: Option<String>,
|
||||||
|
pub clickhouse_user: Option<String>,
|
||||||
|
pub clickhouse_password: Option<String>,
|
||||||
|
pub clickhouse_database: Option<String>,
|
||||||
pub invite_link_prefix: String,
|
pub invite_link_prefix: String,
|
||||||
pub livekit_server: Option<String>,
|
pub livekit_server: Option<String>,
|
||||||
pub livekit_key: Option<String>,
|
pub livekit_key: Option<String>,
|
||||||
@@ -175,9 +180,6 @@ pub struct Config {
|
|||||||
pub anthropic_api_key: Option<Arc<str>>,
|
pub anthropic_api_key: Option<Arc<str>>,
|
||||||
pub anthropic_staff_api_key: Option<Arc<str>>,
|
pub anthropic_staff_api_key: Option<Arc<str>>,
|
||||||
pub llm_closed_beta_model_name: Option<Arc<str>>,
|
pub llm_closed_beta_model_name: Option<Arc<str>>,
|
||||||
pub prediction_api_url: Option<Arc<str>>,
|
|
||||||
pub prediction_api_key: Option<Arc<str>>,
|
|
||||||
pub prediction_model: Option<Arc<str>>,
|
|
||||||
pub zed_client_checksum_seed: Option<String>,
|
pub zed_client_checksum_seed: Option<String>,
|
||||||
pub slack_panics_webhook: Option<String>,
|
pub slack_panics_webhook: Option<String>,
|
||||||
pub auto_join_channel_id: Option<ChannelId>,
|
pub auto_join_channel_id: Option<ChannelId>,
|
||||||
@@ -228,9 +230,10 @@ impl Config {
|
|||||||
anthropic_api_key: None,
|
anthropic_api_key: None,
|
||||||
anthropic_staff_api_key: None,
|
anthropic_staff_api_key: None,
|
||||||
llm_closed_beta_model_name: None,
|
llm_closed_beta_model_name: None,
|
||||||
prediction_api_url: None,
|
clickhouse_url: None,
|
||||||
prediction_api_key: None,
|
clickhouse_user: None,
|
||||||
prediction_model: None,
|
clickhouse_password: None,
|
||||||
|
clickhouse_database: None,
|
||||||
zed_client_checksum_seed: None,
|
zed_client_checksum_seed: None,
|
||||||
slack_panics_webhook: None,
|
slack_panics_webhook: None,
|
||||||
auto_join_channel_id: None,
|
auto_join_channel_id: None,
|
||||||
@@ -280,6 +283,7 @@ pub struct AppState {
|
|||||||
pub stripe_billing: Option<Arc<StripeBilling>>,
|
pub stripe_billing: Option<Arc<StripeBilling>>,
|
||||||
pub rate_limiter: Arc<RateLimiter>,
|
pub rate_limiter: Arc<RateLimiter>,
|
||||||
pub executor: Executor,
|
pub executor: Executor,
|
||||||
|
pub clickhouse_client: Option<::clickhouse::Client>,
|
||||||
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
|
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
}
|
}
|
||||||
@@ -333,6 +337,10 @@ impl AppState {
|
|||||||
stripe_client,
|
stripe_client,
|
||||||
rate_limiter: Arc::new(RateLimiter::new(db)),
|
rate_limiter: Arc::new(RateLimiter::new(db)),
|
||||||
executor,
|
executor,
|
||||||
|
clickhouse_client: config
|
||||||
|
.clickhouse_url
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|_| build_clickhouse_client(&config).log_err()),
|
||||||
kinesis_client: if config.kinesis_access_key.is_some() {
|
kinesis_client: if config.kinesis_access_key.is_some() {
|
||||||
build_kinesis_client(&config).await.log_err()
|
build_kinesis_client(&config).await.log_err()
|
||||||
} else {
|
} else {
|
||||||
@@ -415,3 +423,31 @@ async fn build_kinesis_client(config: &Config) -> anyhow::Result<aws_sdk_kinesis
|
|||||||
|
|
||||||
Ok(aws_sdk_kinesis::Client::new(&kinesis_config))
|
Ok(aws_sdk_kinesis::Client::new(&kinesis_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_clickhouse_client(config: &Config) -> anyhow::Result<::clickhouse::Client> {
|
||||||
|
Ok(::clickhouse::Client::default()
|
||||||
|
.with_url(
|
||||||
|
config
|
||||||
|
.clickhouse_url
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("missing clickhouse_url"))?,
|
||||||
|
)
|
||||||
|
.with_user(
|
||||||
|
config
|
||||||
|
.clickhouse_user
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("missing clickhouse_user"))?,
|
||||||
|
)
|
||||||
|
.with_password(
|
||||||
|
config
|
||||||
|
.clickhouse_password
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("missing clickhouse_password"))?,
|
||||||
|
)
|
||||||
|
.with_database(
|
||||||
|
config
|
||||||
|
.clickhouse_database
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("missing clickhouse_database"))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
mod authorization;
|
mod authorization;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
mod telemetry;
|
||||||
mod token;
|
mod token;
|
||||||
|
|
||||||
use crate::api::events::SnowflakeRow;
|
use crate::api::events::SnowflakeRow;
|
||||||
use crate::api::CloudflareIpCountryHeader;
|
use crate::api::CloudflareIpCountryHeader;
|
||||||
use crate::build_kinesis_client;
|
use crate::build_kinesis_client;
|
||||||
use crate::{db::UserId, executor::Executor, Cents, Config, Error, Result};
|
use crate::{
|
||||||
|
build_clickhouse_client, db::UserId, executor::Executor, Cents, Config, Error, Result,
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Context as _};
|
use anyhow::{anyhow, Context as _};
|
||||||
use authorization::authorize_access_to_language_model;
|
use authorization::authorize_access_to_language_model;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
@@ -26,10 +29,7 @@ use reqwest_client::ReqwestClient;
|
|||||||
use rpc::{
|
use rpc::{
|
||||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||||
};
|
};
|
||||||
use rpc::{
|
use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME};
|
||||||
ListModelsResponse, PredictEditsParams, PredictEditsResponse,
|
|
||||||
MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME,
|
|
||||||
};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{
|
use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@@ -37,6 +37,7 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
use telemetry::{report_llm_rate_limit, report_llm_usage, LlmRateLimitEventRow, LlmUsageEventRow};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ pub struct LlmState {
|
|||||||
pub db: Arc<LlmDatabase>,
|
pub db: Arc<LlmDatabase>,
|
||||||
pub http_client: ReqwestClient,
|
pub http_client: ReqwestClient,
|
||||||
pub kinesis_client: Option<aws_sdk_kinesis::Client>,
|
pub kinesis_client: Option<aws_sdk_kinesis::Client>,
|
||||||
|
pub clickhouse_client: Option<clickhouse::Client>,
|
||||||
active_user_count_by_model:
|
active_user_count_by_model:
|
||||||
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
|
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
|
||||||
}
|
}
|
||||||
@@ -84,6 +86,10 @@ impl LlmState {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
clickhouse_client: config
|
||||||
|
.clickhouse_url
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|_| build_clickhouse_client(&config).log_err()),
|
||||||
active_user_count_by_model: RwLock::new(HashMap::default()),
|
active_user_count_by_model: RwLock::new(HashMap::default()),
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
@@ -120,7 +126,6 @@ pub fn routes() -> Router<(), Body> {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/models", get(list_models))
|
.route("/models", get(list_models))
|
||||||
.route("/completion", post(perform_completion))
|
.route("/completion", post(perform_completion))
|
||||||
.route("/predict_edits", post(predict_edits))
|
|
||||||
.layer(middleware::from_fn(validate_api_token))
|
.layer(middleware::from_fn(validate_api_token))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,59 +439,6 @@ fn normalize_model_name(known_models: Vec<String>, name: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn predict_edits(
|
|
||||||
Extension(state): Extension<Arc<LlmState>>,
|
|
||||||
Extension(claims): Extension<LlmTokenClaims>,
|
|
||||||
_country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
|
|
||||||
Json(params): Json<PredictEditsParams>,
|
|
||||||
) -> Result<impl IntoResponse> {
|
|
||||||
if !claims.is_staff {
|
|
||||||
return Err(anyhow!("not found"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let api_url = state
|
|
||||||
.config
|
|
||||||
.prediction_api_url
|
|
||||||
.as_ref()
|
|
||||||
.context("no PREDICTION_API_URL configured on the server")?;
|
|
||||||
let api_key = state
|
|
||||||
.config
|
|
||||||
.prediction_api_key
|
|
||||||
.as_ref()
|
|
||||||
.context("no PREDICTION_API_KEY configured on the server")?;
|
|
||||||
let model = state
|
|
||||||
.config
|
|
||||||
.prediction_model
|
|
||||||
.as_ref()
|
|
||||||
.context("no PREDICTION_MODEL configured on the server")?;
|
|
||||||
let prompt = include_str!("./llm/prediction_prompt.md")
|
|
||||||
.replace("<events>", ¶ms.input_events)
|
|
||||||
.replace("<excerpt>", ¶ms.input_excerpt);
|
|
||||||
let mut response = open_ai::complete_text(
|
|
||||||
&state.http_client,
|
|
||||||
api_url,
|
|
||||||
api_key,
|
|
||||||
open_ai::CompletionRequest {
|
|
||||||
model: model.to_string(),
|
|
||||||
prompt: prompt.clone(),
|
|
||||||
max_tokens: 1024,
|
|
||||||
temperature: 0.,
|
|
||||||
prediction: Some(open_ai::Prediction::Content {
|
|
||||||
content: params.input_excerpt,
|
|
||||||
}),
|
|
||||||
rewrite_speculation: Some(true),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let choice = response
|
|
||||||
.choices
|
|
||||||
.pop()
|
|
||||||
.context("no output from completion response")?;
|
|
||||||
Ok(Json(PredictEditsResponse {
|
|
||||||
output_excerpt: choice.text,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The maximum monthly spending an individual user can reach on the free tier
|
/// The maximum monthly spending an individual user can reach on the free tier
|
||||||
/// before they have to pay.
|
/// before they have to pay.
|
||||||
pub const FREE_TIER_MONTHLY_SPENDING_LIMIT: Cents = Cents::from_dollars(10);
|
pub const FREE_TIER_MONTHLY_SPENDING_LIMIT: Cents = Cents::from_dollars(10);
|
||||||
@@ -621,6 +573,34 @@ async fn check_usage_limit(
|
|||||||
.await
|
.await
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
|
if let Some(client) = state.clickhouse_client.as_ref() {
|
||||||
|
report_llm_rate_limit(
|
||||||
|
client,
|
||||||
|
LlmRateLimitEventRow {
|
||||||
|
time: Utc::now().timestamp_millis(),
|
||||||
|
user_id: claims.user_id as i32,
|
||||||
|
is_staff: claims.is_staff,
|
||||||
|
plan: match claims.plan {
|
||||||
|
Plan::Free => "free".to_string(),
|
||||||
|
Plan::ZedPro => "zed_pro".to_string(),
|
||||||
|
},
|
||||||
|
model: model.name.clone(),
|
||||||
|
provider: provider.to_string(),
|
||||||
|
usage_measure: resource.to_string(),
|
||||||
|
requests_this_minute: usage.requests_this_minute as u64,
|
||||||
|
tokens_this_minute: usage.tokens_this_minute as u64,
|
||||||
|
tokens_this_day: usage.tokens_this_day as u64,
|
||||||
|
users_in_recent_minutes: users_in_recent_minutes as u64,
|
||||||
|
users_in_recent_days: users_in_recent_days as u64,
|
||||||
|
max_requests_per_minute: per_user_max_requests_per_minute as u64,
|
||||||
|
max_tokens_per_minute: per_user_max_tokens_per_minute as u64,
|
||||||
|
max_tokens_per_day: per_user_max_tokens_per_day as u64,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
return Err(Error::http(
|
return Err(Error::http(
|
||||||
StatusCode::TOO_MANY_REQUESTS,
|
StatusCode::TOO_MANY_REQUESTS,
|
||||||
format!("Rate limit exceeded. Maximum {} reached.", resource),
|
format!("Rate limit exceeded. Maximum {} reached.", resource),
|
||||||
@@ -707,8 +687,6 @@ impl<S> Drop for TokenCountingStream<S> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let properties = json!({
|
let properties = json!({
|
||||||
"has_llm_subscription": claims.has_llm_subscription,
|
|
||||||
"max_monthly_spend_in_cents": claims.max_monthly_spend_in_cents,
|
|
||||||
"plan": match claims.plan {
|
"plan": match claims.plan {
|
||||||
Plan::Free => "free".to_string(),
|
Plan::Free => "free".to_string(),
|
||||||
Plan::ZedPro => "zed_pro".to_string(),
|
Plan::ZedPro => "zed_pro".to_string(),
|
||||||
@@ -728,6 +706,44 @@ impl<S> Drop for TokenCountingStream<S> {
|
|||||||
.write(&state.kinesis_client, &state.config.kinesis_stream)
|
.write(&state.kinesis_client, &state.config.kinesis_stream)
|
||||||
.await
|
.await
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
|
if let Some(clickhouse_client) = state.clickhouse_client.as_ref() {
|
||||||
|
report_llm_usage(
|
||||||
|
clickhouse_client,
|
||||||
|
LlmUsageEventRow {
|
||||||
|
time: Utc::now().timestamp_millis(),
|
||||||
|
user_id: claims.user_id as i32,
|
||||||
|
is_staff: claims.is_staff,
|
||||||
|
plan: match claims.plan {
|
||||||
|
Plan::Free => "free".to_string(),
|
||||||
|
Plan::ZedPro => "zed_pro".to_string(),
|
||||||
|
},
|
||||||
|
model,
|
||||||
|
provider: provider.to_string(),
|
||||||
|
input_token_count: tokens.input as u64,
|
||||||
|
cache_creation_input_token_count: tokens.input_cache_creation as u64,
|
||||||
|
cache_read_input_token_count: tokens.input_cache_read as u64,
|
||||||
|
output_token_count: tokens.output as u64,
|
||||||
|
requests_this_minute: usage.requests_this_minute as u64,
|
||||||
|
tokens_this_minute: usage.tokens_this_minute as u64,
|
||||||
|
tokens_this_day: usage.tokens_this_day as u64,
|
||||||
|
input_tokens_this_month: usage.tokens_this_month.input as u64,
|
||||||
|
cache_creation_input_tokens_this_month: usage
|
||||||
|
.tokens_this_month
|
||||||
|
.input_cache_creation
|
||||||
|
as u64,
|
||||||
|
cache_read_input_tokens_this_month: usage
|
||||||
|
.tokens_this_month
|
||||||
|
.input_cache_read
|
||||||
|
as u64,
|
||||||
|
output_tokens_this_month: usage.tokens_this_month.output as u64,
|
||||||
|
spending_this_month: usage.spending_this_month.0 as u64,
|
||||||
|
lifetime_spending: usage.lifetime_spending.0 as u64,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
|
|
||||||
|
|
||||||
### Instruction:
|
|
||||||
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
|
|
||||||
|
|
||||||
### Events:
|
|
||||||
<events>
|
|
||||||
|
|
||||||
### Input:
|
|
||||||
<excerpt>
|
|
||||||
|
|
||||||
### Response:
|
|
||||||
65
crates/collab/src/llm/telemetry.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::clickhouse::write_to_table;
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
|
pub struct LlmUsageEventRow {
|
||||||
|
pub time: i64,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub is_staff: bool,
|
||||||
|
pub plan: String,
|
||||||
|
pub model: String,
|
||||||
|
pub provider: String,
|
||||||
|
pub input_token_count: u64,
|
||||||
|
pub cache_creation_input_token_count: u64,
|
||||||
|
pub cache_read_input_token_count: u64,
|
||||||
|
pub output_token_count: u64,
|
||||||
|
pub requests_this_minute: u64,
|
||||||
|
pub tokens_this_minute: u64,
|
||||||
|
pub tokens_this_day: u64,
|
||||||
|
pub input_tokens_this_month: u64,
|
||||||
|
pub cache_creation_input_tokens_this_month: u64,
|
||||||
|
pub cache_read_input_tokens_this_month: u64,
|
||||||
|
pub output_tokens_this_month: u64,
|
||||||
|
pub spending_this_month: u64,
|
||||||
|
pub lifetime_spending: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
|
pub struct LlmRateLimitEventRow {
|
||||||
|
pub time: i64,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub is_staff: bool,
|
||||||
|
pub plan: String,
|
||||||
|
pub model: String,
|
||||||
|
pub provider: String,
|
||||||
|
pub usage_measure: String,
|
||||||
|
pub requests_this_minute: u64,
|
||||||
|
pub tokens_this_minute: u64,
|
||||||
|
pub tokens_this_day: u64,
|
||||||
|
pub users_in_recent_minutes: u64,
|
||||||
|
pub users_in_recent_days: u64,
|
||||||
|
pub max_requests_per_minute: u64,
|
||||||
|
pub max_tokens_per_minute: u64,
|
||||||
|
pub max_tokens_per_day: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn report_llm_usage(client: &clickhouse::Client, row: LlmUsageEventRow) -> Result<()> {
|
||||||
|
const LLM_USAGE_EVENTS_TABLE: &str = "llm_usage_events";
|
||||||
|
write_to_table(LLM_USAGE_EVENTS_TABLE, &[row], client)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to upload to table '{LLM_USAGE_EVENTS_TABLE}'"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn report_llm_rate_limit(
|
||||||
|
client: &clickhouse::Client,
|
||||||
|
row: LlmRateLimitEventRow,
|
||||||
|
) -> Result<()> {
|
||||||
|
const LLM_RATE_LIMIT_EVENTS_TABLE: &str = "llm_rate_limit_events";
|
||||||
|
write_to_table(LLM_RATE_LIMIT_EVENTS_TABLE, &[row], client)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to upload to table '{LLM_RATE_LIMIT_EVENTS_TABLE}'"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -17,8 +17,10 @@ pub struct LlmTokenClaims {
|
|||||||
pub exp: u64,
|
pub exp: u64,
|
||||||
pub jti: String,
|
pub jti: String,
|
||||||
pub user_id: u64,
|
pub user_id: u64,
|
||||||
|
#[serde(default)]
|
||||||
pub system_id: Option<String>,
|
pub system_id: Option<String>,
|
||||||
pub metrics_id: Uuid,
|
#[serde(default)]
|
||||||
|
pub metrics_id: Option<Uuid>,
|
||||||
pub github_user_login: String,
|
pub github_user_login: String,
|
||||||
pub is_staff: bool,
|
pub is_staff: bool,
|
||||||
pub has_llm_closed_beta_feature_flag: bool,
|
pub has_llm_closed_beta_feature_flag: bool,
|
||||||
@@ -54,7 +56,7 @@ impl LlmTokenClaims {
|
|||||||
jti: uuid::Uuid::new_v4().to_string(),
|
jti: uuid::Uuid::new_v4().to_string(),
|
||||||
user_id: user.id.to_proto(),
|
user_id: user.id.to_proto(),
|
||||||
system_id,
|
system_id,
|
||||||
metrics_id: user.metrics_id,
|
metrics_id: Some(user.metrics_id),
|
||||||
github_user_login: user.github_login.clone(),
|
github_user_login: user.github_login.clone(),
|
||||||
is_staff,
|
is_staff,
|
||||||
has_llm_closed_beta_feature_flag,
|
has_llm_closed_beta_feature_flag,
|
||||||
|
|||||||
@@ -310,9 +310,6 @@ impl Server {
|
|||||||
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
|
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
|
||||||
.add_request_handler(forward_read_only_project_request::<proto::GitBranches>)
|
.add_request_handler(forward_read_only_project_request::<proto::GitBranches>)
|
||||||
.add_request_handler(forward_read_only_project_request::<proto::GetStagedText>)
|
.add_request_handler(forward_read_only_project_request::<proto::GetStagedText>)
|
||||||
.add_request_handler(
|
|
||||||
forward_mutating_project_request::<proto::RegisterBufferWithLanguageServers>,
|
|
||||||
)
|
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::UpdateGitBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::UpdateGitBranch>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GetCompletions>)
|
.add_request_handler(forward_mutating_project_request::<proto::GetCompletions>)
|
||||||
.add_request_handler(
|
.add_request_handler(
|
||||||
|
|||||||
@@ -994,12 +994,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
|
let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
|
||||||
|
|
||||||
let _buffer_a = project_a
|
let _buffer_a = project_a
|
||||||
.update(cx_a, |p, cx| {
|
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||||
p.open_local_buffer_with_lsp("/dir/main.rs", cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1589,6 +1587,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
let editor_a = workspace_a
|
let editor_a = workspace_a
|
||||||
.update(cx_a, |workspace, cx| {
|
.update(cx_a, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||||
@@ -1598,8 +1597,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
|||||||
.downcast::<Editor>()
|
.downcast::<Editor>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
|
||||||
|
|
||||||
// Set up the language server to return an additional inlay hint on each request.
|
// Set up the language server to return an additional inlay hint on each request.
|
||||||
let edits_made = Arc::new(AtomicUsize::new(0));
|
let edits_made = Arc::new(AtomicUsize::new(0));
|
||||||
let closure_edits_made = Arc::clone(&edits_made);
|
let closure_edits_made = Arc::clone(&edits_made);
|
||||||
|
|||||||
@@ -3891,7 +3891,13 @@ async fn test_collaborating_with_diagnostics(
|
|||||||
// Cause the language server to start.
|
// Cause the language server to start.
|
||||||
let _buffer = project_a
|
let _buffer = project_a
|
||||||
.update(cx_a, |project, cx| {
|
.update(cx_a, |project, cx| {
|
||||||
project.open_local_buffer_with_lsp("/a/other.rs", cx)
|
project.open_buffer(
|
||||||
|
ProjectPath {
|
||||||
|
worktree_id,
|
||||||
|
path: Path::new("other.rs").into(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -4170,9 +4176,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
|||||||
// Join the project as client B and open all three files.
|
// Join the project as client B and open all three files.
|
||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
|
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
|
||||||
project_b.update(cx_b, |p, cx| {
|
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
|
||||||
p.open_buffer_with_lsp((worktree_id, file_name), cx)
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -4226,7 +4230,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
|||||||
cx.subscribe(&project_b, move |_, _, event, cx| {
|
cx.subscribe(&project_b, move |_, _, event, cx| {
|
||||||
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
|
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
|
||||||
disk_based_diagnostics_finished.store(true, SeqCst);
|
disk_based_diagnostics_finished.store(true, SeqCst);
|
||||||
for (buffer, _) in &guest_buffers {
|
for buffer in &guest_buffers {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer
|
buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -4347,6 +4351,7 @@ async fn test_formatting_buffer(
|
|||||||
cx_a: &mut TestAppContext,
|
cx_a: &mut TestAppContext,
|
||||||
cx_b: &mut TestAppContext,
|
cx_b: &mut TestAppContext,
|
||||||
) {
|
) {
|
||||||
|
executor.allow_parking();
|
||||||
let mut server = TestServer::start(executor.clone()).await;
|
let mut server = TestServer::start(executor.clone()).await;
|
||||||
let client_a = server.create_client(cx_a, "user_a").await;
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
let client_b = server.create_client(cx_b, "user_b").await;
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
@@ -4374,16 +4379,10 @@ async fn test_formatting_buffer(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
let lsp_store_b = project_b.update(cx_b, |p, _| p.lsp_store());
|
|
||||||
|
|
||||||
let buffer_b = project_b
|
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
|
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let _handle = lsp_store_b.update(cx_b, |lsp_store, cx| {
|
|
||||||
lsp_store.register_buffer_with_language_servers(&buffer_b, cx)
|
|
||||||
});
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
|
||||||
Ok(Some(vec![
|
Ok(Some(vec![
|
||||||
@@ -4432,8 +4431,6 @@ async fn test_formatting_buffer(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
executor.allow_parking();
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project.format(
|
project.format(
|
||||||
@@ -4506,12 +4503,8 @@ async fn test_prettier_formatting_buffer(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
let (buffer_b, _) = project_b
|
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||||
p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx_a.update(|cx| {
|
cx_a.update(|cx| {
|
||||||
SettingsStore::update_global(cx, |store, cx| {
|
SettingsStore::update_global(cx, |store, cx| {
|
||||||
@@ -4627,12 +4620,8 @@ async fn test_definition(
|
|||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
// Open the file on client B.
|
// Open the file on client B.
|
||||||
let (buffer_b, _handle) = project_b
|
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||||
p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Request the definition of a symbol as the guest.
|
// Request the definition of a symbol as the guest.
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
@@ -4776,12 +4765,8 @@ async fn test_references(
|
|||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
// Open the file on client B.
|
// Open the file on client B.
|
||||||
let (buffer_b, _handle) = project_b
|
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||||
p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Request references to a symbol as the guest.
|
// Request references to a symbol as the guest.
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
@@ -5027,12 +5012,8 @@ async fn test_document_highlights(
|
|||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
// Open the file on client B.
|
// Open the file on client B.
|
||||||
let (buffer_b, _handle) = project_b
|
let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
let buffer_b = cx_b.executor().spawn(open_b).await.unwrap();
|
||||||
p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Request document highlights as the guest.
|
// Request document highlights as the guest.
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
@@ -5149,12 +5130,8 @@ async fn test_lsp_hover(
|
|||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
// Open the file as the guest
|
// Open the file as the guest
|
||||||
let (buffer_b, _handle) = project_b
|
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
|
||||||
p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut servers_with_hover_requests = HashMap::default();
|
let mut servers_with_hover_requests = HashMap::default();
|
||||||
for i in 0..language_server_names.len() {
|
for i in 0..language_server_names.len() {
|
||||||
@@ -5329,12 +5306,9 @@ async fn test_project_symbols(
|
|||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
// Cause the language server to start.
|
// Cause the language server to start.
|
||||||
let _buffer = project_b
|
let open_buffer_task =
|
||||||
.update(cx_b, |p, cx| {
|
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
|
||||||
p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
|
let _buffer = cx_b.executor().spawn(open_buffer_task).await.unwrap();
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
|
||||||
@@ -5426,12 +5400,8 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||||
|
|
||||||
let (buffer_b1, _lsp) = project_b
|
let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap();
|
||||||
p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||||
@@ -5447,22 +5417,13 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||||||
let buffer_b2;
|
let buffer_b2;
|
||||||
if rng.gen() {
|
if rng.gen() {
|
||||||
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
||||||
(buffer_b2, _) = project_b
|
buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
|
||||||
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
} else {
|
} else {
|
||||||
(buffer_b2, _) = project_b
|
buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
|
||||||
.update(cx_b, |p, cx| {
|
|
||||||
p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let buffer_b2 = buffer_b2.await.unwrap();
|
||||||
let definitions = definitions.await.unwrap();
|
let definitions = definitions.await.unwrap();
|
||||||
assert_eq!(definitions.len(), 1);
|
assert_eq!(definitions.len(), 1);
|
||||||
assert_eq!(definitions[0].target.buffer, buffer_b2);
|
assert_eq!(definitions[0].target.buffer, buffer_b2);
|
||||||
|
|||||||
@@ -426,10 +426,8 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
|||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
// Opens the buffer and formats it
|
// Opens the buffer and formats it
|
||||||
let (buffer_b, _handle) = project_b
|
let buffer_b = project_b
|
||||||
.update(cx_b, |p, cx| {
|
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx))
|
||||||
p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.expect("user B opens buffer for formatting");
|
.expect("user B opens buffer for formatting");
|
||||||
|
|
||||||
|
|||||||
@@ -518,6 +518,7 @@ impl TestServer {
|
|||||||
stripe_billing: None,
|
stripe_billing: None,
|
||||||
rate_limiter: Arc::new(RateLimiter::new(test_db.db().clone())),
|
rate_limiter: Arc::new(RateLimiter::new(test_db.db().clone())),
|
||||||
executor,
|
executor,
|
||||||
|
clickhouse_client: None,
|
||||||
kinesis_client: None,
|
kinesis_client: None,
|
||||||
config: Config {
|
config: Config {
|
||||||
http_port: 0,
|
http_port: 0,
|
||||||
@@ -545,9 +546,10 @@ impl TestServer {
|
|||||||
anthropic_api_key: None,
|
anthropic_api_key: None,
|
||||||
anthropic_staff_api_key: None,
|
anthropic_staff_api_key: None,
|
||||||
llm_closed_beta_model_name: None,
|
llm_closed_beta_model_name: None,
|
||||||
prediction_api_url: None,
|
clickhouse_url: None,
|
||||||
prediction_api_key: None,
|
clickhouse_user: None,
|
||||||
prediction_model: None,
|
clickhouse_password: None,
|
||||||
|
clickhouse_database: None,
|
||||||
zed_client_checksum_seed: None,
|
zed_client_checksum_seed: None,
|
||||||
slack_panics_webhook: None,
|
slack_panics_webhook: None,
|
||||||
auto_join_channel_id: None,
|
auto_join_channel_id: None,
|
||||||
|
|||||||
@@ -841,7 +841,7 @@ impl CollabPanel {
|
|||||||
ListItem::new(SharedString::from(user.github_login.clone()))
|
ListItem::new(SharedString::from(user.github_login.clone()))
|
||||||
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
||||||
.child(Label::new(user.github_login.clone()))
|
.child(Label::new(user.github_login.clone()))
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.end_slot(if is_pending {
|
.end_slot(if is_pending {
|
||||||
Label::new("Calling").color(Color::Muted).into_any_element()
|
Label::new("Calling").color(Color::Muted).into_any_element()
|
||||||
} else if is_current_user {
|
} else if is_current_user {
|
||||||
@@ -894,7 +894,7 @@ impl CollabPanel {
|
|||||||
.into();
|
.into();
|
||||||
|
|
||||||
ListItem::new(project_id as usize)
|
ListItem::new(project_id as usize)
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.workspace
|
this.workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
@@ -924,7 +924,7 @@ impl CollabPanel {
|
|||||||
let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
|
let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
|
||||||
|
|
||||||
ListItem::new(("screen", id))
|
ListItem::new(("screen", id))
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.start_slot(
|
.start_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
@@ -964,7 +964,7 @@ impl CollabPanel {
|
|||||||
let channel_store = self.channel_store.read(cx);
|
let channel_store = self.channel_store.read(cx);
|
||||||
let has_channel_buffer_changed = channel_store.has_channel_buffer_changed(channel_id);
|
let has_channel_buffer_changed = channel_store.has_channel_buffer_changed(channel_id);
|
||||||
ListItem::new("channel-notes")
|
ListItem::new("channel-notes")
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.open_channel_notes(channel_id, cx);
|
this.open_channel_notes(channel_id, cx);
|
||||||
}))
|
}))
|
||||||
@@ -996,7 +996,7 @@ impl CollabPanel {
|
|||||||
let channel_store = self.channel_store.read(cx);
|
let channel_store = self.channel_store.read(cx);
|
||||||
let has_messages_notification = channel_store.has_new_messages(channel_id);
|
let has_messages_notification = channel_store.has_new_messages(channel_id);
|
||||||
ListItem::new("channel-chat")
|
ListItem::new("channel-chat")
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
this.join_channel_chat(channel_id, cx);
|
this.join_channel_chat(channel_id, cx);
|
||||||
}))
|
}))
|
||||||
@@ -2253,7 +2253,7 @@ impl CollabPanel {
|
|||||||
})
|
})
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.end_slot::<AnyElement>(button)
|
.end_slot::<AnyElement>(button)
|
||||||
.toggle_state(is_selected),
|
.selected(is_selected),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2270,7 +2270,7 @@ impl CollabPanel {
|
|||||||
let item = ListItem::new(github_login.clone())
|
let item = ListItem::new(github_login.clone())
|
||||||
.indent_level(1)
|
.indent_level(1)
|
||||||
.indent_step_size(px(20.))
|
.indent_step_size(px(20.))
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -2381,7 +2381,7 @@ impl CollabPanel {
|
|||||||
ListItem::new(github_login.clone())
|
ListItem::new(github_login.clone())
|
||||||
.indent_level(1)
|
.indent_level(1)
|
||||||
.indent_step_size(px(20.))
|
.indent_step_size(px(20.))
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -2425,7 +2425,7 @@ impl CollabPanel {
|
|||||||
];
|
];
|
||||||
|
|
||||||
ListItem::new(("channel-invite", channel.id.0 as usize))
|
ListItem::new(("channel-invite", channel.id.0 as usize))
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -2448,7 +2448,7 @@ impl CollabPanel {
|
|||||||
ListItem::new("contact-placeholder")
|
ListItem::new("contact-placeholder")
|
||||||
.child(Icon::new(IconName::Plus))
|
.child(Icon::new(IconName::Plus))
|
||||||
.child(Label::new("Add a Contact"))
|
.child(Label::new("Add a Contact"))
|
||||||
.toggle_state(is_selected)
|
.selected(is_selected)
|
||||||
.on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
|
.on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2547,7 +2547,7 @@ impl CollabPanel {
|
|||||||
// Add one level of depth for the disclosure arrow.
|
// Add one level of depth for the disclosure arrow.
|
||||||
.indent_level(depth + 1)
|
.indent_level(depth + 1)
|
||||||
.indent_step_size(px(20.))
|
.indent_step_size(px(20.))
|
||||||
.toggle_state(is_selected || is_active)
|
.selected(is_selected || is_active)
|
||||||
.toggle(disclosed)
|
.toggle(disclosed)
|
||||||
.on_toggle(
|
.on_toggle(
|
||||||
cx.listener(move |this, _, cx| {
|
cx.listener(move |this, _, cx| {
|
||||||
|
|||||||
@@ -89,15 +89,15 @@ impl ChannelModal {
|
|||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_channel_visibility(&mut self, selection: &ToggleState, cx: &mut ViewContext<Self>) {
|
fn set_channel_visibility(&mut self, selection: &Selection, cx: &mut ViewContext<Self>) {
|
||||||
self.channel_store.update(cx, |channel_store, cx| {
|
self.channel_store.update(cx, |channel_store, cx| {
|
||||||
channel_store
|
channel_store
|
||||||
.set_channel_visibility(
|
.set_channel_visibility(
|
||||||
self.channel_id,
|
self.channel_id,
|
||||||
match selection {
|
match selection {
|
||||||
ToggleState::Unselected => ChannelVisibility::Members,
|
Selection::Unselected => ChannelVisibility::Members,
|
||||||
ToggleState::Selected => ChannelVisibility::Public,
|
Selection::Selected => ChannelVisibility::Public,
|
||||||
ToggleState::Indeterminate => return,
|
Selection::Indeterminate => return,
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@@ -159,9 +159,9 @@ impl Render for ChannelModal {
|
|||||||
"is-public",
|
"is-public",
|
||||||
Label::new("Public").size(LabelSize::Small),
|
Label::new("Public").size(LabelSize::Small),
|
||||||
if visibility == ChannelVisibility::Public {
|
if visibility == ChannelVisibility::Public {
|
||||||
ui::ToggleState::Selected
|
ui::Selection::Selected
|
||||||
} else {
|
} else {
|
||||||
ui::ToggleState::Unselected
|
ui::Selection::Unselected
|
||||||
},
|
},
|
||||||
cx.listener(Self::set_channel_visibility),
|
cx.listener(Self::set_channel_visibility),
|
||||||
))
|
))
|
||||||
@@ -386,7 +386,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
||||||
.child(Label::new(user.github_login.clone()))
|
.child(Label::new(user.github_login.clone()))
|
||||||
.end_slot(h_flex().gap_2().map(|slot| {
|
.end_slot(h_flex().gap_2().map(|slot| {
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
.start_slot(Avatar::new(user.avatar_uri.clone()))
|
||||||
.child(Label::new(user.github_login.clone()))
|
.child(Label::new(user.github_login.clone()))
|
||||||
.end_slot::<Icon>(icon_path.map(Icon::from_path)),
|
.end_slot::<Icon>(icon_path.map(Icon::from_path)),
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.toggle_state(selected)
|
.selected(selected)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|||||||
@@ -59,21 +59,18 @@ workspace.workspace = true
|
|||||||
async-std = { version = "1.12.0", features = ["unstable"] }
|
async-std = { version = "1.12.0", features = ["unstable"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
clock.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
clock = { workspace = true, features = ["test-support"] }
|
|
||||||
client = { workspace = true, features = ["test-support"] }
|
|
||||||
collections = { workspace = true, features = ["test-support"] }
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
fs = { workspace = true, features = ["test-support"] }
|
fs = { workspace = true, features = ["test-support"] }
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
http_client = { workspace = true, features = ["test-support"] }
|
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
lsp = { workspace = true, features = ["test-support"] }
|
lsp = { workspace = true, features = ["test-support"] }
|
||||||
node_runtime = { workspace = true, features = ["test-support"] }
|
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
rpc = { workspace = true, features = ["test-support"] }
|
rpc = { workspace = true, features = ["test-support"] }
|
||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
theme = { workspace = true, features = ["test-support"] }
|
theme = { workspace = true, features = ["test-support"] }
|
||||||
util = { workspace = true, features = ["test-support"] }
|
util = { workspace = true, features = ["test-support"] }
|
||||||
workspace = { workspace = true, features = ["test-support"] }
|
http_client = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ use anyhow::{anyhow, Result};
|
|||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
|
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
|
||||||
use gpui::{prelude::*, AppContext, AsyncAppContext, Global};
|
use gpui::{AppContext, AsyncAppContext, Global};
|
||||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||||
use paths::home_dir;
|
use paths::home_dir;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::watch_config_file;
|
use settings::watch_config_file;
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
use ui::Context;
|
||||||
|
|
||||||
pub const COPILOT_CHAT_COMPLETION_URL: &str = "https://api.githubcopilot.com/chat/completions";
|
pub const COPILOT_CHAT_COMPLETION_URL: &str = "https://api.githubcopilot.com/chat/completions";
|
||||||
pub const COPILOT_CHAT_AUTH_URL: &str = "https://api.github.com/copilot_internal/v2/token";
|
pub const COPILOT_CHAT_AUTH_URL: &str = "https://api.github.com/copilot_internal/v2/token";
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use crate::{Completion, Copilot};
|
use crate::{Completion, Copilot};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use client::telemetry::Telemetry;
|
||||||
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
|
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
|
||||||
use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
|
use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{all_language_settings, AllLanguageSettings},
|
language_settings::{all_language_settings, AllLanguageSettings},
|
||||||
Buffer, OffsetRangeExt, ToOffset,
|
Buffer, OffsetRangeExt, ToOffset,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{path::Path, time::Duration};
|
use std::{path::Path, sync::Arc, time::Duration};
|
||||||
|
|
||||||
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ pub struct CopilotCompletionProvider {
|
|||||||
pending_refresh: Task<Result<()>>,
|
pending_refresh: Task<Result<()>>,
|
||||||
pending_cycling_refresh: Task<Result<()>>,
|
pending_cycling_refresh: Task<Result<()>>,
|
||||||
copilot: Model<Copilot>,
|
copilot: Model<Copilot>,
|
||||||
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CopilotCompletionProvider {
|
impl CopilotCompletionProvider {
|
||||||
@@ -33,9 +35,15 @@ impl CopilotCompletionProvider {
|
|||||||
pending_refresh: Task::ready(Ok(())),
|
pending_refresh: Task::ready(Ok(())),
|
||||||
pending_cycling_refresh: Task::ready(Ok(())),
|
pending_cycling_refresh: Task::ready(Ok(())),
|
||||||
copilot,
|
copilot,
|
||||||
|
telemetry: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_telemetry(mut self, telemetry: Arc<Telemetry>) -> Self {
|
||||||
|
self.telemetry = Some(telemetry);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn active_completion(&self) -> Option<&Completion> {
|
fn active_completion(&self) -> Option<&Completion> {
|
||||||
self.completions.get(self.active_completion_index)
|
self.completions.get(self.active_completion_index)
|
||||||
}
|
}
|
||||||
@@ -182,10 +190,23 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
|||||||
self.copilot
|
self.copilot
|
||||||
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
|
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
if self.active_completion().is_some() {
|
||||||
|
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||||
|
telemetry.report_inline_completion_event(
|
||||||
|
Self::name().to_string(),
|
||||||
|
true,
|
||||||
|
self.file_extension.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discard(&mut self, cx: &mut ModelContext<Self>) {
|
fn discard(
|
||||||
|
&mut self,
|
||||||
|
should_report_inline_completion_event: bool,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
let settings = AllLanguageSettings::get_global(cx);
|
let settings = AllLanguageSettings::get_global(cx);
|
||||||
|
|
||||||
let copilot_enabled = settings.inline_completions_enabled(None, None, cx);
|
let copilot_enabled = settings.inline_completions_enabled(None, None, cx);
|
||||||
@@ -199,14 +220,24 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
|||||||
copilot.discard_completions(&self.completions, cx)
|
copilot.discard_completions(&self.completions, cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
|
if should_report_inline_completion_event && self.active_completion().is_some() {
|
||||||
|
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||||
|
telemetry.report_inline_completion_event(
|
||||||
|
Self::name().to_string(),
|
||||||
|
false,
|
||||||
|
self.file_extension.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suggest(
|
fn active_completion_text<'a>(
|
||||||
&mut self,
|
&'a self,
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cursor_position: language::Anchor,
|
cursor_position: language::Anchor,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &'a AppContext,
|
||||||
) -> Option<InlineCompletion> {
|
) -> Option<CompletionProposal> {
|
||||||
let buffer_id = buffer.entity_id();
|
let buffer_id = buffer.entity_id();
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let completion = self.active_completion()?;
|
let completion = self.active_completion()?;
|
||||||
@@ -236,9 +267,13 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
|||||||
if completion_text.trim().is_empty() {
|
if completion_text.trim().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let position = cursor_position.bias_right(buffer);
|
Some(CompletionProposal {
|
||||||
Some(InlineCompletion {
|
inlays: vec![InlayProposal::Suggestion(
|
||||||
edits: vec![(position..position, completion_text.into())],
|
cursor_position.bias_right(buffer),
|
||||||
|
completion_text.into(),
|
||||||
|
)],
|
||||||
|
text: completion_text.into(),
|
||||||
|
delete_range: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -296,6 +331,7 @@ mod tests {
|
|||||||
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
editor.set_inline_completion_provider(Some(copilot_provider), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When inserting, ensure autocompletion is favored over Copilot suggestions.
|
||||||
cx.set_state(indoc! {"
|
cx.set_state(indoc! {"
|
||||||
oneˇ
|
oneˇ
|
||||||
two
|
two
|
||||||
@@ -322,9 +358,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
// We want to show both: the inline completion and the completion menu
|
|
||||||
assert!(editor.context_menu_visible());
|
assert!(editor.context_menu_visible());
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
|
|
||||||
// Confirming a completion inserts it and hides the context menu, without showing
|
// Confirming a completion inserts it and hides the context menu, without showing
|
||||||
// the copilot suggestion afterwards.
|
// the copilot suggestion afterwards.
|
||||||
@@ -333,12 +368,45 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.detach();
|
.detach();
|
||||||
assert!(!editor.context_menu_visible());
|
assert!(!editor.context_menu_visible());
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
|
||||||
assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset editor and test that accepting completions works
|
// Ensure Copilot suggestions are shown right away if no autocompletion is available.
|
||||||
|
cx.set_state(indoc! {"
|
||||||
|
oneˇ
|
||||||
|
two
|
||||||
|
three
|
||||||
|
"});
|
||||||
|
cx.simulate_keystroke(".");
|
||||||
|
drop(handle_completion_request(
|
||||||
|
&mut cx,
|
||||||
|
indoc! {"
|
||||||
|
one.|<>
|
||||||
|
two
|
||||||
|
three
|
||||||
|
"},
|
||||||
|
vec![],
|
||||||
|
));
|
||||||
|
handle_copilot_completion_request(
|
||||||
|
&copilot_lsp,
|
||||||
|
vec![crate::request::Completion {
|
||||||
|
text: "one.copilot1".into(),
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
assert!(!editor.context_menu_visible());
|
||||||
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
|
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||||
|
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
|
||||||
cx.set_state(indoc! {"
|
cx.set_state(indoc! {"
|
||||||
oneˇ
|
oneˇ
|
||||||
two
|
two
|
||||||
@@ -366,17 +434,22 @@ mod tests {
|
|||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.context_menu_visible());
|
assert!(editor.context_menu_visible());
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
|
|
||||||
|
// When hiding the context menu, the Copilot suggestion becomes visible.
|
||||||
|
editor.cancel(&Default::default(), cx);
|
||||||
|
assert!(!editor.context_menu_visible());
|
||||||
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure existing inline completion is interpolated when inserting again.
|
// Ensure existing completion is interpolated when inserting again.
|
||||||
cx.simulate_keystroke("c");
|
cx.simulate_keystroke("c");
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(!editor.context_menu_visible());
|
assert!(!editor.context_menu_visible());
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -394,25 +467,25 @@ mod tests {
|
|||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(!editor.context_menu_visible());
|
assert!(!editor.context_menu_visible());
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||||
|
|
||||||
// Canceling should remove the active Copilot suggestion.
|
// Canceling should remove the active Copilot suggestion.
|
||||||
editor.cancel(&Default::default(), cx);
|
editor.cancel(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||||
|
|
||||||
// After canceling, tabbing shouldn't insert the previously shown suggestion.
|
// After canceling, tabbing shouldn't insert the previously shown suggestion.
|
||||||
editor.tab(&Default::default(), cx);
|
editor.tab(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
|
||||||
|
|
||||||
// When undoing the previously active suggestion is shown again.
|
// When undoing the previously active suggestion is shown again.
|
||||||
editor.undo(&Default::default(), cx);
|
editor.undo(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -420,25 +493,25 @@ mod tests {
|
|||||||
// If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
|
// If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
|
||||||
cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
|
cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||||
|
|
||||||
// AcceptInlineCompletion when there is an active suggestion inserts it.
|
// AcceptInlineCompletion when there is an active suggestion inserts it.
|
||||||
editor.accept_inline_completion(&Default::default(), cx);
|
editor.accept_inline_completion(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
|
||||||
|
|
||||||
// When undoing the previously active suggestion is shown again.
|
// When undoing the previously active suggestion is shown again.
|
||||||
editor.undo(&Default::default(), cx);
|
editor.undo(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||||
|
|
||||||
// Hide suggestion.
|
// Hide suggestion.
|
||||||
editor.cancel(&Default::default(), cx);
|
editor.cancel(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -447,7 +520,7 @@ mod tests {
|
|||||||
// we won't make it visible.
|
// we won't make it visible.
|
||||||
cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
|
cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -472,19 +545,19 @@ mod tests {
|
|||||||
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
|
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||||
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
||||||
|
|
||||||
// Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
|
// Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
|
||||||
editor.tab(&Default::default(), cx);
|
editor.tab(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
|
||||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||||
|
|
||||||
// Using AcceptInlineCompletion again accepts the suggestion.
|
// Using AcceptInlineCompletion again accepts the suggestion.
|
||||||
editor.accept_inline_completion(&Default::default(), cx);
|
editor.accept_inline_completion(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
|
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
|
||||||
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
|
||||||
});
|
});
|
||||||
@@ -542,17 +615,17 @@ mod tests {
|
|||||||
);
|
);
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
|
|
||||||
// Accepting the first word of the suggestion should only accept the first word and still show the rest.
|
// Accepting the first word of the suggestion should only accept the first word and still show the rest.
|
||||||
editor.accept_partial_inline_completion(&Default::default(), cx);
|
editor.accept_partial_inline_completion(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n");
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||||
|
|
||||||
// Accepting next word should accept the non-word and copilot suggestion should be gone
|
// Accepting next word should accept the non-word and copilot suggestion should be gone
|
||||||
editor.accept_partial_inline_completion(&Default::default(), cx);
|
editor.accept_partial_inline_completion(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
|
||||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -584,11 +657,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
|
|
||||||
// Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
|
// Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
|
||||||
editor.accept_partial_inline_completion(&Default::default(), cx);
|
editor.accept_partial_inline_completion(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
@@ -597,7 +670,7 @@ mod tests {
|
|||||||
|
|
||||||
// Accepting next word should accept the next word and copilot suggestion should still exist
|
// Accepting next word should accept the next word and copilot suggestion should still exist
|
||||||
editor.accept_partial_inline_completion(&Default::default(), cx);
|
editor.accept_partial_inline_completion(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
@@ -606,7 +679,7 @@ mod tests {
|
|||||||
|
|
||||||
// Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
|
// Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
|
||||||
editor.accept_partial_inline_completion(&Default::default(), cx);
|
editor.accept_partial_inline_completion(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
@@ -657,29 +730,29 @@ mod tests {
|
|||||||
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
|
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
|
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
|
||||||
|
|
||||||
editor.backspace(&Default::default(), cx);
|
editor.backspace(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\nt\nthree\n");
|
assert_eq!(editor.text(cx), "one\nt\nthree\n");
|
||||||
|
|
||||||
editor.backspace(&Default::default(), cx);
|
editor.backspace(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\n\nthree\n");
|
assert_eq!(editor.text(cx), "one\n\nthree\n");
|
||||||
|
|
||||||
// Deleting across the original suggestion range invalidates it.
|
// Deleting across the original suggestion range invalidates it.
|
||||||
editor.backspace(&Default::default(), cx);
|
editor.backspace(&Default::default(), cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\nthree\n");
|
assert_eq!(editor.text(cx), "one\nthree\n");
|
||||||
|
|
||||||
// Undoing the deletion restores the suggestion.
|
// Undoing the deletion restores the suggestion.
|
||||||
editor.undo(&Default::default(), cx);
|
editor.undo(&Default::default(), cx);
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\n\nthree\n");
|
assert_eq!(editor.text(cx), "one\n\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -740,7 +813,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
_ = editor.update(cx, |editor, cx| {
|
_ = editor.update(cx, |editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
"\n\n\na = 1\nb = 2 + a\n\n\n\n\n\nc = 3\nd = 4\n\n"
|
"\n\n\na = 1\nb = 2 + a\n\n\n\n\n\nc = 3\nd = 4\n\n"
|
||||||
@@ -762,7 +835,7 @@ mod tests {
|
|||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
||||||
});
|
});
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
"\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4\n\n"
|
"\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4\n\n"
|
||||||
@@ -771,7 +844,7 @@ mod tests {
|
|||||||
|
|
||||||
// Type a character, ensuring we don't even try to interpolate the previous suggestion.
|
// Type a character, ensuring we don't even try to interpolate the previous suggestion.
|
||||||
editor.handle_input(" ", cx);
|
editor.handle_input(" ", cx);
|
||||||
assert!(!editor.has_active_inline_completion());
|
assert!(!editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
"\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4 \n\n"
|
"\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4 \n\n"
|
||||||
@@ -782,7 +855,7 @@ mod tests {
|
|||||||
// Ensure the new suggestion is displayed when the debounce timeout expires.
|
// Ensure the new suggestion is displayed when the debounce timeout expires.
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
_ = editor.update(cx, |editor, cx| {
|
_ = editor.update(cx, |editor, cx| {
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.display_text(cx),
|
editor.display_text(cx),
|
||||||
"\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4 + c\n\n"
|
"\n\n\na = 1\nb = 2\n\n\n\n\n\nc = 3\nd = 4 + c\n\n"
|
||||||
@@ -842,8 +915,8 @@ mod tests {
|
|||||||
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
|
cx.update_editor(|editor, cx| editor.next_inline_completion(&Default::default(), cx));
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(!editor.context_menu_visible());
|
assert!(!editor.context_menu_visible(), "Even there are some completions available, those are not triggered when active copilot suggestion is present");
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
|
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -870,7 +943,7 @@ mod tests {
|
|||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(!editor.context_menu_visible());
|
assert!(!editor.context_menu_visible());
|
||||||
assert!(editor.has_active_inline_completion());
|
assert!(editor.has_active_inline_completion(cx));
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\ntwo\nthree\n");
|
assert_eq!(editor.text(cx), "one\ntwo\nthree\n");
|
||||||
});
|
});
|
||||||
@@ -896,9 +969,15 @@ mod tests {
|
|||||||
);
|
);
|
||||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
assert!(editor.context_menu_visible());
|
assert!(
|
||||||
assert!(editor.has_active_inline_completion(),);
|
editor.context_menu_visible(),
|
||||||
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
|
"On completion trigger input, the completions should be fetched and visible"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!editor.has_active_inline_completion(cx),
|
||||||
|
"On completion trigger input, copilot suggestion should be dismissed"
|
||||||
|
);
|
||||||
|
assert_eq!(editor.display_text(cx), "one\ntwo.\nthree\n");
|
||||||
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
|
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -919,7 +998,7 @@ mod tests {
|
|||||||
"/test",
|
"/test",
|
||||||
json!({
|
json!({
|
||||||
".env": "SECRET=something\n",
|
".env": "SECRET=something\n",
|
||||||
"README.md": "hello\nworld\nhow\nare\nyou\ntoday"
|
"README.md": "hello\n"
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -951,7 +1030,7 @@ mod tests {
|
|||||||
multibuffer.push_excerpts(
|
multibuffer.push_excerpts(
|
||||||
public_buffer.clone(),
|
public_buffer.clone(),
|
||||||
[ExcerptRange {
|
[ExcerptRange {
|
||||||
context: Point::new(0, 0)..Point::new(6, 0),
|
context: Point::new(0, 0)..Point::new(1, 0),
|
||||||
primary: None,
|
primary: None,
|
||||||
}],
|
}],
|
||||||
cx,
|
cx,
|
||||||
@@ -959,7 +1038,6 @@ mod tests {
|
|||||||
multibuffer
|
multibuffer
|
||||||
});
|
});
|
||||||
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, cx));
|
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, true, cx));
|
||||||
editor.update(cx, |editor, cx| editor.focus(cx)).unwrap();
|
|
||||||
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new_model(|_| CopilotCompletionProvider::new(copilot));
|
||||||
editor
|
editor
|
||||||
.update(cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
@@ -995,7 +1073,7 @@ mod tests {
|
|||||||
|
|
||||||
_ = editor.update(cx, |editor, cx| {
|
_ = editor.update(cx, |editor, cx| {
|
||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.select_ranges([Point::new(5, 0)..Point::new(5, 0)])
|
s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
|
||||||
});
|
});
|
||||||
editor.refresh_inline_completion(true, false, cx);
|
editor.refresh_inline_completion(true, false, cx);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use editor::{
|
|||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
||||||
FocusableView, Global, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement,
|
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
|
||||||
Render, SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
|
SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
|
||||||
WeakView, WindowContext,
|
WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
@@ -46,9 +46,6 @@ use workspace::{
|
|||||||
|
|
||||||
actions!(diagnostics, [Deploy, ToggleWarnings]);
|
actions!(diagnostics, [Deploy, ToggleWarnings]);
|
||||||
|
|
||||||
struct IncludeWarnings(bool);
|
|
||||||
impl Global for IncludeWarnings {}
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
ProjectDiagnosticsSettings::register(cx);
|
ProjectDiagnosticsSettings::register(cx);
|
||||||
cx.observe_new_views(ProjectDiagnosticsEditor::register)
|
cx.observe_new_views(ProjectDiagnosticsEditor::register)
|
||||||
@@ -120,7 +117,6 @@ impl ProjectDiagnosticsEditor {
|
|||||||
|
|
||||||
fn new_with_context(
|
fn new_with_context(
|
||||||
context: u32,
|
context: u32,
|
||||||
include_warnings: bool,
|
|
||||||
project_handle: Model<Project>,
|
project_handle: Model<Project>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
@@ -138,16 +134,27 @@ impl ProjectDiagnosticsEditor {
|
|||||||
language_server_id,
|
language_server_id,
|
||||||
path,
|
path,
|
||||||
} => {
|
} => {
|
||||||
this.paths_to_update
|
let max_severity = this.max_severity();
|
||||||
.insert((path.clone(), Some(*language_server_id)));
|
let has_diagnostics_to_display = project.read(cx).lsp_store().read(cx).diagnostics_for_buffer(path)
|
||||||
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
.into_iter().flatten()
|
||||||
cx.emit(EditorEvent::TitleChanged);
|
.filter(|(server_id, _)| language_server_id == server_id)
|
||||||
|
.flat_map(|(_, diagnostics)| diagnostics)
|
||||||
|
.any(|diagnostic| diagnostic.diagnostic.severity <= max_severity);
|
||||||
|
|
||||||
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
|
if has_diagnostics_to_display {
|
||||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
this.paths_to_update
|
||||||
|
.insert((path.clone(), Some(*language_server_id)));
|
||||||
|
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
||||||
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
|
|
||||||
|
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
|
||||||
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
||||||
|
} else {
|
||||||
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
||||||
|
this.update_stale_excerpts(cx);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. no diagnostics to display");
|
||||||
this.update_stale_excerpts(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -179,24 +186,19 @@ impl ProjectDiagnosticsEditor {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
cx.observe_global::<IncludeWarnings>(|this, cx| {
|
|
||||||
this.include_warnings = cx.global::<IncludeWarnings>().0;
|
|
||||||
this.update_all_excerpts(cx);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
let project = project_handle.read(cx);
|
let project = project_handle.read(cx);
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
project: project_handle.clone(),
|
project: project_handle.clone(),
|
||||||
context,
|
context,
|
||||||
summary: project.diagnostic_summary(false, cx),
|
summary: project.diagnostic_summary(false, cx),
|
||||||
include_warnings,
|
|
||||||
workspace,
|
workspace,
|
||||||
excerpts,
|
excerpts,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
editor,
|
editor,
|
||||||
path_states: Default::default(),
|
path_states: Default::default(),
|
||||||
paths_to_update: Default::default(),
|
paths_to_update: Default::default(),
|
||||||
|
include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||||
update_excerpts_task: None,
|
update_excerpts_task: None,
|
||||||
_subscription: project_event_subscription,
|
_subscription: project_event_subscription,
|
||||||
};
|
};
|
||||||
@@ -241,13 +243,11 @@ impl ProjectDiagnosticsEditor {
|
|||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
project_handle: Model<Project>,
|
project_handle: Model<Project>,
|
||||||
include_warnings: bool,
|
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_with_context(
|
Self::new_with_context(
|
||||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||||
include_warnings,
|
|
||||||
project_handle,
|
project_handle,
|
||||||
workspace,
|
workspace,
|
||||||
cx,
|
cx,
|
||||||
@@ -259,19 +259,8 @@ impl ProjectDiagnosticsEditor {
|
|||||||
workspace.activate_item(&existing, true, true, cx);
|
workspace.activate_item(&existing, true, true, cx);
|
||||||
} else {
|
} else {
|
||||||
let workspace_handle = cx.view().downgrade();
|
let workspace_handle = cx.view().downgrade();
|
||||||
|
|
||||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
|
||||||
Some(include_warnings) => include_warnings.0,
|
|
||||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diagnostics = cx.new_view(|cx| {
|
let diagnostics = cx.new_view(|cx| {
|
||||||
ProjectDiagnosticsEditor::new(
|
ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
|
||||||
workspace.project().clone(),
|
|
||||||
include_warnings,
|
|
||||||
workspace_handle,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||||
}
|
}
|
||||||
@@ -279,7 +268,6 @@ impl ProjectDiagnosticsEditor {
|
|||||||
|
|
||||||
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
||||||
self.include_warnings = !self.include_warnings;
|
self.include_warnings = !self.include_warnings;
|
||||||
cx.set_global(IncludeWarnings(self.include_warnings));
|
|
||||||
self.update_all_excerpts(cx);
|
self.update_all_excerpts(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
@@ -352,16 +340,12 @@ impl ProjectDiagnosticsEditor {
|
|||||||
ExcerptId::min()
|
ExcerptId::min()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let max_severity = self.max_severity();
|
||||||
let path_state = &mut self.path_states[path_ix];
|
let path_state = &mut self.path_states[path_ix];
|
||||||
let mut new_group_ixs = Vec::new();
|
let mut new_group_ixs = Vec::new();
|
||||||
let mut blocks_to_add = Vec::new();
|
let mut blocks_to_add = Vec::new();
|
||||||
let mut blocks_to_remove = HashSet::default();
|
let mut blocks_to_remove = HashSet::default();
|
||||||
let mut first_excerpt_id = None;
|
let mut first_excerpt_id = None;
|
||||||
let max_severity = if self.include_warnings {
|
|
||||||
DiagnosticSeverity::WARNING
|
|
||||||
} else {
|
|
||||||
DiagnosticSeverity::ERROR
|
|
||||||
};
|
|
||||||
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, cx| {
|
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, cx| {
|
||||||
let mut old_groups = mem::take(&mut path_state.diagnostic_groups)
|
let mut old_groups = mem::take(&mut path_state.diagnostic_groups)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -650,6 +634,14 @@ impl ProjectDiagnosticsEditor {
|
|||||||
prev_path = Some(path);
|
prev_path = Some(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn max_severity(&self) -> DiagnosticSeverity {
|
||||||
|
if self.include_warnings {
|
||||||
|
DiagnosticSeverity::WARNING
|
||||||
|
} else {
|
||||||
|
DiagnosticSeverity::ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusableView for ProjectDiagnosticsEditor {
|
impl FocusableView for ProjectDiagnosticsEditor {
|
||||||
@@ -748,12 +740,7 @@ impl Item for ProjectDiagnosticsEditor {
|
|||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
Some(cx.new_view(|cx| {
|
Some(cx.new_view(|cx| {
|
||||||
ProjectDiagnosticsEditor::new(
|
ProjectDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
|
||||||
self.project.clone(),
|
|
||||||
self.include_warnings,
|
|
||||||
self.workspace.clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,13 +151,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
// Open the project diagnostics view while there are already diagnostics.
|
// Open the project diagnostics view while there are already diagnostics.
|
||||||
let view = window.build_view(cx, |cx| {
|
let view = window.build_view(cx, |cx| {
|
||||||
ProjectDiagnosticsEditor::new_with_context(
|
ProjectDiagnosticsEditor::new_with_context(1, project.clone(), workspace.downgrade(), cx)
|
||||||
1,
|
|
||||||
true,
|
|
||||||
project.clone(),
|
|
||||||
workspace.downgrade(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
let editor = view.update(cx, |view, _| view.editor.clone());
|
let editor = view.update(cx, |view, _| view.editor.clone());
|
||||||
|
|
||||||
@@ -465,13 +459,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||||||
let workspace = window.root(cx).unwrap();
|
let workspace = window.root(cx).unwrap();
|
||||||
|
|
||||||
let view = window.build_view(cx, |cx| {
|
let view = window.build_view(cx, |cx| {
|
||||||
ProjectDiagnosticsEditor::new_with_context(
|
ProjectDiagnosticsEditor::new_with_context(1, project.clone(), workspace.downgrade(), cx)
|
||||||
1,
|
|
||||||
true,
|
|
||||||
project.clone(),
|
|
||||||
workspace.downgrade(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
let editor = view.update(cx, |view, _| view.editor.clone());
|
let editor = view.update(cx, |view, _| view.editor.clone());
|
||||||
|
|
||||||
@@ -732,13 +720,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||||||
let workspace = window.root(cx).unwrap();
|
let workspace = window.root(cx).unwrap();
|
||||||
|
|
||||||
let mutated_view = window.build_view(cx, |cx| {
|
let mutated_view = window.build_view(cx, |cx| {
|
||||||
ProjectDiagnosticsEditor::new_with_context(
|
ProjectDiagnosticsEditor::new_with_context(1, project.clone(), workspace.downgrade(), cx)
|
||||||
1,
|
|
||||||
true,
|
|
||||||
project.clone(),
|
|
||||||
workspace.downgrade(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
@@ -809,7 +791,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||||||
|
|
||||||
updated_language_servers.insert(server_id);
|
updated_language_servers.insert(server_id);
|
||||||
|
|
||||||
lsp_store.update(cx, |lsp_store, cx| {
|
project.update(cx, |project, cx| {
|
||||||
log::info!("updating diagnostics. language server {server_id} path {path:?}");
|
log::info!("updating diagnostics. language server {server_id} path {path:?}");
|
||||||
randomly_update_diagnostics_for_path(
|
randomly_update_diagnostics_for_path(
|
||||||
&fs,
|
&fs,
|
||||||
@@ -818,12 +800,10 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||||||
&mut next_group_id,
|
&mut next_group_id,
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
lsp_store
|
project
|
||||||
.update_diagnostic_entries(server_id, path, None, diagnostics.clone(), cx)
|
.update_diagnostic_entries(server_id, path, None, diagnostics.clone(), cx)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
cx.executor()
|
|
||||||
.advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10));
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
}
|
}
|
||||||
@@ -836,33 +816,12 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
|||||||
|
|
||||||
log::info!("constructing reference diagnostics view");
|
log::info!("constructing reference diagnostics view");
|
||||||
let reference_view = window.build_view(cx, |cx| {
|
let reference_view = window.build_view(cx, |cx| {
|
||||||
ProjectDiagnosticsEditor::new_with_context(
|
ProjectDiagnosticsEditor::new_with_context(1, project.clone(), workspace.downgrade(), cx)
|
||||||
1,
|
|
||||||
true,
|
|
||||||
project.clone(),
|
|
||||||
workspace.downgrade(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
cx.executor()
|
|
||||||
.advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10));
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
let mutated_excerpts = get_diagnostics_excerpts(&mutated_view, cx);
|
let mutated_excerpts = get_diagnostics_excerpts(&mutated_view, cx);
|
||||||
let reference_excerpts = get_diagnostics_excerpts(&reference_view, cx);
|
let reference_excerpts = get_diagnostics_excerpts(&reference_view, cx);
|
||||||
|
|
||||||
for ((path, language_server_id), diagnostics) in current_diagnostics {
|
|
||||||
for diagnostic in diagnostics {
|
|
||||||
let found_excerpt = reference_excerpts.iter().any(|info| {
|
|
||||||
let row_range = info.range.context.start.row..info.range.context.end.row;
|
|
||||||
info.path == path.strip_prefix("/test").unwrap()
|
|
||||||
&& info.language_server == language_server_id
|
|
||||||
&& row_range.contains(&diagnostic.range.start.0.row)
|
|
||||||
});
|
|
||||||
assert!(found_excerpt, "diagnostic not found in reference view");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(mutated_excerpts, reference_excerpts);
|
assert_eq!(mutated_excerpts, reference_excerpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ unindent = { workspace = true, optional = true }
|
|||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ pub struct MoveDownByLines {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(super) lines: u32,
|
pub(super) lines: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct SelectUpByLines {
|
pub struct SelectUpByLines {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -167,13 +166,6 @@ pub struct SpawnNearestTask {
|
|||||||
pub reveal: task::RevealStrategy,
|
pub reveal: task::RevealStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Default)]
|
|
||||||
pub enum UuidVersion {
|
|
||||||
#[default]
|
|
||||||
V4,
|
|
||||||
V7,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
editor,
|
editor,
|
||||||
[
|
[
|
||||||
@@ -251,7 +243,6 @@ gpui::actions!(
|
|||||||
DisplayCursorNames,
|
DisplayCursorNames,
|
||||||
DuplicateLineDown,
|
DuplicateLineDown,
|
||||||
DuplicateLineUp,
|
DuplicateLineUp,
|
||||||
DuplicateSelection,
|
|
||||||
ExpandAllHunkDiffs,
|
ExpandAllHunkDiffs,
|
||||||
ExpandMacroRecursively,
|
ExpandMacroRecursively,
|
||||||
FindAllReferences,
|
FindAllReferences,
|
||||||
@@ -280,8 +271,6 @@ gpui::actions!(
|
|||||||
HalfPageUp,
|
HalfPageUp,
|
||||||
Hover,
|
Hover,
|
||||||
Indent,
|
Indent,
|
||||||
InsertUuidV4,
|
|
||||||
InsertUuidV7,
|
|
||||||
JoinLines,
|
JoinLines,
|
||||||
KillRingCut,
|
KillRingCut,
|
||||||
KillRingYank,
|
KillRingYank,
|
||||||
|
|||||||