Compare commits
1 Commits
cnext-cpre
...
revert-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee53b805f0 |
16
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
16
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
@@ -75,22 +75,6 @@ body:
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant Keymap
|
||||
description: |
|
||||
Open the command palette in Zed, then type “zed: open keymap file” and copy/paste the file's contents.
|
||||
value: |
|
||||
<details><summary>keymap.json</summary>
|
||||
|
||||
<!-- Paste your keymap file inside the code block. -->
|
||||
```json
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: (for AI issues) Model provider details
|
||||
|
||||
25
.github/workflows/after_release.yml
vendored
25
.github/workflows/after_release.yml
vendored
@@ -5,27 +5,13 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: tag_name
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: prerelease
|
||||
required: true
|
||||
type: boolean
|
||||
body:
|
||||
description: body
|
||||
type: string
|
||||
default: ''
|
||||
jobs:
|
||||
rebuild_releases_page:
|
||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
steps:
|
||||
- name: after_release::rebuild_releases_page::refresh_cloud_releases
|
||||
run: curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name || inputs.tag_name }}
|
||||
run: curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name }}
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: after_release::rebuild_releases_page::redeploy_zed_dev
|
||||
run: npm exec --yes -- vercel@37 --token="$VERCEL_TOKEN" --scope zed-industries redeploy https://zed.dev
|
||||
@@ -41,7 +27,7 @@ jobs:
|
||||
- id: get-release-url
|
||||
name: after_release::post_to_discord::get_release_url
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease || inputs.prerelease }}" == "true" ]; then
|
||||
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||
URL="https://zed.dev/releases/preview"
|
||||
else
|
||||
URL="https://zed.dev/releases/stable"
|
||||
@@ -54,9 +40,9 @@ jobs:
|
||||
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
|
||||
with:
|
||||
stringToTruncate: |
|
||||
📣 Zed [${{ github.event.release.tag_name || inputs.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
|
||||
📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
|
||||
|
||||
${{ github.event.release.body || inputs.body }}
|
||||
${{ github.event.release.body }}
|
||||
maxLength: 2000
|
||||
truncationSymbol: '...'
|
||||
- name: after_release::post_to_discord::discord_webhook_action
|
||||
@@ -70,7 +56,7 @@ jobs:
|
||||
- id: set-package-name
|
||||
name: after_release::publish_winget::set_package_name
|
||||
run: |
|
||||
if ("${{ github.event.release.prerelease || inputs.prerelease }}" -eq "true") {
|
||||
if ("${{ github.event.release.prerelease }}" -eq "true") {
|
||||
$PACKAGE_NAME = "ZedIndustries.Zed.Preview"
|
||||
} else {
|
||||
$PACKAGE_NAME = "ZedIndustries.Zed"
|
||||
@@ -82,7 +68,6 @@ jobs:
|
||||
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
|
||||
with:
|
||||
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
|
||||
release-tag: ${{ github.event.release.tag_name || inputs.tag_name }}
|
||||
max-versions-to-keep: 5
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
create_sentry_release:
|
||||
|
||||
83
.github/workflows/autofix_pr.yml
vendored
83
.github/workflows/autofix_pr.yml
vendored
@@ -1,83 +0,0 @@
|
||||
# Generated from xtask::workflows::autofix_pr
|
||||
# Rebuild with `cargo xtask workflows`.
|
||||
name: autofix_pr
|
||||
run-name: 'autofix PR #${{ inputs.pr_number }}'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: pr_number
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
run_autofix:
|
||||
runs-on: namespace-profile-16x32-ubuntu-2204
|
||||
steps:
|
||||
- id: get-app-token
|
||||
name: autofix_pr::run_autofix::authenticate_as_zippy
|
||||
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
|
||||
with:
|
||||
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
|
||||
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
|
||||
- name: steps::checkout_repo_with_token
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
clean: false
|
||||
token: ${{ steps.get-app-token.outputs.token }}
|
||||
- name: autofix_pr::run_autofix::checkout_pr
|
||||
run: gh pr checkout ${{ inputs.pr_number }}
|
||||
shell: bash -euxo pipefail {0}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
|
||||
- name: steps::setup_cargo_config
|
||||
run: |
|
||||
mkdir -p ./../.cargo
|
||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::cache_rust_dependencies_namespace
|
||||
uses: namespacelabs/nscloud-cache-action@v1
|
||||
with:
|
||||
cache: rust
|
||||
- name: steps::setup_linux
|
||||
run: ./script/linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::install_mold
|
||||
run: ./script/install-mold
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::download_wasi_sdk
|
||||
run: ./script/download-wasi-sdk
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: steps::setup_pnpm
|
||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||
with:
|
||||
version: '9'
|
||||
- name: autofix_pr::run_autofix::run_prettier_fix
|
||||
run: ./script/prettier --write
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: autofix_pr::run_autofix::run_cargo_fmt
|
||||
run: cargo fmt --all
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: autofix_pr::run_autofix::run_clippy_fix
|
||||
run: cargo clippy --workspace --release --all-targets --all-features --fix --allow-dirty --allow-staged
|
||||
shell: bash -euxo pipefail {0}
|
||||
- name: autofix_pr::run_autofix::commit_and_push
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git add -A
|
||||
git commit -m "Autofix"
|
||||
git push
|
||||
fi
|
||||
shell: bash -euxo pipefail {0}
|
||||
env:
|
||||
GIT_COMMITTER_NAME: Zed Zippy
|
||||
GIT_COMMITTER_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
|
||||
GIT_AUTHOR_NAME: Zed Zippy
|
||||
GIT_AUTHOR_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
|
||||
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
|
||||
- name: steps::cleanup_cargo_config
|
||||
if: always()
|
||||
run: |
|
||||
rm -rf ./../.cargo
|
||||
shell: bash -euxo pipefail {0}
|
||||
1
.github/workflows/extension_bump.yml
vendored
1
.github/workflows/extension_bump.yml
vendored
@@ -113,7 +113,6 @@ jobs:
|
||||
delete-branch: true
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
sign-commits: true
|
||||
assignees: ${{ github.actor }}
|
||||
timeout-minutes: 1
|
||||
create_version_label:
|
||||
needs:
|
||||
|
||||
2
.github/workflows/run_tests.yml
vendored
2
.github/workflows/run_tests.yml
vendored
@@ -497,8 +497,6 @@ jobs:
|
||||
env:
|
||||
GIT_AUTHOR_NAME: Protobuf Action
|
||||
GIT_AUTHOR_EMAIL: ci@zed.dev
|
||||
GIT_COMMITTER_NAME: Protobuf Action
|
||||
GIT_COMMITTER_EMAIL: ci@zed.dev
|
||||
steps:
|
||||
- name: steps::checkout_repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,7 +8,6 @@
|
||||
.DS_Store
|
||||
.blob_store
|
||||
.build
|
||||
.claude/settings.local.json
|
||||
.envrc
|
||||
.flatpak-builder
|
||||
.idea
|
||||
@@ -42,4 +41,4 @@ xcuserdata/
|
||||
.env.secret.toml
|
||||
|
||||
# `nix build` output
|
||||
/result
|
||||
/result
|
||||
|
||||
6
.rules
6
.rules
@@ -26,12 +26,6 @@
|
||||
});
|
||||
```
|
||||
|
||||
# Timers in tests
|
||||
|
||||
* In GPUI tests, prefer GPUI executor timers over `smol::Timer::after(...)` when you need timeouts, delays, or to drive `run_until_parked()`:
|
||||
- Use `cx.background_executor().timer(duration).await` (or `cx.background_executor.timer(duration).await` in `TestAppContext`) so the work is scheduled on GPUI's dispatcher.
|
||||
- Avoid `smol::Timer::after(...)` for test timeouts when you rely on `run_until_parked()`, because it may not be tracked by GPUI's scheduler and can lead to "nothing left to run" when pumping.
|
||||
|
||||
# GPUI
|
||||
|
||||
GPUI is a UI framework which also provides primitives for state and concurrency management.
|
||||
|
||||
117
Cargo.lock
generated
117
Cargo.lock
generated
@@ -37,7 +37,6 @@ dependencies = [
|
||||
"terminal",
|
||||
"ui",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid",
|
||||
"watch",
|
||||
@@ -389,6 +388,7 @@ dependencies = [
|
||||
"streaming_diff",
|
||||
"task",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
"text",
|
||||
@@ -407,37 +407,6 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent_ui_v2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"agent",
|
||||
"agent_servers",
|
||||
"agent_settings",
|
||||
"agent_ui",
|
||||
"anyhow",
|
||||
"assistant_text_thread",
|
||||
"chrono",
|
||||
"db",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"menu",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"text",
|
||||
"time",
|
||||
"time_format",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@@ -866,6 +835,7 @@ dependencies = [
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"globset",
|
||||
"gpui",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
@@ -924,7 +894,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"ui",
|
||||
"unindent",
|
||||
@@ -2800,9 +2770,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
version = "1.2.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -3141,11 +3111,21 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloud_zeta2_prompt"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cloud_llm_client",
|
||||
"indoc",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.56"
|
||||
version = "0.1.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586"
|
||||
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -3673,7 +3653,6 @@ dependencies = [
|
||||
"task",
|
||||
"theme",
|
||||
"ui",
|
||||
"url",
|
||||
"util",
|
||||
"workspace",
|
||||
"zlog",
|
||||
@@ -4810,7 +4789,6 @@ dependencies = [
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"multi_buffer",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"rand 0.9.2",
|
||||
@@ -5141,8 +5119,10 @@ dependencies = [
|
||||
"clock",
|
||||
"cloud_api_types",
|
||||
"cloud_llm_client",
|
||||
"cloud_zeta2_prompt",
|
||||
"collections",
|
||||
"copilot",
|
||||
"credentials_provider",
|
||||
"ctor",
|
||||
"db",
|
||||
"edit_prediction_context",
|
||||
@@ -5163,7 +5143,6 @@ dependencies = [
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"release_channel",
|
||||
@@ -5171,6 +5150,8 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"strsim",
|
||||
"strum 0.27.2",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
@@ -5181,7 +5162,6 @@ dependencies = [
|
||||
"workspace",
|
||||
"worktree",
|
||||
"zed_actions",
|
||||
"zeta_prompt",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
@@ -5195,10 +5175,11 @@ dependencies = [
|
||||
"clap",
|
||||
"client",
|
||||
"cloud_llm_client",
|
||||
"cloud_zeta2_prompt",
|
||||
"collections",
|
||||
"debug_adapter_extension",
|
||||
"dirs 4.0.0",
|
||||
"edit_prediction",
|
||||
"edit_prediction_context",
|
||||
"extension",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
@@ -5211,13 +5192,13 @@ dependencies = [
|
||||
"language_model",
|
||||
"language_models",
|
||||
"languages",
|
||||
"libc",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"serde",
|
||||
@@ -5228,10 +5209,10 @@ dependencies = [
|
||||
"sqlez",
|
||||
"sqlez_macros",
|
||||
"terminal_view",
|
||||
"toml 0.8.23",
|
||||
"util",
|
||||
"wasmtime",
|
||||
"watch",
|
||||
"zeta_prompt",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5258,7 +5239,6 @@ dependencies = [
|
||||
"text",
|
||||
"tree-sitter",
|
||||
"util",
|
||||
"zeta_prompt",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
@@ -5280,6 +5260,7 @@ dependencies = [
|
||||
"buffer_diff",
|
||||
"client",
|
||||
"cloud_llm_client",
|
||||
"cloud_zeta2_prompt",
|
||||
"codestral",
|
||||
"command_palette_hooks",
|
||||
"copilot",
|
||||
@@ -5289,11 +5270,9 @@ dependencies = [
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
"menu",
|
||||
@@ -5307,12 +5286,11 @@ dependencies = [
|
||||
"telemetry",
|
||||
"text",
|
||||
"theme",
|
||||
"time",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zeta_prompt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6126,9 +6104,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
@@ -7080,8 +7058,6 @@ dependencies = [
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_store",
|
||||
"rand 0.9.2",
|
||||
"recent_projects",
|
||||
"remote",
|
||||
"schemars",
|
||||
@@ -7274,7 +7250,6 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"lyon",
|
||||
"mach2 0.5.0",
|
||||
"media",
|
||||
"metal",
|
||||
"naga",
|
||||
@@ -8837,7 +8812,6 @@ dependencies = [
|
||||
"cloud_api_types",
|
||||
"cloud_llm_client",
|
||||
"collections",
|
||||
"credentials_provider",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"http_client",
|
||||
@@ -8853,9 +8827,9 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"telemetry_events",
|
||||
"thiserror 2.0.17",
|
||||
"util",
|
||||
"zed_env_vars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8912,6 +8886,7 @@ dependencies = [
|
||||
"util",
|
||||
"vercel",
|
||||
"x_ai",
|
||||
"zed_env_vars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10845,6 +10820,7 @@ dependencies = [
|
||||
"documented",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"git",
|
||||
"gpui",
|
||||
"menu",
|
||||
"notifications",
|
||||
@@ -14480,14 +14456,12 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"tracing",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"util_macros",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"ztracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14812,8 +14786,6 @@ dependencies = [
|
||||
"assets",
|
||||
"bm25",
|
||||
"client",
|
||||
"copilot",
|
||||
"edit_prediction",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
@@ -14822,7 +14794,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"heck 0.5.0",
|
||||
"language",
|
||||
"language_models",
|
||||
"log",
|
||||
"menu",
|
||||
"node_runtime",
|
||||
@@ -16403,13 +16374,13 @@ dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"collections",
|
||||
"fancy-regex",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"libc",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"release_channel",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -18137,11 +18108,9 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown_preview",
|
||||
"menu",
|
||||
"multi_buffer",
|
||||
"nvim-rs",
|
||||
"outline_panel",
|
||||
"parking_lot",
|
||||
"perf",
|
||||
"picker",
|
||||
@@ -20094,16 +20063,13 @@ dependencies = [
|
||||
"component",
|
||||
"dap",
|
||||
"db",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"log",
|
||||
"markdown",
|
||||
"menu",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
@@ -20513,7 +20479,6 @@ dependencies = [
|
||||
"activity_indicator",
|
||||
"agent_settings",
|
||||
"agent_ui",
|
||||
"agent_ui_v2",
|
||||
"anyhow",
|
||||
"ashpd 0.11.0",
|
||||
"askpass",
|
||||
@@ -20825,16 +20790,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_html"
|
||||
version = "0.3.0"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_proto"
|
||||
version = "0.3.0"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.7.0",
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -20968,13 +20933,6 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeta_prompt"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
@@ -21067,7 +21025,6 @@ dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-tracy",
|
||||
"zlog",
|
||||
"ztracing_macro",
|
||||
]
|
||||
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -9,7 +9,6 @@ members = [
|
||||
"crates/agent_servers",
|
||||
"crates/agent_settings",
|
||||
"crates/agent_ui",
|
||||
"crates/agent_ui_v2",
|
||||
"crates/ai_onboarding",
|
||||
"crates/anthropic",
|
||||
"crates/askpass",
|
||||
@@ -33,6 +32,7 @@ members = [
|
||||
"crates/cloud_api_client",
|
||||
"crates/cloud_api_types",
|
||||
"crates/cloud_llm_client",
|
||||
"crates/cloud_zeta2_prompt",
|
||||
"crates/collab",
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
@@ -202,7 +202,6 @@ members = [
|
||||
"crates/zed_actions",
|
||||
"crates/zed_env_vars",
|
||||
"crates/edit_prediction_cli",
|
||||
"crates/zeta_prompt",
|
||||
"crates/zlog",
|
||||
"crates/zlog_settings",
|
||||
"crates/ztracing",
|
||||
@@ -243,7 +242,6 @@ action_log = { path = "crates/action_log" }
|
||||
agent = { path = "crates/agent" }
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
agent_ui = { path = "crates/agent_ui" }
|
||||
agent_ui_v2 = { path = "crates/agent_ui_v2" }
|
||||
agent_settings = { path = "crates/agent_settings" }
|
||||
agent_servers = { path = "crates/agent_servers" }
|
||||
ai_onboarding = { path = "crates/ai_onboarding" }
|
||||
@@ -268,6 +266,7 @@ clock = { path = "crates/clock" }
|
||||
cloud_api_client = { path = "crates/cloud_api_client" }
|
||||
cloud_api_types = { path = "crates/cloud_api_types" }
|
||||
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
||||
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections", version = "0.1.0" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
@@ -426,7 +425,6 @@ zed = { path = "crates/zed" }
|
||||
zed_actions = { path = "crates/zed_actions" }
|
||||
zed_env_vars = { path = "crates/zed_env_vars" }
|
||||
edit_prediction = { path = "crates/edit_prediction" }
|
||||
zeta_prompt = { path = "crates/zeta_prompt" }
|
||||
zlog = { path = "crates/zlog" }
|
||||
zlog_settings = { path = "crates/zlog_settings" }
|
||||
ztracing = { path = "crates/ztracing" }
|
||||
@@ -633,7 +631,7 @@ shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
simplelog = "0.12.2"
|
||||
slotmap = "1.0.6"
|
||||
smallvec = { version = "1.6", features = ["union", "const_new"] }
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "2.0"
|
||||
sqlformat = "0.2"
|
||||
stacksafe = "0.1"
|
||||
@@ -659,7 +657,6 @@ time = { version = "0.3", features = [
|
||||
tiny_http = "0.8"
|
||||
tokio = { version = "1" }
|
||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
||||
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io", "tokio"] }
|
||||
toml = "0.8"
|
||||
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
|
||||
tower-http = "0.4.4"
|
||||
@@ -857,6 +854,8 @@ unexpected_cfgs = { level = "allow" }
|
||||
dbg_macro = "deny"
|
||||
todo = "deny"
|
||||
|
||||
# This is not a style lint, see https://github.com/rust-lang/rust-clippy/pull/15454
|
||||
# Remove when the lint gets promoted to `suspicious`.
|
||||
declare_interior_mutable_const = "deny"
|
||||
|
||||
redundant_clone = "deny"
|
||||
|
||||
@@ -9,7 +9,7 @@ Welcome to Zed, a high-performance, multiplayer code editor from the creators of
|
||||
|
||||
### Installation
|
||||
|
||||
On macOS, Linux, and Windows you can [download Zed directly](https://zed.dev/download) or install Zed via your local package manager ([macOS](https://zed.dev/docs/installation#macos)/[Linux](https://zed.dev/docs/linux#installing-via-a-package-manager)/[Windows](https://zed.dev/docs/windows#package-managers)).
|
||||
On macOS, Linux, and Windows you can [download Zed directly](https://zed.dev/download) or [install Zed via your local package manager](https://zed.dev/docs/linux#installing-via-a-package-manager).
|
||||
|
||||
Other platforms are not yet available:
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.2224 1.32129L5.2036 4.41875C5.15145 4.57727 5.06282 4.72134 4.94481 4.83934C4.82681 4.95735 4.68274 5.04598 4.52422 5.09813L1.42676 6.11693L4.52422 7.13574C4.68274 7.18788 4.82681 7.27652 4.94481 7.39453C5.06282 7.51253 5.15145 7.6566 5.2036 7.81512L6.2224 10.9126L7.24121 7.81512C7.29335 7.6566 7.38199 7.51253 7.5 7.39453C7.618 7.27652 7.76207 7.18788 7.9206 7.13574L11.018 6.11693L7.9206 5.09813C7.76207 5.04598 7.618 4.95735 7.5 4.83934C7.38199 4.72134 7.29335 4.57727 7.24121 4.41875L6.2224 1.32129Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.76681 13.9373C9.76681 13.6048 9.95997 13.3083 10.5126 12.7917L11.8872 11.4978C12.3545 11.0575 12.5612 10.77 12.5612 10.4735C12.5612 10.1411 12.3185 9.91643 11.9681 9.91643C11.6986 9.91643 11.5054 10.0242 11.2673 10.3208C10.9933 10.6622 10.7956 10.779 10.4946 10.779C10.0633 10.779 9.75781 10.4915 9.75781 10.0916C9.75781 9.21559 10.8136 8.44287 12.067 8.44287C13.3743 8.44287 14.3492 9.22907 14.3492 10.2848C14.3492 10.9452 13.9988 11.5742 13.2845 12.2077L12.2242 13.1511V13.223H13.7292C14.2503 13.223 14.5738 13.5015 14.5738 13.9552C14.5738 14.4089 14.2593 14.6785 13.7292 14.6785H10.5979C10.1037 14.6785 9.76681 14.3775 9.76681 13.9373Z" fill="black"/>
|
||||
<path d="M12.8994 1.32129V4.00482M11.5576 2.66302H14.2412" stroke="black" stroke-opacity="0.75" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,12 +10,12 @@
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
// "shift shift": "file_finder::Toggle"
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == insert",
|
||||
"bindings": {
|
||||
// "j k": "vim::NormalBefore"
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
"bindings": {
|
||||
"ctrl-shift-f5": "workspace::Reload", // window:reload
|
||||
"ctrl-k ctrl-n": "workspace::ActivatePreviousPane", // window:focus-next-pane
|
||||
"ctrl-k ctrl-p": "workspace::ActivateNextPane", // window:focus-previous-pane
|
||||
},
|
||||
"ctrl-k ctrl-p": "workspace::ActivateNextPane" // window:focus-previous-pane
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", // editor:lower-case
|
||||
},
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase" // editor:lower-case
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
@@ -32,8 +32,8 @@
|
||||
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
|
||||
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide", // markdown-preview:toggle
|
||||
"ctrl-r": "outline::Toggle", // symbols-view:toggle-project-symbols
|
||||
},
|
||||
"ctrl-r": "outline::Toggle" // symbols-view:toggle-project-symbols
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
@@ -41,8 +41,8 @@
|
||||
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
|
||||
"ctrl-shift-f3": "search::SelectPreviousMatch", // find-and-replace:find-previous-selected
|
||||
},
|
||||
"ctrl-shift-f3": "search::SelectPreviousMatch" // find-and-replace:find-previous-selected
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
@@ -50,8 +50,8 @@
|
||||
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||
"ctrl-k ctrl-b": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||
"ctrl-t": "file_finder::Toggle", // fuzzy-finder:toggle-file-finder
|
||||
"ctrl-r": "project_symbols::Toggle", // symbols-view:toggle-project-symbols
|
||||
},
|
||||
"ctrl-r": "project_symbols::Toggle" // symbols-view:toggle-project-symbols
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -65,8 +65,8 @@
|
||||
"ctrl-6": ["pane::ActivateItem", 5], // tree-view:open-selected-entry-in-pane-6
|
||||
"ctrl-7": ["pane::ActivateItem", 6], // tree-view:open-selected-entry-in-pane-7
|
||||
"ctrl-8": ["pane::ActivateItem", 7], // tree-view:open-selected-entry-in-pane-8
|
||||
"ctrl-9": ["pane::ActivateItem", 8], // tree-view:open-selected-entry-in-pane-9
|
||||
},
|
||||
"ctrl-9": ["pane::ActivateItem", 8] // tree-view:open-selected-entry-in-pane-9
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
@@ -75,8 +75,8 @@
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"ctrl-x": "project_panel::Cut", // tree-view:cut
|
||||
"ctrl-c": "project_panel::Copy", // tree-view:copy
|
||||
"ctrl-v": "project_panel::Paste", // tree-view:paste
|
||||
},
|
||||
"ctrl-v": "project_panel::Paste" // tree-view:paste
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel && not_editing",
|
||||
@@ -90,7 +90,7 @@
|
||||
"d": "project_panel::Duplicate", // tree-view:duplicate
|
||||
"home": "menu::SelectFirst", // core:move-to-top
|
||||
"end": "menu::SelectLast", // core:move-to-bottom
|
||||
"shift-a": "project_panel::NewDirectory", // tree-view:add-folder
|
||||
},
|
||||
},
|
||||
"shift-a": "project_panel::NewDirectory" // tree-view:add-folder
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"ctrl-shift-i": "agent::ToggleFocus",
|
||||
"ctrl-l": "agent::ToggleFocus",
|
||||
"ctrl-shift-l": "agent::ToggleFocus",
|
||||
"ctrl-shift-j": "agent::OpenSettings",
|
||||
},
|
||||
"ctrl-shift-j": "agent::OpenSettings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
@@ -20,18 +20,18 @@
|
||||
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||
"ctrl-k": "assistant::InlineAssist",
|
||||
"ctrl-shift-k": "assistant::InsertIntoEditor",
|
||||
},
|
||||
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "InlineAssistEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-backspace": "editor::Cancel",
|
||||
"ctrl-shift-backspace": "editor::Cancel"
|
||||
// "alt-enter": // Quick Question
|
||||
// "ctrl-shift-enter": // Full File Context
|
||||
// "ctrl-shift-k": // Toggle input focus (editor <> inline assist)
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
||||
@@ -47,7 +47,7 @@
|
||||
"ctrl-shift-backspace": "editor::Cancel",
|
||||
"ctrl-r": "agent::NewThread",
|
||||
"ctrl-shift-v": "editor::Paste",
|
||||
"ctrl-shift-k": "assistant::InsertIntoEditor",
|
||||
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||
// "escape": "agent::ToggleFocus"
|
||||
///// Enable when Zed supports multiple thread tabs
|
||||
// "ctrl-t": // new thread tab
|
||||
@@ -56,29 +56,28 @@
|
||||
///// Enable if Zed adds support for keyboard navigation of thread elements
|
||||
// "tab": // cycle to next message
|
||||
// "shift-tab": // cycle to previous message
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && editor_agent_diff",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-enter": "agent::KeepAll",
|
||||
"ctrl-backspace": "agent::RejectAll",
|
||||
},
|
||||
"ctrl-backspace": "agent::RejectAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-right": "editor::AcceptNextWordEditPrediction",
|
||||
"ctrl-down": "editor::AcceptNextLineEditPrediction",
|
||||
},
|
||||
"ctrl-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k": "assistant::InlineAssist",
|
||||
},
|
||||
},
|
||||
"ctrl-k": "assistant::InlineAssist"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-g": "menu::Cancel",
|
||||
},
|
||||
"ctrl-g": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to avoid falling back to default bindings.
|
||||
@@ -18,8 +18,8 @@
|
||||
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
|
||||
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
|
||||
"ctrl-n": null, // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
|
||||
},
|
||||
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -82,8 +82,8 @@
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"ctrl-r": "buffer_search::Deploy", // isearch-backward
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap", // fill-paragraph
|
||||
},
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && selection_mode", // region selection
|
||||
@@ -119,22 +119,22 @@
|
||||
"alt->": "editor::SelectToEnd",
|
||||
"ctrl-home": "editor::SelectToBeginning",
|
||||
"ctrl-end": "editor::SelectToEnd",
|
||||
"ctrl-g": "editor::Cancel",
|
||||
},
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrevious",
|
||||
"ctrl-n": "editor::ContextMenuNext",
|
||||
},
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext",
|
||||
},
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Example setting for using emacs-style tab
|
||||
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
||||
@@ -164,8 +164,8 @@
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
||||
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
||||
"ctrl-x s": "workspace::SaveAll", // save-some-buffers
|
||||
},
|
||||
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using native emacs from the Zed terminal.
|
||||
@@ -185,22 +185,22 @@
|
||||
"ctrl-x ctrl-f": null, // find-file
|
||||
"ctrl-x ctrl-s": null, // save-buffer
|
||||
"ctrl-x ctrl-w": null, // write-file
|
||||
"ctrl-x s": null, // save-some-buffers
|
||||
},
|
||||
"ctrl-x s": null // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > Editor",
|
||||
"bindings": {
|
||||
"ctrl-s": "search::SelectNextMatch",
|
||||
"ctrl-r": "search::SelectPreviousMatch",
|
||||
"ctrl-g": "buffer_search::Dismiss",
|
||||
},
|
||||
"ctrl-g": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-alt-left": "pane::GoBack",
|
||||
"ctrl-alt-right": "pane::GoForward",
|
||||
},
|
||||
},
|
||||
"ctrl-alt-right": "pane::GoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"shift-f8": "debugger::StepOut",
|
||||
"f9": "debugger::Continue",
|
||||
"shift-f9": "debugger::Start",
|
||||
"alt-shift-f9": "debugger::Start",
|
||||
},
|
||||
"alt-shift-f9": "debugger::Start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -62,30 +62,28 @@
|
||||
"ctrl-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||
"ctrl-shift-u": "editor::ToggleCase",
|
||||
},
|
||||
"ctrl-shift-u": "editor::ToggleCase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-f12": "outline::Toggle",
|
||||
"ctrl-r": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
"ctrl-e": "file_finder::Toggle",
|
||||
"ctrl-shift-n": "file_finder::Toggle",
|
||||
"ctrl-alt-n": "file_finder::Toggle",
|
||||
"ctrl-g": "go_to_line::Toggle",
|
||||
"alt-enter": "editor::ToggleCodeActions",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-q": "editor::Hover",
|
||||
"ctrl-p": "editor::ShowSignatureHelp",
|
||||
"ctrl-\\": "assistant::InlineAssist",
|
||||
},
|
||||
"ctrl-\\": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"shift-enter": "search::SelectPreviousMatch",
|
||||
},
|
||||
"shift-enter": "search::SelectPreviousMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar || ProjectSearchBar",
|
||||
@@ -93,8 +91,8 @@
|
||||
"alt-c": "search::ToggleCaseSensitive",
|
||||
"alt-e": "search::ToggleSelection",
|
||||
"alt-x": "search::ToggleRegex",
|
||||
"alt-w": "search::ToggleWholeWord",
|
||||
},
|
||||
"alt-w": "search::ToggleWholeWord"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
@@ -107,8 +105,8 @@
|
||||
"ctrl-e": "file_finder::Toggle",
|
||||
"ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
|
||||
"ctrl-shift-n": "file_finder::Toggle",
|
||||
"ctrl-alt-n": "file_finder::Toggle",
|
||||
"ctrl-n": "project_symbols::Toggle",
|
||||
"ctrl-alt-n": "file_finder::Toggle",
|
||||
"ctrl-shift-a": "command_palette::Toggle",
|
||||
"shift shift": "command_palette::Toggle",
|
||||
"ctrl-alt-shift-n": "project_symbols::Toggle",
|
||||
@@ -116,8 +114,8 @@
|
||||
"alt-1": "project_panel::ToggleFocus",
|
||||
"alt-5": "debug_panel::ToggleFocus",
|
||||
"alt-6": "diagnostics::Deploy",
|
||||
"alt-7": "outline_panel::ToggleFocus",
|
||||
},
|
||||
"alt-7": "outline_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane", // this is to override the default Pane mappings to switch tabs
|
||||
@@ -131,15 +129,15 @@
|
||||
"alt-7": "outline_panel::ToggleFocus",
|
||||
"alt-8": null, // Services (bottom dock)
|
||||
"alt-9": null, // Git History (bottom dock)
|
||||
"alt-0": "git_panel::ToggleFocus",
|
||||
},
|
||||
"alt-0": "git_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace || Editor",
|
||||
"bindings": {
|
||||
"alt-f12": "terminal_panel::Toggle",
|
||||
"ctrl-shift-k": "git::Push",
|
||||
},
|
||||
"ctrl-shift-k": "git::Push"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -147,8 +145,8 @@
|
||||
"ctrl-alt-left": "pane::GoBack",
|
||||
"ctrl-alt-right": "pane::GoForward",
|
||||
"alt-left": "pane::ActivatePreviousItem",
|
||||
"alt-right": "pane::ActivateNextItem",
|
||||
},
|
||||
"alt-right": "pane::ActivateNextItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
@@ -158,8 +156,8 @@
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"shift-f6": "project_panel::Rename",
|
||||
},
|
||||
"shift-f6": "project_panel::Rename"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
@@ -169,8 +167,8 @@
|
||||
"ctrl-up": "terminal::ScrollLineUp",
|
||||
"ctrl-down": "terminal::ScrollLineDown",
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
},
|
||||
"shift-pagedown": "terminal::ScrollPageDown"
|
||||
}
|
||||
},
|
||||
{ "context": "GitPanel", "bindings": { "alt-0": "workspace::CloseActiveDock" } },
|
||||
{ "context": "ProjectPanel", "bindings": { "alt-1": "workspace::CloseActiveDock" } },
|
||||
@@ -181,7 +179,7 @@
|
||||
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
|
||||
"bindings": {
|
||||
"escape": "editor::ToggleFocus",
|
||||
"shift-escape": "workspace::CloseActiveDock",
|
||||
},
|
||||
},
|
||||
"shift-escape": "workspace::CloseActiveDock"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }],
|
||||
},
|
||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -55,20 +55,20 @@
|
||||
"alt-right": "editor::MoveToNextSubwordEnd",
|
||||
"alt-left": "editor::MoveToPreviousSubwordStart",
|
||||
"alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||
"alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
||||
},
|
||||
"alt-shift-left": "editor::SelectToPreviousSubwordStart"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-r": "outline::Toggle",
|
||||
},
|
||||
"ctrl-r": "outline::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !agent_diff",
|
||||
"bindings": {
|
||||
"ctrl-k ctrl-z": "git::Restore",
|
||||
},
|
||||
"ctrl-k ctrl-z": "git::Restore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -83,15 +83,15 @@
|
||||
"alt-6": ["pane::ActivateItem", 5],
|
||||
"alt-7": ["pane::ActivateItem", 6],
|
||||
"alt-8": ["pane::ActivateItem", 7],
|
||||
"alt-9": "pane::ActivateLastItem",
|
||||
},
|
||||
"alt-9": "pane::ActivateLastItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"ctrl-k ctrl-b": "workspace::ToggleLeftDock",
|
||||
// "ctrl-0": "project_panel::ToggleFocus", // normally resets zoom
|
||||
"shift-ctrl-r": "project_symbols::Toggle",
|
||||
},
|
||||
},
|
||||
"shift-ctrl-r": "project_symbols::Toggle"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
"bindings": {
|
||||
"ctrl-alt-cmd-l": "workspace::Reload",
|
||||
"cmd-k cmd-p": "workspace::ActivatePreviousPane",
|
||||
"cmd-k cmd-n": "workspace::ActivateNextPane",
|
||||
},
|
||||
"cmd-k cmd-n": "workspace::ActivateNextPane"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
||||
},
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
@@ -33,8 +33,8 @@
|
||||
"ctrl-cmd-down": "editor::MoveLineDown",
|
||||
"cmd-\\": "workspace::ToggleLeftDock",
|
||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-r": "outline::Toggle",
|
||||
},
|
||||
"cmd-r": "outline::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
@@ -42,8 +42,8 @@
|
||||
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||
"cmd-f3": "search::SelectNextMatch",
|
||||
"cmd-shift-f3": "search::SelectPreviousMatch",
|
||||
},
|
||||
"cmd-shift-f3": "search::SelectPreviousMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
@@ -51,8 +51,8 @@
|
||||
"cmd-\\": "workspace::ToggleLeftDock",
|
||||
"cmd-k cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-t": "file_finder::Toggle",
|
||||
"cmd-shift-r": "project_symbols::Toggle",
|
||||
},
|
||||
"cmd-shift-r": "project_symbols::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -67,8 +67,8 @@
|
||||
"cmd-6": ["pane::ActivateItem", 5],
|
||||
"cmd-7": ["pane::ActivateItem", 6],
|
||||
"cmd-8": ["pane::ActivateItem", 7],
|
||||
"cmd-9": "pane::ActivateLastItem",
|
||||
},
|
||||
"cmd-9": "pane::ActivateLastItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
@@ -77,8 +77,8 @@
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"cmd-x": "project_panel::Cut",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
},
|
||||
"cmd-v": "project_panel::Paste"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel && not_editing",
|
||||
@@ -92,7 +92,7 @@
|
||||
"d": "project_panel::Duplicate",
|
||||
"home": "menu::SelectFirst",
|
||||
"end": "menu::SelectLast",
|
||||
"shift-a": "project_panel::NewDirectory",
|
||||
},
|
||||
},
|
||||
"shift-a": "project_panel::NewDirectory"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"cmd-shift-i": "agent::ToggleFocus",
|
||||
"cmd-l": "agent::ToggleFocus",
|
||||
"cmd-shift-l": "agent::ToggleFocus",
|
||||
"cmd-shift-j": "agent::OpenSettings",
|
||||
},
|
||||
"cmd-shift-j": "agent::OpenSettings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
@@ -20,19 +20,19 @@
|
||||
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||
"cmd-k": "assistant::InlineAssist",
|
||||
"cmd-shift-k": "assistant::InsertIntoEditor",
|
||||
},
|
||||
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "InlineAssistEditor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-backspace": "editor::Cancel",
|
||||
"cmd-enter": "menu::Confirm",
|
||||
"cmd-enter": "menu::Confirm"
|
||||
// "alt-enter": // Quick Question
|
||||
// "cmd-shift-enter": // Full File Context
|
||||
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
||||
@@ -48,7 +48,7 @@
|
||||
"cmd-shift-backspace": "editor::Cancel",
|
||||
"cmd-r": "agent::NewThread",
|
||||
"cmd-shift-v": "editor::Paste",
|
||||
"cmd-shift-k": "assistant::InsertIntoEditor",
|
||||
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||
// "escape": "agent::ToggleFocus"
|
||||
///// Enable when Zed supports multiple thread tabs
|
||||
// "cmd-t": // new thread tab
|
||||
@@ -57,29 +57,28 @@
|
||||
///// Enable if Zed adds support for keyboard navigation of thread elements
|
||||
// "tab": // cycle to next message
|
||||
// "shift-tab": // cycle to previous message
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && editor_agent_diff",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-enter": "agent::KeepAll",
|
||||
"cmd-backspace": "agent::RejectAll",
|
||||
},
|
||||
"cmd-backspace": "agent::RejectAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full && edit_prediction",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-right": "editor::AcceptNextWordEditPrediction",
|
||||
"cmd-down": "editor::AcceptNextLineEditPrediction",
|
||||
},
|
||||
"cmd-right": "editor::AcceptPartialEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k": "assistant::InlineAssist",
|
||||
},
|
||||
},
|
||||
"cmd-k": "assistant::InlineAssist"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
{
|
||||
"context": "!GitPanel",
|
||||
"bindings": {
|
||||
"ctrl-g": "menu::Cancel",
|
||||
},
|
||||
"ctrl-g": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to avoid falling back to default bindings.
|
||||
@@ -15,8 +15,8 @@
|
||||
// NOTE: must be declared before the `Editor` override.
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||
},
|
||||
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -79,8 +79,8 @@
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"ctrl-r": "buffer_search::Deploy", // isearch-backward
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap", // fill-paragraph
|
||||
},
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && selection_mode", // region selection
|
||||
@@ -116,22 +116,22 @@
|
||||
"alt->": "editor::SelectToEnd",
|
||||
"ctrl-home": "editor::SelectToBeginning",
|
||||
"ctrl-end": "editor::SelectToEnd",
|
||||
"ctrl-g": "editor::Cancel",
|
||||
},
|
||||
"ctrl-g": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ContextMenuPrevious",
|
||||
"ctrl-n": "editor::ContextMenuNext",
|
||||
},
|
||||
"ctrl-n": "editor::ContextMenuNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext",
|
||||
},
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
// Example setting for using emacs-style tab
|
||||
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
||||
@@ -161,8 +161,8 @@
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
||||
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
||||
"ctrl-x s": "workspace::SaveAll", // save-some-buffers
|
||||
},
|
||||
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
// Workaround to enable using native emacs from the Zed terminal.
|
||||
@@ -182,22 +182,22 @@
|
||||
"ctrl-x ctrl-f": null, // find-file
|
||||
"ctrl-x ctrl-s": null, // save-buffer
|
||||
"ctrl-x ctrl-w": null, // write-file
|
||||
"ctrl-x s": null, // save-some-buffers
|
||||
},
|
||||
"ctrl-x s": null // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > Editor",
|
||||
"bindings": {
|
||||
"ctrl-s": "search::SelectNextMatch",
|
||||
"ctrl-r": "search::SelectPreviousMatch",
|
||||
"ctrl-g": "buffer_search::Dismiss",
|
||||
},
|
||||
"ctrl-g": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-alt-left": "pane::GoBack",
|
||||
"ctrl-alt-right": "pane::GoForward",
|
||||
},
|
||||
},
|
||||
"ctrl-alt-right": "pane::GoForward"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"shift-f8": "debugger::StepOut",
|
||||
"f9": "debugger::Continue",
|
||||
"shift-f9": "debugger::Start",
|
||||
"alt-shift-f9": "debugger::Start",
|
||||
},
|
||||
"alt-shift-f9": "debugger::Start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -60,30 +60,28 @@
|
||||
"cmd-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||
"cmd-shift-u": "editor::ToggleCase",
|
||||
},
|
||||
"cmd-shift-u": "editor::ToggleCase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"cmd-f12": "outline::Toggle",
|
||||
"cmd-r": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
"cmd-l": "go_to_line::Toggle",
|
||||
"cmd-e": "file_finder::Toggle",
|
||||
"cmd-shift-o": "file_finder::Toggle",
|
||||
"cmd-shift-n": "file_finder::Toggle",
|
||||
"cmd-l": "go_to_line::Toggle",
|
||||
"alt-enter": "editor::ToggleCodeActions",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"cmd-j": "editor::Hover",
|
||||
"cmd-p": "editor::ShowSignatureHelp",
|
||||
"cmd-\\": "assistant::InlineAssist",
|
||||
},
|
||||
"cmd-\\": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"shift-enter": "search::SelectPreviousMatch",
|
||||
},
|
||||
"shift-enter": "search::SelectPreviousMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar || ProjectSearchBar",
|
||||
@@ -95,8 +93,8 @@
|
||||
"ctrl-alt-c": "search::ToggleCaseSensitive",
|
||||
"ctrl-alt-e": "search::ToggleSelection",
|
||||
"ctrl-alt-w": "search::ToggleWholeWord",
|
||||
"ctrl-alt-x": "search::ToggleRegex",
|
||||
},
|
||||
"ctrl-alt-x": "search::ToggleRegex"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
@@ -118,8 +116,8 @@
|
||||
"cmd-1": "project_panel::ToggleFocus",
|
||||
"cmd-5": "debug_panel::ToggleFocus",
|
||||
"cmd-6": "diagnostics::Deploy",
|
||||
"cmd-7": "outline_panel::ToggleFocus",
|
||||
},
|
||||
"cmd-7": "outline_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane", // this is to override the default Pane mappings to switch tabs
|
||||
@@ -133,15 +131,15 @@
|
||||
"cmd-7": "outline_panel::ToggleFocus",
|
||||
"cmd-8": null, // Services (bottom dock)
|
||||
"cmd-9": null, // Git History (bottom dock)
|
||||
"cmd-0": "git_panel::ToggleFocus",
|
||||
},
|
||||
"cmd-0": "git_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace || Editor",
|
||||
"bindings": {
|
||||
"alt-f12": "terminal_panel::Toggle",
|
||||
"cmd-shift-k": "git::Push",
|
||||
},
|
||||
"cmd-shift-k": "git::Push"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -149,8 +147,8 @@
|
||||
"cmd-alt-left": "pane::GoBack",
|
||||
"cmd-alt-right": "pane::GoForward",
|
||||
"alt-left": "pane::ActivatePreviousItem",
|
||||
"alt-right": "pane::ActivateNextItem",
|
||||
},
|
||||
"alt-right": "pane::ActivateNextItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
@@ -161,8 +159,8 @@
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"shift-f6": "project_panel::Rename",
|
||||
},
|
||||
"shift-f6": "project_panel::Rename"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
@@ -172,8 +170,8 @@
|
||||
"cmd-up": "terminal::ScrollLineUp",
|
||||
"cmd-down": "terminal::ScrollLineDown",
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
},
|
||||
"shift-pagedown": "terminal::ScrollPageDown"
|
||||
}
|
||||
},
|
||||
{ "context": "GitPanel", "bindings": { "cmd-0": "workspace::CloseActiveDock" } },
|
||||
{ "context": "ProjectPanel", "bindings": { "cmd-1": "workspace::CloseActiveDock" } },
|
||||
@@ -184,7 +182,7 @@
|
||||
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
|
||||
"bindings": {
|
||||
"escape": "editor::ToggleFocus",
|
||||
"shift-escape": "workspace::CloseActiveDock",
|
||||
},
|
||||
},
|
||||
"shift-escape": "workspace::CloseActiveDock"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }],
|
||||
},
|
||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -57,20 +57,20 @@
|
||||
"ctrl-right": "editor::MoveToNextSubwordEnd",
|
||||
"ctrl-left": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
|
||||
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart",
|
||||
},
|
||||
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"cmd-r": "outline::Toggle",
|
||||
},
|
||||
"cmd-r": "outline::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && !agent_diff",
|
||||
"bindings": {
|
||||
"cmd-k cmd-z": "git::Restore",
|
||||
},
|
||||
"cmd-k cmd-z": "git::Restore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -85,8 +85,8 @@
|
||||
"cmd-6": ["pane::ActivateItem", 5],
|
||||
"cmd-7": ["pane::ActivateItem", 6],
|
||||
"cmd-8": ["pane::ActivateItem", 7],
|
||||
"cmd-9": "pane::ActivateLastItem",
|
||||
},
|
||||
"cmd-9": "pane::ActivateLastItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
@@ -95,7 +95,7 @@
|
||||
"cmd-t": "file_finder::Toggle",
|
||||
"shift-cmd-r": "project_symbols::Toggle",
|
||||
// Currently busted: https://github.com/zed-industries/feedback/issues/898
|
||||
"ctrl-0": "project_panel::ToggleFocus",
|
||||
},
|
||||
},
|
||||
"ctrl-0": "project_panel::ToggleFocus"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-shift-o": "projects::OpenRecent",
|
||||
"cmd-alt-tab": "project_panel::ToggleFocus",
|
||||
},
|
||||
"cmd-alt-tab": "project_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
@@ -15,8 +15,8 @@
|
||||
"cmd-enter": "editor::NewlineBelow",
|
||||
"cmd-alt-enter": "editor::NewlineAbove",
|
||||
"cmd-shift-l": "editor::SelectLine",
|
||||
"cmd-shift-t": "outline::Toggle",
|
||||
},
|
||||
"cmd-shift-t": "outline::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
@@ -41,30 +41,30 @@
|
||||
"ctrl-u": "editor::ConvertToUpperCase",
|
||||
"ctrl-shift-u": "editor::ConvertToLowerCase",
|
||||
"ctrl-alt-u": "editor::ConvertToUpperCamelCase",
|
||||
"ctrl-_": "editor::ConvertToSnakeCase",
|
||||
},
|
||||
"ctrl-_": "editor::ConvertToSnakeCase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"ctrl-s": "search::SelectNextMatch",
|
||||
"ctrl-shift-s": "search::SelectPreviousMatch",
|
||||
},
|
||||
"ctrl-shift-s": "search::SelectPreviousMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"cmd-alt-ctrl-d": "workspace::ToggleLeftDock",
|
||||
"cmd-t": "file_finder::Toggle",
|
||||
"cmd-shift-t": "project_symbols::Toggle",
|
||||
},
|
||||
"cmd-shift-t": "project_symbols::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"alt-cmd-r": "search::ToggleRegex",
|
||||
"ctrl-tab": "project_panel::ToggleFocus",
|
||||
},
|
||||
"ctrl-tab": "project_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
@@ -75,11 +75,11 @@
|
||||
"return": "project_panel::Rename",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
"cmd-alt-c": "project_panel::CopyPath",
|
||||
},
|
||||
"cmd-alt-c": "project_panel::CopyPath"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
"bindings": {},
|
||||
},
|
||||
"bindings": {}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"backspace": "editor::Backspace",
|
||||
"delete": "editor::Delete",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
},
|
||||
},
|
||||
"right": "editor::MoveRight"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -181,8 +181,8 @@
|
||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-^": "pane::AlternateFile",
|
||||
".": "vim::Repeat",
|
||||
},
|
||||
".": "vim::Repeat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal || vim_mode == visual || vim_mode == operator",
|
||||
@@ -223,8 +223,8 @@
|
||||
"] r": "vim::GoToNextReference",
|
||||
// tree-sitter related commands
|
||||
"[ x": "vim::SelectLargerSyntaxNode",
|
||||
"] x": "vim::SelectSmallerSyntaxNode",
|
||||
},
|
||||
"] x": "vim::SelectSmallerSyntaxNode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == normal",
|
||||
@@ -261,16 +261,16 @@
|
||||
"[ d": "editor::GoToPreviousDiagnostic",
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPreviousHunk",
|
||||
"g c": "vim::PushToggleComments",
|
||||
},
|
||||
"g c": "vim::PushToggleComments"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "VimControl && VimCount",
|
||||
"bindings": {
|
||||
"0": ["vim::Number", 0],
|
||||
":": "vim::CountCommand",
|
||||
"%": "vim::GoToPercentage",
|
||||
},
|
||||
"%": "vim::GoToPercentage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == visual",
|
||||
@@ -322,8 +322,8 @@
|
||||
"g w": "vim::Rewrap",
|
||||
"g ?": "vim::ConvertToRot13",
|
||||
// "g ?": "vim::ConvertToRot47",
|
||||
"\"": "vim::PushRegister",
|
||||
},
|
||||
"\"": "vim::PushRegister"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_select",
|
||||
@@ -343,8 +343,8 @@
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
".": "vim::Repeat",
|
||||
"alt-.": "vim::RepeatFind",
|
||||
},
|
||||
"alt-.": "vim::RepeatFind"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert",
|
||||
@@ -374,8 +374,8 @@
|
||||
"ctrl-r": "vim::PushRegister",
|
||||
"insert": "vim::ToggleReplace",
|
||||
"ctrl-o": "vim::TemporaryNormal",
|
||||
"ctrl-s": "editor::ShowSignatureHelp",
|
||||
},
|
||||
"ctrl-s": "editor::ShowSignatureHelp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "showing_completions",
|
||||
@@ -383,8 +383,8 @@
|
||||
"ctrl-d": "vim::ScrollDown",
|
||||
"ctrl-u": "vim::ScrollUp",
|
||||
"ctrl-e": "vim::LineDown",
|
||||
"ctrl-y": "vim::LineUp",
|
||||
},
|
||||
"ctrl-y": "vim::LineUp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "(vim_mode == normal || vim_mode == helix_normal) && !menu",
|
||||
@@ -409,31 +409,23 @@
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"\"": "vim::PushRegister",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||
},
|
||||
"ctrl-pageup": "pane::ActivatePreviousItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "VimControl && vim_mode == helix_normal && !menu",
|
||||
"bindings": {
|
||||
"j": ["vim::Down", { "display_lines": true }],
|
||||
"down": ["vim::Down", { "display_lines": true }],
|
||||
"k": ["vim::Up", { "display_lines": true }],
|
||||
"up": ["vim::Up", { "display_lines": true }],
|
||||
"g j": "vim::Down",
|
||||
"g down": "vim::Down",
|
||||
"g k": "vim::Up",
|
||||
"g up": "vim::Up",
|
||||
"escape": "vim::SwitchToHelixNormalMode",
|
||||
"i": "vim::HelixInsert",
|
||||
"a": "vim::HelixAppend",
|
||||
"ctrl-[": "editor::Cancel",
|
||||
},
|
||||
"ctrl-[": "editor::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_select && !menu",
|
||||
"bindings": {
|
||||
"escape": "vim::SwitchToHelixNormalMode",
|
||||
},
|
||||
"escape": "vim::SwitchToHelixNormalMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
|
||||
@@ -453,9 +445,9 @@
|
||||
"shift-r": "editor::Paste",
|
||||
"`": "vim::ConvertToLowerCase",
|
||||
"alt-`": "vim::ConvertToUpperCase",
|
||||
"insert": "vim::InsertBefore", // not a helix default
|
||||
"insert": "vim::InsertBefore",
|
||||
"shift-u": "editor::Redo",
|
||||
"ctrl-r": "vim::Redo", // not a helix default
|
||||
"ctrl-r": "vim::Redo",
|
||||
"y": "vim::HelixYank",
|
||||
"p": "vim::HelixPaste",
|
||||
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||
@@ -484,7 +476,6 @@
|
||||
"alt-p": "editor::SelectPreviousSyntaxNode",
|
||||
"alt-n": "editor::SelectNextSyntaxNode",
|
||||
|
||||
// Search
|
||||
"n": "vim::HelixSelectNext",
|
||||
"shift-n": "vim::HelixSelectPrevious",
|
||||
|
||||
@@ -492,27 +483,27 @@
|
||||
"g e": "vim::EndOfDocument",
|
||||
"g h": "vim::StartOfLine",
|
||||
"g l": "vim::EndOfLine",
|
||||
"g s": "vim::FirstNonWhitespace",
|
||||
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||
"g t": "vim::WindowTop",
|
||||
"g c": "vim::WindowMiddle",
|
||||
"g b": "vim::WindowBottom",
|
||||
"g r": "editor::FindAllReferences",
|
||||
"g r": "editor::FindAllReferences", // zed specific
|
||||
"g n": "pane::ActivateNextItem",
|
||||
"shift-l": "pane::ActivateNextItem", // not a helix default
|
||||
"shift-l": "pane::ActivateNextItem",
|
||||
"g p": "pane::ActivatePreviousItem",
|
||||
"shift-h": "pane::ActivatePreviousItem", // not a helix default
|
||||
"g .": "vim::HelixGotoLastModification",
|
||||
"shift-h": "pane::ActivatePreviousItem",
|
||||
"g .": "vim::HelixGotoLastModification", // go to last modification
|
||||
|
||||
// Window mode
|
||||
"space w v": "pane::SplitDown",
|
||||
"space w s": "pane::SplitRight",
|
||||
"space w h": "workspace::ActivatePaneLeft",
|
||||
"space w j": "workspace::ActivatePaneDown",
|
||||
"space w k": "workspace::ActivatePaneUp",
|
||||
"space w l": "workspace::ActivatePaneRight",
|
||||
"space w k": "workspace::ActivatePaneUp",
|
||||
"space w j": "workspace::ActivatePaneDown",
|
||||
"space w q": "pane::CloseActiveItem",
|
||||
"space w r": "pane::SplitRight", // not a helix default
|
||||
"space w d": "pane::SplitDown", // not a helix default
|
||||
"space w s": "pane::SplitRight",
|
||||
"space w r": "pane::SplitRight",
|
||||
"space w v": "pane::SplitDown",
|
||||
"space w d": "pane::SplitDown",
|
||||
|
||||
// Space mode
|
||||
"space f": "file_finder::Toggle",
|
||||
@@ -526,7 +517,6 @@
|
||||
"space c": "editor::ToggleComments",
|
||||
"space p": "editor::Paste",
|
||||
"space y": "editor::Copy",
|
||||
"space /": "pane::DeploySearch",
|
||||
|
||||
// Other
|
||||
":": "command_palette::Toggle",
|
||||
@@ -534,22 +524,24 @@
|
||||
"]": ["vim::PushHelixNext", { "around": true }],
|
||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap", // not a helix default & clashes with helix `goto_word`
|
||||
},
|
||||
"g w": "vim::PushRewrap"
|
||||
// "tab": "pane::ActivateNextItem",
|
||||
// "shift-tab": "pane::ActivatePrevItem",
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowWordCompletions",
|
||||
"ctrl-n": "editor::ShowWordCompletions",
|
||||
},
|
||||
"ctrl-n": "editor::ShowWordCompletions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "(vim_mode == insert || vim_mode == normal) && showing_signature_help && !showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||
"ctrl-n": "editor::SignatureHelpNext",
|
||||
},
|
||||
"ctrl-n": "editor::SignatureHelpNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == replace",
|
||||
@@ -565,8 +557,8 @@
|
||||
"backspace": "vim::UndoReplace",
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"insert": "vim::InsertBefore",
|
||||
},
|
||||
"insert": "vim::InsertBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == waiting",
|
||||
@@ -578,14 +570,14 @@
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-k": ["vim::PushDigraph", {}],
|
||||
"ctrl-v": ["vim::PushLiteral", {}],
|
||||
"ctrl-q": ["vim::PushLiteral", {}],
|
||||
},
|
||||
"ctrl-q": ["vim::PushLiteral", {}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == waiting && (vim_operator == ys || vim_operator == cs)",
|
||||
"bindings": {
|
||||
"escape": "vim::SwitchToNormalMode",
|
||||
},
|
||||
"escape": "vim::SwitchToNormalMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == operator",
|
||||
@@ -593,8 +585,8 @@
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"escape": "vim::ClearOperators",
|
||||
"g c": "vim::Comment",
|
||||
},
|
||||
"g c": "vim::Comment"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs || vim_operator == helix_next || vim_operator == helix_previous",
|
||||
@@ -631,14 +623,14 @@
|
||||
"shift-i": ["vim::IndentObj", { "include_below": true }],
|
||||
"f": "vim::Method",
|
||||
"c": "vim::Class",
|
||||
"e": "vim::EntireFile",
|
||||
},
|
||||
"e": "vim::EntireFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == helix_m",
|
||||
"bindings": {
|
||||
"m": "vim::Matching",
|
||||
},
|
||||
"m": "vim::Matching"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == helix_next",
|
||||
@@ -655,8 +647,8 @@
|
||||
"x": "editor::SelectSmallerSyntaxNode",
|
||||
"d": "editor::GoToDiagnostic",
|
||||
"c": "editor::GoToHunk",
|
||||
"space": "vim::InsertEmptyLineBelow",
|
||||
},
|
||||
"space": "vim::InsertEmptyLineBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == helix_previous",
|
||||
@@ -673,8 +665,8 @@
|
||||
"x": "editor::SelectLargerSyntaxNode",
|
||||
"d": "editor::GoToPreviousDiagnostic",
|
||||
"c": "editor::GoToPreviousHunk",
|
||||
"space": "vim::InsertEmptyLineAbove",
|
||||
},
|
||||
"space": "vim::InsertEmptyLineAbove"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == c",
|
||||
@@ -682,8 +674,8 @@
|
||||
"c": "vim::CurrentLine",
|
||||
"x": "vim::Exchange",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushChangeSurrounds", {}],
|
||||
},
|
||||
"s": ["vim::PushChangeSurrounds", {}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == d",
|
||||
@@ -695,36 +687,36 @@
|
||||
"shift-o": "git::ToggleStaged",
|
||||
"p": "git::Restore", // "d p"
|
||||
"u": "git::StageAndNext", // "d u"
|
||||
"shift-u": "git::UnstageAndNext", // "d shift-u"
|
||||
},
|
||||
"shift-u": "git::UnstageAndNext" // "d shift-u"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gu",
|
||||
"bindings": {
|
||||
"g u": "vim::CurrentLine",
|
||||
"u": "vim::CurrentLine",
|
||||
},
|
||||
"u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gU",
|
||||
"bindings": {
|
||||
"g shift-u": "vim::CurrentLine",
|
||||
"shift-u": "vim::CurrentLine",
|
||||
},
|
||||
"shift-u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == g~",
|
||||
"bindings": {
|
||||
"g ~": "vim::CurrentLine",
|
||||
"~": "vim::CurrentLine",
|
||||
},
|
||||
"~": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == g?",
|
||||
"bindings": {
|
||||
"g ?": "vim::CurrentLine",
|
||||
"?": "vim::CurrentLine",
|
||||
},
|
||||
"?": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gq",
|
||||
@@ -732,66 +724,66 @@
|
||||
"g q": "vim::CurrentLine",
|
||||
"q": "vim::CurrentLine",
|
||||
"g w": "vim::CurrentLine",
|
||||
"w": "vim::CurrentLine",
|
||||
},
|
||||
"w": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == y",
|
||||
"bindings": {
|
||||
"y": "vim::CurrentLine",
|
||||
"v": "vim::PushForcedMotion",
|
||||
"s": ["vim::PushAddSurrounds", {}],
|
||||
},
|
||||
"s": ["vim::PushAddSurrounds", {}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == ys",
|
||||
"bindings": {
|
||||
"s": "vim::CurrentLine",
|
||||
},
|
||||
"s": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == >",
|
||||
"bindings": {
|
||||
">": "vim::CurrentLine",
|
||||
},
|
||||
">": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == <",
|
||||
"bindings": {
|
||||
"<": "vim::CurrentLine",
|
||||
},
|
||||
"<": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == eq",
|
||||
"bindings": {
|
||||
"=": "vim::CurrentLine",
|
||||
},
|
||||
"=": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == sh",
|
||||
"bindings": {
|
||||
"!": "vim::CurrentLine",
|
||||
},
|
||||
"!": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
},
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gR",
|
||||
"bindings": {
|
||||
"r": "vim::CurrentLine",
|
||||
"shift-r": "vim::CurrentLine",
|
||||
},
|
||||
"shift-r": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == cx",
|
||||
"bindings": {
|
||||
"x": "vim::CurrentLine",
|
||||
"c": "vim::ClearExchange",
|
||||
},
|
||||
"c": "vim::ClearExchange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == literal",
|
||||
@@ -833,15 +825,15 @@
|
||||
"tab": ["vim::Literal", ["tab", "\u0009"]],
|
||||
// zed extensions:
|
||||
"backspace": ["vim::Literal", ["backspace", "\u0008"]],
|
||||
"delete": ["vim::Literal", ["delete", "\u007F"]],
|
||||
},
|
||||
"delete": ["vim::Literal", ["delete", "\u007F"]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss",
|
||||
},
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "VimControl && !menu || !Editor && !Terminal",
|
||||
@@ -902,8 +894,8 @@
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||
"g t": "vim::GoToTab",
|
||||
"g shift-t": "vim::GoToPreviousTab",
|
||||
},
|
||||
"g shift-t": "vim::GoToPreviousTab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "!Editor && !Terminal",
|
||||
@@ -913,8 +905,8 @@
|
||||
"] b": "pane::ActivateNextItem",
|
||||
"[ b": "pane::ActivatePreviousItem",
|
||||
"] shift-b": "pane::ActivateLastItem",
|
||||
"[ shift-b": ["pane::ActivateItem", 0],
|
||||
},
|
||||
"[ shift-b": ["pane::ActivateItem", 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
// netrw compatibility
|
||||
@@ -964,45 +956,17 @@
|
||||
"6": ["vim::Number", 6],
|
||||
"7": ["vim::Number", 7],
|
||||
"8": ["vim::Number", 8],
|
||||
"9": ["vim::Number", 9],
|
||||
},
|
||||
"9": ["vim::Number", 9]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel && not_editing",
|
||||
"bindings": {
|
||||
"h": "outline_panel::CollapseSelectedEntry",
|
||||
"j": "vim::MenuSelectNext",
|
||||
"k": "vim::MenuSelectPrevious",
|
||||
"down": "vim::MenuSelectNext",
|
||||
"up": "vim::MenuSelectPrevious",
|
||||
"l": "outline_panel::ExpandSelectedEntry",
|
||||
"j": "menu::SelectNext",
|
||||
"k": "menu::SelectPrevious",
|
||||
"shift-g": "menu::SelectLast",
|
||||
"g g": "menu::SelectFirst",
|
||||
"-": "outline_panel::SelectParent",
|
||||
"enter": "editor::ToggleFocus",
|
||||
"/": "menu::Cancel",
|
||||
"ctrl-u": "outline_panel::ScrollUp",
|
||||
"ctrl-d": "outline_panel::ScrollDown",
|
||||
"z t": "outline_panel::ScrollCursorTop",
|
||||
"z z": "outline_panel::ScrollCursorCenter",
|
||||
"z b": "outline_panel::ScrollCursorBottom",
|
||||
"0": ["vim::Number", 0],
|
||||
"1": ["vim::Number", 1],
|
||||
"2": ["vim::Number", 2],
|
||||
"3": ["vim::Number", 3],
|
||||
"4": ["vim::Number", 4],
|
||||
"5": ["vim::Number", 5],
|
||||
"6": ["vim::Number", 6],
|
||||
"7": ["vim::Number", 7],
|
||||
"8": ["vim::Number", 8],
|
||||
"9": ["vim::Number", 9],
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel && editing",
|
||||
"bindings": {
|
||||
"enter": "menu::Cancel",
|
||||
},
|
||||
"g g": "menu::SelectFirst"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
@@ -1017,8 +981,8 @@
|
||||
"x": "git::ToggleStaged",
|
||||
"shift-x": "git::StageAll",
|
||||
"g x": "git::StageRange",
|
||||
"shift-u": "git::UnstageAll",
|
||||
},
|
||||
"shift-u": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == auto_height && VimControl",
|
||||
@@ -1029,8 +993,8 @@
|
||||
"#": null,
|
||||
"*": null,
|
||||
"n": null,
|
||||
"shift-n": null,
|
||||
},
|
||||
"shift-n": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Picker > Editor",
|
||||
@@ -1039,29 +1003,29 @@
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-p": "menu::SelectPrevious",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
},
|
||||
"ctrl-n": "menu::SelectNext"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
|
||||
"bindings": {
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
},
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && edit_prediction",
|
||||
"bindings": {
|
||||
// This is identical to the binding in the base keymap, but the vim bindings above to
|
||||
// "vim::Tab" shadow it, so it needs to be bound again.
|
||||
"tab": "editor::AcceptEditPrediction",
|
||||
},
|
||||
"tab": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "MessageEditor > Editor && VimControl",
|
||||
"bindings": {
|
||||
"enter": "agent::Chat",
|
||||
},
|
||||
"enter": "agent::Chat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "os != macos && Editor && edit_prediction_conflict",
|
||||
@@ -1069,8 +1033,8 @@
|
||||
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This
|
||||
// is because alt-tab may not be available, as it is often used for window switching on Linux
|
||||
// and Windows.
|
||||
"alt-l": "editor::AcceptEditPrediction",
|
||||
},
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow > NavigationMenu && !search",
|
||||
@@ -1080,16 +1044,7 @@
|
||||
"k": "settings_editor::FocusPreviousNavEntry",
|
||||
"j": "settings_editor::FocusNextNavEntry",
|
||||
"g g": "settings_editor::FocusFirstNavEntry",
|
||||
"shift-g": "settings_editor::FocusLastNavEntry",
|
||||
},
|
||||
},
|
||||
{
|
||||
"context": "MarkdownPreview",
|
||||
"bindings": {
|
||||
"ctrl-u": "markdown::ScrollPageUp",
|
||||
"ctrl-d": "markdown::ScrollPageDown",
|
||||
"ctrl-y": "markdown::ScrollUp",
|
||||
"ctrl-e": "markdown::ScrollDown",
|
||||
},
|
||||
},
|
||||
"shift-g": "settings_editor::FocusLastNavEntry"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -39,5 +39,6 @@ Only make changes that are necessary to fulfill the prompt, leave everything els
|
||||
|
||||
Start at the indentation level in the original file in the rewritten {{content_type}}.
|
||||
|
||||
IMPORTANT: You MUST use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled. You MUST NOT send back unstructured text. If you need to make a statement or ask a question you MUST use one of the tools to do so.
|
||||
You must use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled. It is an error if
|
||||
you simply send back unstructured text. If you need to make a statement or ask a question you must use one of the tools to do so.
|
||||
It is an error if you try to make a change that cannot be made simply by editing the rewrite_section.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark",
|
||||
"dark": "One Dark"
|
||||
},
|
||||
"icon_theme": "Zed (Default)",
|
||||
// The name of a base set of key bindings to use.
|
||||
@@ -29,7 +29,7 @@
|
||||
// Features that can be globally enabled or disabled
|
||||
"features": {
|
||||
// Which edit prediction provider to use.
|
||||
"edit_prediction_provider": "zed",
|
||||
"edit_prediction_provider": "zed"
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
// ".ZedMono" currently aliases to Lilex
|
||||
@@ -69,7 +69,7 @@
|
||||
// The OpenType features to enable for text in the UI
|
||||
"ui_font_features": {
|
||||
// Disable ligatures:
|
||||
"calt": false,
|
||||
"calt": false
|
||||
},
|
||||
// The weight of the UI font in standard CSS units from 100 to 900.
|
||||
"ui_font_weight": 400,
|
||||
@@ -87,7 +87,7 @@
|
||||
"border_size": 0.0,
|
||||
// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
|
||||
// Values are clamped to the [0.0, 1.0] range.
|
||||
"inactive_opacity": 1.0,
|
||||
"inactive_opacity": 1.0
|
||||
},
|
||||
// Layout mode of the bottom dock. Defaults to "contained"
|
||||
// choices: contained, full, left_aligned, right_aligned
|
||||
@@ -103,12 +103,12 @@
|
||||
"left_padding": 0.2,
|
||||
// The relative width of the right padding of the central pane from the
|
||||
// workspace when the centered layout is used.
|
||||
"right_padding": 0.2,
|
||||
"right_padding": 0.2
|
||||
},
|
||||
// Image viewer settings
|
||||
"image_viewer": {
|
||||
// The unit for image file sizes: "binary" (KiB, MiB) or decimal (KB, MB)
|
||||
"unit": "binary",
|
||||
"unit": "binary"
|
||||
},
|
||||
// Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier.
|
||||
//
|
||||
@@ -296,7 +296,7 @@
|
||||
// When true, enables drag and drop text selection in buffer.
|
||||
"enabled": true,
|
||||
// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created.
|
||||
"delay": 300,
|
||||
"delay": 300
|
||||
},
|
||||
// What to do when go to definition yields no results.
|
||||
//
|
||||
@@ -400,14 +400,14 @@
|
||||
// Visible characters used to render whitespace when show_whitespaces is enabled.
|
||||
"whitespace_map": {
|
||||
"space": "•",
|
||||
"tab": "→",
|
||||
"tab": "→"
|
||||
},
|
||||
// Settings related to calls in Zed
|
||||
"calls": {
|
||||
// Join calls with the microphone live by default
|
||||
"mute_on_join": false,
|
||||
// Share your project when you are the first to join a channel
|
||||
"share_on_join": false,
|
||||
"share_on_join": false
|
||||
},
|
||||
// Toolbar related settings
|
||||
"toolbar": {
|
||||
@@ -420,7 +420,7 @@
|
||||
// Whether to show agent review buttons in the editor toolbar.
|
||||
"agent_review": true,
|
||||
// Whether to show code action buttons in the editor toolbar.
|
||||
"code_actions": false,
|
||||
"code_actions": false
|
||||
},
|
||||
// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
"use_system_window_tabs": false,
|
||||
@@ -436,12 +436,10 @@
|
||||
"show_onboarding_banner": true,
|
||||
// Whether to show user picture in the titlebar.
|
||||
"show_user_picture": true,
|
||||
// Whether to show the user menu in the titlebar.
|
||||
"show_user_menu": true,
|
||||
// Whether to show the sign in button in the titlebar.
|
||||
"show_sign_in": true,
|
||||
// Whether to show the menus in the titlebar.
|
||||
"show_menus": false,
|
||||
"show_menus": false
|
||||
},
|
||||
"audio": {
|
||||
// Opt into the new audio system.
|
||||
@@ -474,7 +472,7 @@
|
||||
// the future we will migrate by setting this to false
|
||||
//
|
||||
// You need to rejoin a call for this setting to apply
|
||||
"experimental.legacy_audio_compatible": true,
|
||||
"experimental.legacy_audio_compatible": true
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
@@ -513,8 +511,8 @@
|
||||
// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
"horizontal": true,
|
||||
// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
"vertical": true,
|
||||
},
|
||||
"vertical": true
|
||||
}
|
||||
},
|
||||
// Minimap related settings
|
||||
"minimap": {
|
||||
@@ -562,7 +560,7 @@
|
||||
// 3. "gutter" or "none" to not highlight the current line in the minimap.
|
||||
"current_line_highlight": null,
|
||||
// Maximum number of columns to display in the minimap.
|
||||
"max_width_columns": 80,
|
||||
"max_width_columns": 80
|
||||
},
|
||||
// Enable middle-click paste on Linux.
|
||||
"middle_click_paste": true,
|
||||
@@ -585,7 +583,7 @@
|
||||
// Whether to show fold buttons in the gutter.
|
||||
"folds": true,
|
||||
// Minimum number of characters to reserve space for in the gutter.
|
||||
"min_line_number_digits": 4,
|
||||
"min_line_number_digits": 4
|
||||
},
|
||||
"indent_guides": {
|
||||
// Whether to show indent guides in the editor.
|
||||
@@ -606,7 +604,7 @@
|
||||
//
|
||||
// 1. "disabled"
|
||||
// 2. "indent_aware"
|
||||
"background_coloring": "disabled",
|
||||
"background_coloring": "disabled"
|
||||
},
|
||||
// Whether the editor will scroll beyond the last line.
|
||||
"scroll_beyond_last_line": "one_page",
|
||||
@@ -625,7 +623,7 @@
|
||||
"fast_scroll_sensitivity": 4.0,
|
||||
"sticky_scroll": {
|
||||
// Whether to stick scopes to the top of the editor.
|
||||
"enabled": false,
|
||||
"enabled": false
|
||||
},
|
||||
"relative_line_numbers": "disabled",
|
||||
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
|
||||
@@ -643,7 +641,7 @@
|
||||
// Whether to interpret the search query as a regular expression.
|
||||
"regex": false,
|
||||
// Whether to center the cursor on each search match when navigating.
|
||||
"center_on_match": false,
|
||||
"center_on_match": false
|
||||
},
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
// This setting can take the following three values:
|
||||
@@ -686,8 +684,8 @@
|
||||
"shift": false,
|
||||
"alt": false,
|
||||
"platform": false,
|
||||
"function": false,
|
||||
},
|
||||
"function": false
|
||||
}
|
||||
},
|
||||
// Whether to resize all the panels in a dock when resizing the dock.
|
||||
// Can be a combination of "left", "right" and "bottom".
|
||||
@@ -735,7 +733,7 @@
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null,
|
||||
"show": null
|
||||
},
|
||||
// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
// This setting can take the following three values:
|
||||
@@ -758,7 +756,7 @@
|
||||
// "always"
|
||||
// 2. Never show indent guides:
|
||||
// "never"
|
||||
"show": "always",
|
||||
"show": "always"
|
||||
},
|
||||
// Sort order for entries in the project panel.
|
||||
// This setting can take three values:
|
||||
@@ -783,8 +781,8 @@
|
||||
// Whether to automatically open files after pasting or duplicating them.
|
||||
"on_paste": true,
|
||||
// Whether to automatically open files dropped from external sources.
|
||||
"on_drop": true,
|
||||
},
|
||||
"on_drop": true
|
||||
}
|
||||
},
|
||||
"outline_panel": {
|
||||
// Whether to show the outline panel button in the status bar
|
||||
@@ -817,7 +815,7 @@
|
||||
// "always"
|
||||
// 2. Never show indent guides:
|
||||
// "never"
|
||||
"show": "always",
|
||||
"show": "always"
|
||||
},
|
||||
// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
@@ -834,11 +832,11 @@
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null,
|
||||
"show": null
|
||||
},
|
||||
// Default depth to expand outline items in the current file.
|
||||
// Set to 0 to collapse all items that have children, 1 or higher to collapse items at that depth or deeper.
|
||||
"expand_outlines_with_depth": 100,
|
||||
"expand_outlines_with_depth": 100
|
||||
},
|
||||
"collaboration_panel": {
|
||||
// Whether to show the collaboration panel button in the status bar.
|
||||
@@ -846,7 +844,7 @@
|
||||
// Where to dock the collaboration panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the collaboration panel.
|
||||
"default_width": 240,
|
||||
"default_width": 240
|
||||
},
|
||||
"git_panel": {
|
||||
// Whether to show the git panel button in the status bar.
|
||||
@@ -882,12 +880,12 @@
|
||||
// Choices: always, auto, never, system
|
||||
// Default: inherits editor scrollbar settings
|
||||
// "show": null
|
||||
},
|
||||
}
|
||||
},
|
||||
"message_editor": {
|
||||
// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||
// For example: typing `:wave:` gets replaced with `👋`.
|
||||
"auto_replace_emoji_shortcode": true,
|
||||
"auto_replace_emoji_shortcode": true
|
||||
},
|
||||
"notification_panel": {
|
||||
// Whether to show the notification panel button in the status bar.
|
||||
@@ -895,11 +893,9 @@
|
||||
// Where to dock the notification panel. Can be 'left' or 'right'.
|
||||
"dock": "right",
|
||||
// Default width of the notification panel.
|
||||
"default_width": 380,
|
||||
"default_width": 380
|
||||
},
|
||||
"agent": {
|
||||
// Whether the inline assistant should use streaming tools, when available
|
||||
"inline_assistant_use_streaming_tools": true,
|
||||
// Whether the agent is enabled.
|
||||
"enabled": true,
|
||||
// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
|
||||
@@ -908,8 +904,6 @@
|
||||
"button": true,
|
||||
// Where to dock the agent panel. Can be 'left', 'right' or 'bottom'.
|
||||
"dock": "right",
|
||||
// Where to dock the agents panel. Can be 'left' or 'right'.
|
||||
"agents_panel_dock": "left",
|
||||
// Default width when the agent panel is docked to the left or right.
|
||||
"default_width": 640,
|
||||
// Default height when the agent panel is docked to the bottom.
|
||||
@@ -921,7 +915,7 @@
|
||||
// The provider to use.
|
||||
"provider": "zed.dev",
|
||||
// The model to use.
|
||||
"model": "claude-sonnet-4",
|
||||
"model": "claude-sonnet-4"
|
||||
},
|
||||
// Additional parameters for language model requests. When making a request to a model, parameters will be taken
|
||||
// from the last entry in this list that matches the model's provider and name. In each entry, both provider
|
||||
@@ -976,8 +970,8 @@
|
||||
"grep": true,
|
||||
"terminal": true,
|
||||
"thinking": true,
|
||||
"web_search": true,
|
||||
},
|
||||
"web_search": true
|
||||
}
|
||||
},
|
||||
"ask": {
|
||||
"name": "Ask",
|
||||
@@ -994,14 +988,14 @@
|
||||
"open": true,
|
||||
"grep": true,
|
||||
"thinking": true,
|
||||
"web_search": true,
|
||||
},
|
||||
"web_search": true
|
||||
}
|
||||
},
|
||||
"minimal": {
|
||||
"name": "Minimal",
|
||||
"enable_all_context_servers": false,
|
||||
"tools": {},
|
||||
},
|
||||
"tools": {}
|
||||
}
|
||||
},
|
||||
// Where to show notifications when the agent has either completed
|
||||
// its response, or else needs confirmation before it can run a
|
||||
@@ -1030,7 +1024,7 @@
|
||||
// Minimum number of lines to display in the agent message editor.
|
||||
//
|
||||
// Default: 4
|
||||
"message_editor_min_lines": 4,
|
||||
"message_editor_min_lines": 4
|
||||
},
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
@@ -1065,7 +1059,7 @@
|
||||
// Whether or not to show the navigation history buttons.
|
||||
"show_nav_history_buttons": true,
|
||||
// Whether or not to show the tab bar buttons.
|
||||
"show_tab_bar_buttons": true,
|
||||
"show_tab_bar_buttons": true
|
||||
},
|
||||
// Settings related to the editor's tabs
|
||||
"tabs": {
|
||||
@@ -1104,7 +1098,7 @@
|
||||
// "errors"
|
||||
// 3. Mark files with errors and warnings:
|
||||
// "all"
|
||||
"show_diagnostics": "off",
|
||||
"show_diagnostics": "off"
|
||||
},
|
||||
// Settings related to preview tabs.
|
||||
"preview_tabs": {
|
||||
@@ -1125,7 +1119,7 @@
|
||||
"enable_preview_file_from_code_navigation": true,
|
||||
// Whether to keep tabs in preview mode when code navigation is used to navigate away from them.
|
||||
// If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.
|
||||
"enable_keep_preview_on_code_navigation": false,
|
||||
"enable_keep_preview_on_code_navigation": false
|
||||
},
|
||||
// Settings related to the file finder.
|
||||
"file_finder": {
|
||||
@@ -1169,7 +1163,7 @@
|
||||
// * "all": Use all gitignored files
|
||||
// * "indexed": Use only the files Zed had indexed
|
||||
// * "smart": Be smart and search for ignored when called from a gitignored worktree
|
||||
"include_ignored": "smart",
|
||||
"include_ignored": "smart"
|
||||
},
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
@@ -1240,7 +1234,7 @@
|
||||
// Send debug info like crash reports.
|
||||
"diagnostics": true,
|
||||
// Send anonymized usage data like what languages you're using Zed with.
|
||||
"metrics": true,
|
||||
"metrics": true
|
||||
},
|
||||
// Whether to disable all AI features in Zed.
|
||||
//
|
||||
@@ -1274,7 +1268,7 @@
|
||||
"enabled": true,
|
||||
// Minimum time to wait before pulling diagnostics from the language server(s).
|
||||
// 0 turns the debounce off.
|
||||
"debounce_ms": 50,
|
||||
"debounce_ms": 50
|
||||
},
|
||||
// Settings for inline diagnostics
|
||||
"inline": {
|
||||
@@ -1292,8 +1286,8 @@
|
||||
"min_column": 0,
|
||||
// The minimum severity of the diagnostics to show inline.
|
||||
// Inherits editor's diagnostics' max severity settings when `null`.
|
||||
"max_severity": null,
|
||||
},
|
||||
"max_severity": null
|
||||
}
|
||||
},
|
||||
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
|
||||
// scans, file searches, and not be displayed in the project file tree. Takes precedence over `file_scan_inclusions`.
|
||||
@@ -1307,7 +1301,7 @@
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db",
|
||||
"**/.classpath",
|
||||
"**/.settings",
|
||||
"**/.settings"
|
||||
],
|
||||
// Files or globs of files that will be included by Zed, even when ignored by git. This is useful
|
||||
// for files that are not tracked by git, but are still important to your project. Note that globs
|
||||
@@ -1342,14 +1336,14 @@
|
||||
// Whether or not to display the git commit summary on the same line.
|
||||
"show_commit_summary": false,
|
||||
// The minimum column number to show the inline blame information at
|
||||
"min_column": 0,
|
||||
"min_column": 0
|
||||
},
|
||||
"blame": {
|
||||
"show_avatar": true,
|
||||
"show_avatar": true
|
||||
},
|
||||
// Control which information is shown in the branch picker.
|
||||
"branch_picker": {
|
||||
"show_author_name": true,
|
||||
"show_author_name": true
|
||||
},
|
||||
// How git hunks are displayed visually in the editor.
|
||||
// This setting can take two values:
|
||||
@@ -1361,7 +1355,7 @@
|
||||
"hunk_style": "staged_hollow",
|
||||
// Should the name or path be displayed first in the git view.
|
||||
// "path_style": "file_name_first" or "file_path_first"
|
||||
"path_style": "file_name_first",
|
||||
"path_style": "file_name_first"
|
||||
},
|
||||
// The list of custom Git hosting providers.
|
||||
"git_hosting_providers": [
|
||||
@@ -1395,7 +1389,7 @@
|
||||
"**/secrets.yml",
|
||||
"**/.zed/settings.json", // zed project settings
|
||||
"/**/zed/settings.json", // zed user settings
|
||||
"/**/zed/keymap.json",
|
||||
"/**/zed/keymap.json"
|
||||
],
|
||||
// When to show edit predictions previews in buffer.
|
||||
// This setting takes two possible values:
|
||||
@@ -1413,16 +1407,15 @@
|
||||
"copilot": {
|
||||
"enterprise_uri": null,
|
||||
"proxy": null,
|
||||
"proxy_no_verify": null,
|
||||
"proxy_no_verify": null
|
||||
},
|
||||
"codestral": {
|
||||
"api_url": "https://codestral.mistral.ai",
|
||||
"model": "codestral-latest",
|
||||
"max_tokens": 150,
|
||||
"model": null,
|
||||
"max_tokens": null
|
||||
},
|
||||
// Whether edit predictions are enabled when editing text threads in the agent panel.
|
||||
// This setting has no effect if globally disabled.
|
||||
"enabled_in_text_threads": true,
|
||||
"enabled_in_text_threads": true
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
@@ -1432,7 +1425,7 @@
|
||||
// May take 2 values:
|
||||
// 1. hour12
|
||||
// 2. hour24
|
||||
"hour_format": "hour12",
|
||||
"hour_format": "hour12"
|
||||
},
|
||||
// Status bar-related settings.
|
||||
"status_bar": {
|
||||
@@ -1443,7 +1436,7 @@
|
||||
// Whether to show the cursor position button in the status bar.
|
||||
"cursor_position_button": true,
|
||||
// Whether to show active line endings button in the status bar.
|
||||
"line_endings_button": false,
|
||||
"line_endings_button": false
|
||||
},
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
@@ -1564,8 +1557,8 @@
|
||||
// Preferred Conda manager to use when activating Conda environments.
|
||||
// Values: "auto", "conda", "mamba", "micromamba"
|
||||
// Default: "auto"
|
||||
"conda_manager": "auto",
|
||||
},
|
||||
"conda_manager": "auto"
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
// Whether to display the terminal title in its toolbar's breadcrumbs.
|
||||
@@ -1573,7 +1566,7 @@
|
||||
//
|
||||
// The shell running in the terminal needs to be configured to emit the title.
|
||||
// Example: `echo -e "\e]2;New Title\007";`
|
||||
"breadcrumbs": false,
|
||||
"breadcrumbs": false
|
||||
},
|
||||
// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
@@ -1590,7 +1583,7 @@
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": null,
|
||||
"show": null
|
||||
},
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
@@ -1653,26 +1646,30 @@
|
||||
// surrounding symbols or quotes
|
||||
[
|
||||
"(?x)",
|
||||
"(?<path>",
|
||||
" (",
|
||||
" # multi-char path: first char (not opening delimiter or space)",
|
||||
" [^({\\[<\"'`\\ ]",
|
||||
" # middle chars: non-space, and colon/paren only if not followed by digit/paren",
|
||||
" ([^\\ :(]|[:(][^0-9()])*",
|
||||
" # last char: not closing delimiter or colon",
|
||||
" [^()}\\]>\"'`.,;:\\ ]",
|
||||
" |",
|
||||
" # single-char path: not delimiter, punctuation, or space",
|
||||
" [^(){}\\[\\]<>\"'`.,;:\\ ]",
|
||||
" )",
|
||||
" # optional line/column suffix (included in path for PathWithPosition::parse_str)",
|
||||
" (:+[0-9]+(:[0-9]+)?|:?\\([0-9]+([,:]?[0-9]+)?\\))?",
|
||||
")",
|
||||
],
|
||||
"# optionally starts with 0-2 opening prefix symbols",
|
||||
"[({\\[<]{0,2}",
|
||||
"# which may be followed by an opening quote",
|
||||
"(?<quote>[\"'`])?",
|
||||
"# `path` is the shortest sequence of any non-space character",
|
||||
"(?<link>(?<path>[^ ]+?",
|
||||
" # which may end with a line and optionally a column,",
|
||||
" (?<line_column>:+[0-9]+(:[0-9]+)?|:?\\([0-9]+([,:][0-9]+)?\\))?",
|
||||
"))",
|
||||
"# which must be followed by a matching quote",
|
||||
"(?(<quote>)\\k<quote>)",
|
||||
"# and optionally a single closing symbol",
|
||||
"[)}\\]>]?",
|
||||
"# if line/column matched, may be followed by a description",
|
||||
"(?(<line_column>):[^ 0-9][^ ]*)?",
|
||||
"# which may be followed by trailing punctuation",
|
||||
"[.,:)}\\]>]*",
|
||||
"# and always includes trailing whitespace or end of line",
|
||||
"([ ]+|$)"
|
||||
]
|
||||
],
|
||||
// Timeout for hover and Cmd-click path hyperlink discovery in milliseconds. Specifying a
|
||||
// timeout of `0` will disable path hyperlinking in terminal.
|
||||
"path_hyperlink_timeout_ms": 1,
|
||||
"path_hyperlink_timeout_ms": 1
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// Settings related to running tasks.
|
||||
@@ -1688,7 +1685,7 @@
|
||||
// * Zed task from history (e.g. one-off task was spawned before)
|
||||
//
|
||||
// Default: true
|
||||
"prefer_lsp": true,
|
||||
"prefer_lsp": true
|
||||
},
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
@@ -1705,7 +1702,7 @@
|
||||
"file_types": {
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json", "tsconfig*.json"],
|
||||
"Markdown": [".rules", ".cursorrules", ".windsurfrules", ".clinerules"],
|
||||
"Shell Script": [".env.*"],
|
||||
"Shell Script": [".env.*"]
|
||||
},
|
||||
// Settings for which version of Node.js and NPM to use when installing
|
||||
// language servers and Copilot.
|
||||
@@ -1721,14 +1718,14 @@
|
||||
// `path`, but not `npm_path`, Zed will assume that `npm` is located at
|
||||
// `${path}/../npm`.
|
||||
"path": null,
|
||||
"npm_path": null,
|
||||
"npm_path": null
|
||||
},
|
||||
// The extensions that Zed should automatically install on startup.
|
||||
//
|
||||
// If you don't want any of these extensions, add this field to your settings
|
||||
// and change the value to `false`.
|
||||
"auto_install_extensions": {
|
||||
"html": true,
|
||||
"html": true
|
||||
},
|
||||
// The capabilities granted to extensions.
|
||||
//
|
||||
@@ -1736,7 +1733,7 @@
|
||||
"granted_extension_capabilities": [
|
||||
{ "kind": "process:exec", "command": "*", "args": ["**"] },
|
||||
{ "kind": "download_file", "host": "*", "path": ["**"] },
|
||||
{ "kind": "npm:install", "package": "*" },
|
||||
{ "kind": "npm:install", "package": "*" }
|
||||
],
|
||||
// Controls how completions are processed for this language.
|
||||
"completions": {
|
||||
@@ -1787,7 +1784,7 @@
|
||||
// 4. "replace_suffix"
|
||||
// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
|
||||
// `"insert"` otherwise.
|
||||
"lsp_insert_mode": "replace_suffix",
|
||||
"lsp_insert_mode": "replace_suffix"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
@@ -1795,116 +1792,116 @@
|
||||
"language_servers": ["astro-language-server", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["prettier-plugin-astro"],
|
||||
},
|
||||
"plugins": ["prettier-plugin-astro"]
|
||||
}
|
||||
},
|
||||
"Blade": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"C": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false,
|
||||
"prettier": {
|
||||
"allowed": false,
|
||||
},
|
||||
"allowed": false
|
||||
}
|
||||
},
|
||||
"C++": {
|
||||
"format_on_save": "off",
|
||||
"use_on_type_format": false,
|
||||
"prettier": {
|
||||
"allowed": false,
|
||||
},
|
||||
"allowed": false
|
||||
}
|
||||
},
|
||||
"CSharp": {
|
||||
"language_servers": ["roslyn", "!omnisharp", "..."],
|
||||
"language_servers": ["roslyn", "!omnisharp", "..."]
|
||||
},
|
||||
"CSS": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"Dart": {
|
||||
"tab_size": 2,
|
||||
"tab_size": 2
|
||||
},
|
||||
"Diff": {
|
||||
"show_edit_predictions": false,
|
||||
"remove_trailing_whitespace_on_save": false,
|
||||
"ensure_final_newline_on_save": false,
|
||||
"ensure_final_newline_on_save": false
|
||||
},
|
||||
"Elixir": {
|
||||
"language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "..."],
|
||||
"language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "..."]
|
||||
},
|
||||
"Elm": {
|
||||
"tab_size": 4,
|
||||
"tab_size": 4
|
||||
},
|
||||
"Erlang": {
|
||||
"language_servers": ["erlang-ls", "!elp", "..."],
|
||||
"language_servers": ["erlang-ls", "!elp", "..."]
|
||||
},
|
||||
"Git Commit": {
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width",
|
||||
"preferred_line_length": 72,
|
||||
"preferred_line_length": 72
|
||||
},
|
||||
"Go": {
|
||||
"hard_tabs": true,
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true,
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"debuggers": ["Delve"],
|
||||
"debuggers": ["Delve"]
|
||||
},
|
||||
"GraphQL": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"HEEX": {
|
||||
"language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "..."],
|
||||
"language_servers": ["elixir-ls", "!expert", "!next-ls", "!lexical", "..."]
|
||||
},
|
||||
"HTML": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"HTML+ERB": {
|
||||
"language_servers": ["herb", "!ruby-lsp", "..."],
|
||||
"language_servers": ["herb", "!ruby-lsp", "..."]
|
||||
},
|
||||
"Java": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["prettier-plugin-java"],
|
||||
},
|
||||
"plugins": ["prettier-plugin-java"]
|
||||
}
|
||||
},
|
||||
"JavaScript": {
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"JSON": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"JSONC": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"JS+ERB": {
|
||||
"language_servers": ["!ruby-lsp", "..."],
|
||||
"language_servers": ["!ruby-lsp", "..."]
|
||||
},
|
||||
"Kotlin": {
|
||||
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."],
|
||||
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
|
||||
},
|
||||
"LaTeX": {
|
||||
"formatter": "language_server",
|
||||
"language_servers": ["texlab", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["prettier-plugin-latex"],
|
||||
},
|
||||
"plugins": ["prettier-plugin-latex"]
|
||||
}
|
||||
},
|
||||
"Markdown": {
|
||||
"format_on_save": "off",
|
||||
@@ -1912,145 +1909,136 @@
|
||||
"remove_trailing_whitespace_on_save": false,
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width",
|
||||
"completions": {
|
||||
"words": "disabled",
|
||||
},
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"PHP": {
|
||||
"language_servers": ["phpactor", "!intelephense", "!phptools", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["@prettier/plugin-php"],
|
||||
"parser": "php",
|
||||
},
|
||||
"parser": "php"
|
||||
}
|
||||
},
|
||||
"Plain Text": {
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width",
|
||||
"completions": {
|
||||
"words": "disabled",
|
||||
},
|
||||
},
|
||||
"Proto": {
|
||||
"language_servers": ["buf", "!protols", "!protobuf-language-server", "..."],
|
||||
"soft_wrap": "editor_width"
|
||||
},
|
||||
"Python": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports.ruff": true,
|
||||
"source.organizeImports.ruff": true
|
||||
},
|
||||
"formatter": {
|
||||
"language_server": {
|
||||
"name": "ruff",
|
||||
},
|
||||
"name": "ruff"
|
||||
}
|
||||
},
|
||||
"debuggers": ["Debugpy"],
|
||||
"language_servers": ["basedpyright", "ruff", "!ty", "!pyrefly", "!pyright", "!pylsp", "..."],
|
||||
"language_servers": ["basedpyright", "ruff", "!ty", "!pyrefly", "!pyright", "!pylsp", "..."]
|
||||
},
|
||||
"Ruby": {
|
||||
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."],
|
||||
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
|
||||
},
|
||||
"Rust": {
|
||||
"debuggers": ["CodeLLDB"],
|
||||
"debuggers": ["CodeLLDB"]
|
||||
},
|
||||
"SCSS": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"Starlark": {
|
||||
"language_servers": ["starpls", "!buck2-lsp", "..."],
|
||||
"language_servers": ["starpls", "!buck2-lsp", "..."]
|
||||
},
|
||||
"Svelte": {
|
||||
"language_servers": ["svelte-language-server", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
},
|
||||
"plugins": ["prettier-plugin-svelte"]
|
||||
}
|
||||
},
|
||||
"TSX": {
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"Twig": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"TypeScript": {
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"SystemVerilog": {
|
||||
"format_on_save": "off",
|
||||
"language_servers": ["!slang", "..."],
|
||||
"use_on_type_format": false,
|
||||
"use_on_type_format": false
|
||||
},
|
||||
"Vue.js": {
|
||||
"language_servers": ["vue-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"XML": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["@prettier/plugin-xml"],
|
||||
},
|
||||
"plugins": ["@prettier/plugin-xml"]
|
||||
}
|
||||
},
|
||||
"YAML": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
},
|
||||
"YAML+ERB": {
|
||||
"language_servers": ["!ruby-lsp", "..."],
|
||||
"language_servers": ["!ruby-lsp", "..."]
|
||||
},
|
||||
"Zig": {
|
||||
"language_servers": ["zls", "..."],
|
||||
},
|
||||
"language_servers": ["zls", "..."]
|
||||
}
|
||||
},
|
||||
// Different settings for specific language models.
|
||||
"language_models": {
|
||||
"anthropic": {
|
||||
"api_url": "https://api.anthropic.com",
|
||||
"api_url": "https://api.anthropic.com"
|
||||
},
|
||||
"bedrock": {},
|
||||
"google": {
|
||||
"api_url": "https://generativelanguage.googleapis.com",
|
||||
"api_url": "https://generativelanguage.googleapis.com"
|
||||
},
|
||||
"ollama": {
|
||||
"api_url": "http://localhost:11434",
|
||||
"api_url": "http://localhost:11434"
|
||||
},
|
||||
"openai": {
|
||||
"api_url": "https://api.openai.com/v1",
|
||||
"api_url": "https://api.openai.com/v1"
|
||||
},
|
||||
"openai_compatible": {},
|
||||
"open_router": {
|
||||
"api_url": "https://openrouter.ai/api/v1",
|
||||
"api_url": "https://openrouter.ai/api/v1"
|
||||
},
|
||||
"lmstudio": {
|
||||
"api_url": "http://localhost:1234/api/v0",
|
||||
"api_url": "http://localhost:1234/api/v0"
|
||||
},
|
||||
"deepseek": {
|
||||
"api_url": "https://api.deepseek.com/v1",
|
||||
"api_url": "https://api.deepseek.com/v1"
|
||||
},
|
||||
"mistral": {
|
||||
"api_url": "https://api.mistral.ai/v1",
|
||||
"api_url": "https://api.mistral.ai/v1"
|
||||
},
|
||||
"vercel": {
|
||||
"api_url": "https://api.v0.dev/v1",
|
||||
"api_url": "https://api.v0.dev/v1"
|
||||
},
|
||||
"x_ai": {
|
||||
"api_url": "https://api.x.ai/v1",
|
||||
"api_url": "https://api.x.ai/v1"
|
||||
},
|
||||
"zed.dev": {},
|
||||
"zed.dev": {}
|
||||
},
|
||||
"session": {
|
||||
// Whether or not to restore unsaved buffers on restart.
|
||||
@@ -2059,7 +2047,7 @@
|
||||
// dirty files when closing the application.
|
||||
//
|
||||
// Default: true
|
||||
"restore_unsaved_buffers": true,
|
||||
"restore_unsaved_buffers": true
|
||||
},
|
||||
// Zed's Prettier integration settings.
|
||||
// Allows to enable/disable formatting with Prettier
|
||||
@@ -2077,11 +2065,11 @@
|
||||
// "singleQuote": true
|
||||
// Forces Prettier integration to use a specific parser name when formatting files with the language
|
||||
// when set to a non-empty string.
|
||||
"parser": "",
|
||||
"parser": ""
|
||||
},
|
||||
// Settings for auto-closing of JSX tags.
|
||||
"jsx_tag_auto_close": {
|
||||
"enabled": true,
|
||||
"enabled": true
|
||||
},
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
@@ -2102,19 +2090,19 @@
|
||||
// Specify the DAP name as a key here.
|
||||
"CodeLLDB": {
|
||||
"env": {
|
||||
"RUST_LOG": "info",
|
||||
},
|
||||
},
|
||||
"RUST_LOG": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
// Common language server settings.
|
||||
"global_lsp_settings": {
|
||||
// Whether to show the LSP servers button in the status bar.
|
||||
"button": true,
|
||||
"button": true
|
||||
},
|
||||
// Jupyter settings
|
||||
"jupyter": {
|
||||
"enabled": true,
|
||||
"kernel_selections": {},
|
||||
"kernel_selections": {}
|
||||
// Specify the language name as the key and the kernel name as the value.
|
||||
// "kernel_selections": {
|
||||
// "python": "conda-base"
|
||||
@@ -2128,7 +2116,7 @@
|
||||
"max_columns": 128,
|
||||
// Maximum number of lines to keep in REPL's scrollback buffer.
|
||||
// Clamped with [4, 256] range.
|
||||
"max_lines": 32,
|
||||
"max_lines": 32
|
||||
},
|
||||
// Vim settings
|
||||
"vim": {
|
||||
@@ -2142,7 +2130,7 @@
|
||||
// Specify the mode as the key and the shape as the value.
|
||||
// The mode can be one of the following: "normal", "replace", "insert", "visual".
|
||||
// The shape can be one of the following: "block", "bar", "underline", "hollow".
|
||||
"cursor_shape": {},
|
||||
"cursor_shape": {}
|
||||
},
|
||||
// The server to connect to. If the environment variable
|
||||
// ZED_SERVER_URL is set, it will override this setting.
|
||||
@@ -2175,9 +2163,9 @@
|
||||
"windows": {
|
||||
"languages": {
|
||||
"PHP": {
|
||||
"language_servers": ["intelephense", "!phpactor", "!phptools", "..."],
|
||||
},
|
||||
},
|
||||
"language_servers": ["intelephense", "!phpactor", "!phptools", "..."]
|
||||
}
|
||||
}
|
||||
},
|
||||
// Whether to show full labels in line indicator or short ones
|
||||
//
|
||||
@@ -2236,7 +2224,7 @@
|
||||
"dock": "bottom",
|
||||
"log_dap_communications": true,
|
||||
"format_dap_log_messages": true,
|
||||
"button": true,
|
||||
"button": true
|
||||
},
|
||||
// Configures any number of settings profiles that are temporarily applied on
|
||||
// top of your existing user settings when selected from
|
||||
@@ -2263,5 +2251,5 @@
|
||||
// Useful for filtering out noisy logs or enabling more verbose logging.
|
||||
//
|
||||
// Example: {"log": {"client": "warn"}}
|
||||
"log": {},
|
||||
"log": {}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"adapter": "Debugpy",
|
||||
"program": "$ZED_FILE",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
"cwd": "$ZED_WORKTREE_ROOT"
|
||||
},
|
||||
{
|
||||
"label": "Debug active JavaScript file",
|
||||
@@ -16,7 +16,7 @@
|
||||
"program": "$ZED_FILE",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
"type": "pwa-node",
|
||||
"type": "pwa-node"
|
||||
},
|
||||
{
|
||||
"label": "JavaScript debug terminal",
|
||||
@@ -24,6 +24,6 @@
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
"console": "integratedTerminal",
|
||||
"type": "pwa-node",
|
||||
},
|
||||
"type": "pwa-node"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
// For a full list of overridable settings, and general information on settings,
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||
{
|
||||
"lsp": {},
|
||||
"lsp": {}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
// Whether to show the task line in the output of the spawned task, defaults to `true`.
|
||||
"show_summary": true,
|
||||
// Whether to show the command line in the output of the spawned task, defaults to `true`.
|
||||
"show_command": true,
|
||||
"show_command": true
|
||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||
// "tags": []
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark",
|
||||
},
|
||||
"dark": "One Dark"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,33 +71,33 @@
|
||||
"editor.document_highlight.read_background": "#83a5981a",
|
||||
"editor.document_highlight.write_background": "#92847466",
|
||||
"terminal.background": "#282828ff",
|
||||
"terminal.foreground": "#ebdbb2ff",
|
||||
"terminal.foreground": "#fbf1c7ff",
|
||||
"terminal.bright_foreground": "#fbf1c7ff",
|
||||
"terminal.dim_foreground": "#766b5dff",
|
||||
"terminal.dim_foreground": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#928374ff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#fbf1c7ff",
|
||||
"terminal.ansi.red": "#cc241dff",
|
||||
"terminal.ansi.bright_red": "#fb4934ff",
|
||||
"terminal.ansi.dim_red": "#8e1814ff",
|
||||
"terminal.ansi.green": "#98971aff",
|
||||
"terminal.ansi.bright_green": "#b8bb26ff",
|
||||
"terminal.ansi.dim_green": "#6a6912ff",
|
||||
"terminal.ansi.yellow": "#d79921ff",
|
||||
"terminal.ansi.bright_yellow": "#fabd2fff",
|
||||
"terminal.ansi.dim_yellow": "#966a17ff",
|
||||
"terminal.ansi.blue": "#458588ff",
|
||||
"terminal.ansi.bright_blue": "#83a598ff",
|
||||
"terminal.ansi.dim_blue": "#305d5fff",
|
||||
"terminal.ansi.magenta": "#b16286ff",
|
||||
"terminal.ansi.bright_magenta": "#d3869bff",
|
||||
"terminal.ansi.dim_magenta": "#7c455eff",
|
||||
"terminal.ansi.cyan": "#689d6aff",
|
||||
"terminal.ansi.bright_cyan": "#8ec07cff",
|
||||
"terminal.ansi.dim_cyan": "#496e4aff",
|
||||
"terminal.ansi.white": "#a89984ff",
|
||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||
"terminal.ansi.dim_white": "#766b5dff",
|
||||
"terminal.ansi.red": "#fb4a35ff",
|
||||
"terminal.ansi.bright_red": "#93201dff",
|
||||
"terminal.ansi.dim_red": "#ffaa95ff",
|
||||
"terminal.ansi.green": "#b7bb26ff",
|
||||
"terminal.ansi.bright_green": "#605c1bff",
|
||||
"terminal.ansi.dim_green": "#e0dc98ff",
|
||||
"terminal.ansi.yellow": "#f9bd2fff",
|
||||
"terminal.ansi.bright_yellow": "#91611bff",
|
||||
"terminal.ansi.dim_yellow": "#fedc9bff",
|
||||
"terminal.ansi.blue": "#83a598ff",
|
||||
"terminal.ansi.bright_blue": "#414f4aff",
|
||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||
"terminal.ansi.magenta": "#d3869bff",
|
||||
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||
"terminal.ansi.cyan": "#8ec07cff",
|
||||
"terminal.ansi.bright_cyan": "#45603eff",
|
||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||
"terminal.ansi.white": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#83a598ff",
|
||||
"version_control.added": "#b7bb26ff",
|
||||
"version_control.modified": "#f9bd2fff",
|
||||
@@ -478,33 +478,33 @@
|
||||
"editor.document_highlight.read_background": "#83a5981a",
|
||||
"editor.document_highlight.write_background": "#92847466",
|
||||
"terminal.background": "#1d2021ff",
|
||||
"terminal.foreground": "#ebdbb2ff",
|
||||
"terminal.foreground": "#fbf1c7ff",
|
||||
"terminal.bright_foreground": "#fbf1c7ff",
|
||||
"terminal.dim_foreground": "#766b5dff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#928374ff",
|
||||
"terminal.dim_foreground": "#1d2021ff",
|
||||
"terminal.ansi.black": "#1d2021ff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#fbf1c7ff",
|
||||
"terminal.ansi.red": "#cc241dff",
|
||||
"terminal.ansi.bright_red": "#fb4934ff",
|
||||
"terminal.ansi.dim_red": "#8e1814ff",
|
||||
"terminal.ansi.green": "#98971aff",
|
||||
"terminal.ansi.bright_green": "#b8bb26ff",
|
||||
"terminal.ansi.dim_green": "#6a6912ff",
|
||||
"terminal.ansi.yellow": "#d79921ff",
|
||||
"terminal.ansi.bright_yellow": "#fabd2fff",
|
||||
"terminal.ansi.dim_yellow": "#966a17ff",
|
||||
"terminal.ansi.blue": "#458588ff",
|
||||
"terminal.ansi.bright_blue": "#83a598ff",
|
||||
"terminal.ansi.dim_blue": "#305d5fff",
|
||||
"terminal.ansi.magenta": "#b16286ff",
|
||||
"terminal.ansi.bright_magenta": "#d3869bff",
|
||||
"terminal.ansi.dim_magenta": "#7c455eff",
|
||||
"terminal.ansi.cyan": "#689d6aff",
|
||||
"terminal.ansi.bright_cyan": "#8ec07cff",
|
||||
"terminal.ansi.dim_cyan": "#496e4aff",
|
||||
"terminal.ansi.white": "#a89984ff",
|
||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||
"terminal.ansi.dim_white": "#766b5dff",
|
||||
"terminal.ansi.red": "#fb4a35ff",
|
||||
"terminal.ansi.bright_red": "#93201dff",
|
||||
"terminal.ansi.dim_red": "#ffaa95ff",
|
||||
"terminal.ansi.green": "#b7bb26ff",
|
||||
"terminal.ansi.bright_green": "#605c1bff",
|
||||
"terminal.ansi.dim_green": "#e0dc98ff",
|
||||
"terminal.ansi.yellow": "#f9bd2fff",
|
||||
"terminal.ansi.bright_yellow": "#91611bff",
|
||||
"terminal.ansi.dim_yellow": "#fedc9bff",
|
||||
"terminal.ansi.blue": "#83a598ff",
|
||||
"terminal.ansi.bright_blue": "#414f4aff",
|
||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||
"terminal.ansi.magenta": "#d3869bff",
|
||||
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||
"terminal.ansi.cyan": "#8ec07cff",
|
||||
"terminal.ansi.bright_cyan": "#45603eff",
|
||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||
"terminal.ansi.white": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#83a598ff",
|
||||
"version_control.added": "#b7bb26ff",
|
||||
"version_control.modified": "#f9bd2fff",
|
||||
@@ -885,33 +885,33 @@
|
||||
"editor.document_highlight.read_background": "#83a5981a",
|
||||
"editor.document_highlight.write_background": "#92847466",
|
||||
"terminal.background": "#32302fff",
|
||||
"terminal.foreground": "#ebdbb2ff",
|
||||
"terminal.foreground": "#fbf1c7ff",
|
||||
"terminal.bright_foreground": "#fbf1c7ff",
|
||||
"terminal.dim_foreground": "#766b5dff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#928374ff",
|
||||
"terminal.dim_foreground": "#32302fff",
|
||||
"terminal.ansi.black": "#32302fff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#fbf1c7ff",
|
||||
"terminal.ansi.red": "#cc241dff",
|
||||
"terminal.ansi.bright_red": "#fb4934ff",
|
||||
"terminal.ansi.dim_red": "#8e1814ff",
|
||||
"terminal.ansi.green": "#98971aff",
|
||||
"terminal.ansi.bright_green": "#b8bb26ff",
|
||||
"terminal.ansi.dim_green": "#6a6912ff",
|
||||
"terminal.ansi.yellow": "#d79921ff",
|
||||
"terminal.ansi.bright_yellow": "#fabd2fff",
|
||||
"terminal.ansi.dim_yellow": "#966a17ff",
|
||||
"terminal.ansi.blue": "#458588ff",
|
||||
"terminal.ansi.bright_blue": "#83a598ff",
|
||||
"terminal.ansi.dim_blue": "#305d5fff",
|
||||
"terminal.ansi.magenta": "#b16286ff",
|
||||
"terminal.ansi.bright_magenta": "#d3869bff",
|
||||
"terminal.ansi.dim_magenta": "#7c455eff",
|
||||
"terminal.ansi.cyan": "#689d6aff",
|
||||
"terminal.ansi.bright_cyan": "#8ec07cff",
|
||||
"terminal.ansi.dim_cyan": "#496e4aff",
|
||||
"terminal.ansi.white": "#a89984ff",
|
||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
||||
"terminal.ansi.dim_white": "#766b5dff",
|
||||
"terminal.ansi.red": "#fb4a35ff",
|
||||
"terminal.ansi.bright_red": "#93201dff",
|
||||
"terminal.ansi.dim_red": "#ffaa95ff",
|
||||
"terminal.ansi.green": "#b7bb26ff",
|
||||
"terminal.ansi.bright_green": "#605c1bff",
|
||||
"terminal.ansi.dim_green": "#e0dc98ff",
|
||||
"terminal.ansi.yellow": "#f9bd2fff",
|
||||
"terminal.ansi.bright_yellow": "#91611bff",
|
||||
"terminal.ansi.dim_yellow": "#fedc9bff",
|
||||
"terminal.ansi.blue": "#83a598ff",
|
||||
"terminal.ansi.bright_blue": "#414f4aff",
|
||||
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||
"terminal.ansi.magenta": "#d3869bff",
|
||||
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||
"terminal.ansi.cyan": "#8ec07cff",
|
||||
"terminal.ansi.bright_cyan": "#45603eff",
|
||||
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||
"terminal.ansi.white": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#83a598ff",
|
||||
"version_control.added": "#b7bb26ff",
|
||||
"version_control.modified": "#f9bd2fff",
|
||||
@@ -1295,30 +1295,30 @@
|
||||
"terminal.foreground": "#282828ff",
|
||||
"terminal.bright_foreground": "#282828ff",
|
||||
"terminal.dim_foreground": "#fbf1c7ff",
|
||||
"terminal.ansi.black": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_black": "#928374ff",
|
||||
"terminal.ansi.dim_black": "#7c6f64ff",
|
||||
"terminal.ansi.red": "#cc241dff",
|
||||
"terminal.ansi.bright_red": "#9d0006ff",
|
||||
"terminal.ansi.dim_red": "#c31c16ff",
|
||||
"terminal.ansi.green": "#98971aff",
|
||||
"terminal.ansi.bright_green": "#79740eff",
|
||||
"terminal.ansi.dim_green": "#929015ff",
|
||||
"terminal.ansi.yellow": "#d79921ff",
|
||||
"terminal.ansi.bright_yellow": "#b57614ff",
|
||||
"terminal.ansi.dim_yellow": "#cf8e1aff",
|
||||
"terminal.ansi.blue": "#458588ff",
|
||||
"terminal.ansi.bright_blue": "#076678ff",
|
||||
"terminal.ansi.dim_blue": "#356f77ff",
|
||||
"terminal.ansi.magenta": "#b16286ff",
|
||||
"terminal.ansi.bright_magenta": "#8f3f71ff",
|
||||
"terminal.ansi.dim_magenta": "#a85580ff",
|
||||
"terminal.ansi.cyan": "#689d6aff",
|
||||
"terminal.ansi.bright_cyan": "#427b58ff",
|
||||
"terminal.ansi.dim_cyan": "#5f9166ff",
|
||||
"terminal.ansi.white": "#7c6f64ff",
|
||||
"terminal.ansi.bright_white": "#282828ff",
|
||||
"terminal.ansi.dim_white": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#0b6678ff",
|
||||
"terminal.ansi.dim_black": "#5f5650ff",
|
||||
"terminal.ansi.red": "#9d0308ff",
|
||||
"terminal.ansi.bright_red": "#db8b7aff",
|
||||
"terminal.ansi.dim_red": "#4e1207ff",
|
||||
"terminal.ansi.green": "#797410ff",
|
||||
"terminal.ansi.bright_green": "#bfb787ff",
|
||||
"terminal.ansi.dim_green": "#3e3a11ff",
|
||||
"terminal.ansi.yellow": "#b57615ff",
|
||||
"terminal.ansi.bright_yellow": "#e2b88bff",
|
||||
"terminal.ansi.dim_yellow": "#5c3a12ff",
|
||||
"terminal.ansi.blue": "#0b6678ff",
|
||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||
"terminal.ansi.dim_blue": "#14333bff",
|
||||
"terminal.ansi.magenta": "#8f3e71ff",
|
||||
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
"terminal.ansi.white": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#0b6678ff",
|
||||
"version_control.added": "#797410ff",
|
||||
"version_control.modified": "#b57615ff",
|
||||
@@ -1702,30 +1702,30 @@
|
||||
"terminal.foreground": "#282828ff",
|
||||
"terminal.bright_foreground": "#282828ff",
|
||||
"terminal.dim_foreground": "#f9f5d7ff",
|
||||
"terminal.ansi.black": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_black": "#928374ff",
|
||||
"terminal.ansi.dim_black": "#7c6f64ff",
|
||||
"terminal.ansi.red": "#cc241dff",
|
||||
"terminal.ansi.bright_red": "#9d0006ff",
|
||||
"terminal.ansi.dim_red": "#c31c16ff",
|
||||
"terminal.ansi.green": "#98971aff",
|
||||
"terminal.ansi.bright_green": "#79740eff",
|
||||
"terminal.ansi.dim_green": "#929015ff",
|
||||
"terminal.ansi.yellow": "#d79921ff",
|
||||
"terminal.ansi.bright_yellow": "#b57614ff",
|
||||
"terminal.ansi.dim_yellow": "#cf8e1aff",
|
||||
"terminal.ansi.blue": "#458588ff",
|
||||
"terminal.ansi.bright_blue": "#076678ff",
|
||||
"terminal.ansi.dim_blue": "#356f77ff",
|
||||
"terminal.ansi.magenta": "#b16286ff",
|
||||
"terminal.ansi.bright_magenta": "#8f3f71ff",
|
||||
"terminal.ansi.dim_magenta": "#a85580ff",
|
||||
"terminal.ansi.cyan": "#689d6aff",
|
||||
"terminal.ansi.bright_cyan": "#427b58ff",
|
||||
"terminal.ansi.dim_cyan": "#5f9166ff",
|
||||
"terminal.ansi.white": "#7c6f64ff",
|
||||
"terminal.ansi.bright_white": "#282828ff",
|
||||
"terminal.ansi.dim_white": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#f9f5d7ff",
|
||||
"terminal.ansi.red": "#9d0308ff",
|
||||
"terminal.ansi.bright_red": "#db8b7aff",
|
||||
"terminal.ansi.dim_red": "#4e1207ff",
|
||||
"terminal.ansi.green": "#797410ff",
|
||||
"terminal.ansi.bright_green": "#bfb787ff",
|
||||
"terminal.ansi.dim_green": "#3e3a11ff",
|
||||
"terminal.ansi.yellow": "#b57615ff",
|
||||
"terminal.ansi.bright_yellow": "#e2b88bff",
|
||||
"terminal.ansi.dim_yellow": "#5c3a12ff",
|
||||
"terminal.ansi.blue": "#0b6678ff",
|
||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||
"terminal.ansi.dim_blue": "#14333bff",
|
||||
"terminal.ansi.magenta": "#8f3e71ff",
|
||||
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
"terminal.ansi.white": "#f9f5d7ff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#0b6678ff",
|
||||
"version_control.added": "#797410ff",
|
||||
"version_control.modified": "#b57615ff",
|
||||
@@ -2109,30 +2109,30 @@
|
||||
"terminal.foreground": "#282828ff",
|
||||
"terminal.bright_foreground": "#282828ff",
|
||||
"terminal.dim_foreground": "#f2e5bcff",
|
||||
"terminal.ansi.black": "#fbf1c7ff",
|
||||
"terminal.ansi.bright_black": "#928374ff",
|
||||
"terminal.ansi.dim_black": "#7c6f64ff",
|
||||
"terminal.ansi.red": "#cc241dff",
|
||||
"terminal.ansi.bright_red": "#9d0006ff",
|
||||
"terminal.ansi.dim_red": "#c31c16ff",
|
||||
"terminal.ansi.green": "#98971aff",
|
||||
"terminal.ansi.bright_green": "#79740eff",
|
||||
"terminal.ansi.dim_green": "#929015ff",
|
||||
"terminal.ansi.yellow": "#d79921ff",
|
||||
"terminal.ansi.bright_yellow": "#b57614ff",
|
||||
"terminal.ansi.dim_yellow": "#cf8e1aff",
|
||||
"terminal.ansi.blue": "#458588ff",
|
||||
"terminal.ansi.bright_blue": "#076678ff",
|
||||
"terminal.ansi.dim_blue": "#356f77ff",
|
||||
"terminal.ansi.magenta": "#b16286ff",
|
||||
"terminal.ansi.bright_magenta": "#8f3f71ff",
|
||||
"terminal.ansi.dim_magenta": "#a85580ff",
|
||||
"terminal.ansi.cyan": "#689d6aff",
|
||||
"terminal.ansi.bright_cyan": "#427b58ff",
|
||||
"terminal.ansi.dim_cyan": "#5f9166ff",
|
||||
"terminal.ansi.white": "#7c6f64ff",
|
||||
"terminal.ansi.bright_white": "#282828ff",
|
||||
"terminal.ansi.dim_white": "#282828ff",
|
||||
"terminal.ansi.black": "#282828ff",
|
||||
"terminal.ansi.bright_black": "#73675eff",
|
||||
"terminal.ansi.dim_black": "#f2e5bcff",
|
||||
"terminal.ansi.red": "#9d0308ff",
|
||||
"terminal.ansi.bright_red": "#db8b7aff",
|
||||
"terminal.ansi.dim_red": "#4e1207ff",
|
||||
"terminal.ansi.green": "#797410ff",
|
||||
"terminal.ansi.bright_green": "#bfb787ff",
|
||||
"terminal.ansi.dim_green": "#3e3a11ff",
|
||||
"terminal.ansi.yellow": "#b57615ff",
|
||||
"terminal.ansi.bright_yellow": "#e2b88bff",
|
||||
"terminal.ansi.dim_yellow": "#5c3a12ff",
|
||||
"terminal.ansi.blue": "#0b6678ff",
|
||||
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||
"terminal.ansi.dim_blue": "#14333bff",
|
||||
"terminal.ansi.magenta": "#8f3e71ff",
|
||||
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||
"terminal.ansi.cyan": "#437b59ff",
|
||||
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||
"terminal.ansi.white": "#f2e5bcff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#b0a189ff",
|
||||
"link_text.hover": "#0b6678ff",
|
||||
"version_control.added": "#797410ff",
|
||||
"version_control.modified": "#b57615ff",
|
||||
|
||||
@@ -68,34 +68,34 @@
|
||||
"editor.active_wrap_guide": "#c8ccd41a",
|
||||
"editor.document_highlight.read_background": "#74ade81a",
|
||||
"editor.document_highlight.write_background": "#555a6366",
|
||||
"terminal.background": "#282c34ff",
|
||||
"terminal.foreground": "#abb2bfff",
|
||||
"terminal.background": "#282c33ff",
|
||||
"terminal.foreground": "#dce0e5ff",
|
||||
"terminal.bright_foreground": "#dce0e5ff",
|
||||
"terminal.dim_foreground": "#636d83ff",
|
||||
"terminal.ansi.black": "#282c34ff",
|
||||
"terminal.ansi.bright_black": "#636d83ff",
|
||||
"terminal.ansi.dim_black": "#3b3f4aff",
|
||||
"terminal.ansi.red": "#e06c75ff",
|
||||
"terminal.ansi.bright_red": "#EA858Bff",
|
||||
"terminal.ansi.dim_red": "#a7545aff",
|
||||
"terminal.ansi.green": "#98c379ff",
|
||||
"terminal.ansi.bright_green": "#AAD581ff",
|
||||
"terminal.ansi.dim_green": "#6d8f59ff",
|
||||
"terminal.ansi.yellow": "#e5c07bff",
|
||||
"terminal.ansi.bright_yellow": "#FFD885ff",
|
||||
"terminal.ansi.dim_yellow": "#b8985bff",
|
||||
"terminal.ansi.blue": "#61afefff",
|
||||
"terminal.ansi.bright_blue": "#85C1FFff",
|
||||
"terminal.ansi.dim_blue": "#457cadff",
|
||||
"terminal.ansi.magenta": "#c678ddff",
|
||||
"terminal.ansi.bright_magenta": "#D398EBff",
|
||||
"terminal.ansi.dim_magenta": "#8d54a0ff",
|
||||
"terminal.ansi.cyan": "#56b6c2ff",
|
||||
"terminal.ansi.bright_cyan": "#6ED5DEff",
|
||||
"terminal.ansi.dim_cyan": "#3c818aff",
|
||||
"terminal.ansi.white": "#abb2bfff",
|
||||
"terminal.dim_foreground": "#282c33ff",
|
||||
"terminal.ansi.black": "#282c33ff",
|
||||
"terminal.ansi.bright_black": "#525561ff",
|
||||
"terminal.ansi.dim_black": "#dce0e5ff",
|
||||
"terminal.ansi.red": "#d07277ff",
|
||||
"terminal.ansi.bright_red": "#673a3cff",
|
||||
"terminal.ansi.dim_red": "#eab7b9ff",
|
||||
"terminal.ansi.green": "#a1c181ff",
|
||||
"terminal.ansi.bright_green": "#4d6140ff",
|
||||
"terminal.ansi.dim_green": "#d1e0bfff",
|
||||
"terminal.ansi.yellow": "#dec184ff",
|
||||
"terminal.ansi.bright_yellow": "#e5c07bff",
|
||||
"terminal.ansi.dim_yellow": "#f1dfc1ff",
|
||||
"terminal.ansi.blue": "#74ade8ff",
|
||||
"terminal.ansi.bright_blue": "#385378ff",
|
||||
"terminal.ansi.dim_blue": "#bed5f4ff",
|
||||
"terminal.ansi.magenta": "#b477cfff",
|
||||
"terminal.ansi.bright_magenta": "#d6b4e4ff",
|
||||
"terminal.ansi.dim_magenta": "#612a79ff",
|
||||
"terminal.ansi.cyan": "#6eb4bfff",
|
||||
"terminal.ansi.bright_cyan": "#3a565bff",
|
||||
"terminal.ansi.dim_cyan": "#b9d9dfff",
|
||||
"terminal.ansi.white": "#dce0e5ff",
|
||||
"terminal.ansi.bright_white": "#fafafaff",
|
||||
"terminal.ansi.dim_white": "#8f969bff",
|
||||
"terminal.ansi.dim_white": "#575d65ff",
|
||||
"link_text.hover": "#74ade8ff",
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
@@ -473,33 +473,33 @@
|
||||
"editor.document_highlight.read_background": "#5c78e225",
|
||||
"editor.document_highlight.write_background": "#a3a3a466",
|
||||
"terminal.background": "#fafafaff",
|
||||
"terminal.foreground": "#2a2c33ff",
|
||||
"terminal.bright_foreground": "#2a2c33ff",
|
||||
"terminal.dim_foreground": "#bbbbbbff",
|
||||
"terminal.ansi.black": "#000000ff",
|
||||
"terminal.ansi.bright_black": "#000000ff",
|
||||
"terminal.ansi.dim_black": "#555555ff",
|
||||
"terminal.ansi.red": "#de3e35ff",
|
||||
"terminal.ansi.bright_red": "#de3e35ff",
|
||||
"terminal.ansi.dim_red": "#9c2b26ff",
|
||||
"terminal.ansi.green": "#3f953aff",
|
||||
"terminal.ansi.bright_green": "#3f953aff",
|
||||
"terminal.ansi.dim_green": "#2b6927ff",
|
||||
"terminal.ansi.yellow": "#d2b67cff",
|
||||
"terminal.ansi.bright_yellow": "#d2b67cff",
|
||||
"terminal.ansi.dim_yellow": "#a48c5aff",
|
||||
"terminal.ansi.blue": "#2f5af3ff",
|
||||
"terminal.ansi.bright_blue": "#2f5af3ff",
|
||||
"terminal.ansi.dim_blue": "#2140abff",
|
||||
"terminal.ansi.magenta": "#950095ff",
|
||||
"terminal.ansi.bright_magenta": "#a00095ff",
|
||||
"terminal.ansi.dim_magenta": "#6a006aff",
|
||||
"terminal.ansi.cyan": "#3f953aff",
|
||||
"terminal.ansi.bright_cyan": "#3f953aff",
|
||||
"terminal.ansi.dim_cyan": "#2b6927ff",
|
||||
"terminal.ansi.white": "#bbbbbbff",
|
||||
"terminal.foreground": "#242529ff",
|
||||
"terminal.bright_foreground": "#242529ff",
|
||||
"terminal.dim_foreground": "#fafafaff",
|
||||
"terminal.ansi.black": "#242529ff",
|
||||
"terminal.ansi.bright_black": "#747579ff",
|
||||
"terminal.ansi.dim_black": "#97979aff",
|
||||
"terminal.ansi.red": "#d36151ff",
|
||||
"terminal.ansi.bright_red": "#f0b0a4ff",
|
||||
"terminal.ansi.dim_red": "#6f312aff",
|
||||
"terminal.ansi.green": "#669f59ff",
|
||||
"terminal.ansi.bright_green": "#b2cfa9ff",
|
||||
"terminal.ansi.dim_green": "#354d2eff",
|
||||
"terminal.ansi.yellow": "#dec184ff",
|
||||
"terminal.ansi.bright_yellow": "#826221ff",
|
||||
"terminal.ansi.dim_yellow": "#786441ff",
|
||||
"terminal.ansi.blue": "#5c78e2ff",
|
||||
"terminal.ansi.bright_blue": "#b5baf2ff",
|
||||
"terminal.ansi.dim_blue": "#2d3d75ff",
|
||||
"terminal.ansi.magenta": "#984ea5ff",
|
||||
"terminal.ansi.bright_magenta": "#cea6d3ff",
|
||||
"terminal.ansi.dim_magenta": "#4b2a50ff",
|
||||
"terminal.ansi.cyan": "#3a82b7ff",
|
||||
"terminal.ansi.bright_cyan": "#a3bedaff",
|
||||
"terminal.ansi.dim_cyan": "#254058ff",
|
||||
"terminal.ansi.white": "#fafafaff",
|
||||
"terminal.ansi.bright_white": "#ffffffff",
|
||||
"terminal.ansi.dim_white": "#888888ff",
|
||||
"terminal.ansi.dim_white": "#aaaaaaff",
|
||||
"link_text.hover": "#5c78e2ff",
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
|
||||
@@ -14,7 +14,6 @@ disallowed-methods = [
|
||||
{ path = "std::process::Command::stderr", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stderr" },
|
||||
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
|
||||
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
|
||||
{ path = "cocoa::foundation::NSString::alloc", reason = "NSString must be autoreleased to avoid memory leaks. Use `ns_string()` helper instead." },
|
||||
]
|
||||
disallowed-types = [
|
||||
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },
|
||||
|
||||
@@ -46,7 +46,6 @@ url.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
watch.workspace = true
|
||||
urlencoding.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger.workspace = true
|
||||
|
||||
@@ -166,7 +166,7 @@ impl Diff {
|
||||
}
|
||||
|
||||
pub fn has_revealed_range(&self, cx: &App) -> bool {
|
||||
self.multibuffer().read(cx).paths().next().is_some()
|
||||
self.multibuffer().read(cx).excerpt_paths().next().is_some()
|
||||
}
|
||||
|
||||
pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
|
||||
|
||||
@@ -4,14 +4,12 @@ use file_icons::FileIcons;
|
||||
use prompt_store::{PromptId, UserPromptId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt,
|
||||
ops::RangeInclusive,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use ui::{App, IconName, SharedString};
|
||||
use url::Url;
|
||||
use urlencoding::decode;
|
||||
use util::paths::PathStyle;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
@@ -76,13 +74,11 @@ impl MentionUri {
|
||||
let path = url.path();
|
||||
match url.scheme() {
|
||||
"file" => {
|
||||
let normalized = if path_style.is_windows() {
|
||||
let path = if path_style.is_windows() {
|
||||
path.trim_start_matches("/")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
let decoded = decode(normalized).unwrap_or(Cow::Borrowed(normalized));
|
||||
let path = decoded.as_ref();
|
||||
|
||||
if let Some(fragment) = url.fragment() {
|
||||
let line_range = parse_line_range(fragment)?;
|
||||
@@ -410,19 +406,6 @@ mod tests {
|
||||
assert_eq!(parsed.to_uri().to_string(), selection_uri);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_uri_with_non_ascii() {
|
||||
let file_uri = uri!("file:///path/to/%E6%97%A5%E6%9C%AC%E8%AA%9E.txt");
|
||||
let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::File { abs_path } => {
|
||||
assert_eq!(abs_path, Path::new(path!("/path/to/日本語.txt")));
|
||||
}
|
||||
_ => panic!("Expected File variant"),
|
||||
}
|
||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_untitled_selection_uri() {
|
||||
let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10");
|
||||
|
||||
@@ -187,10 +187,8 @@ pub async fn create_terminal_entity(
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// Disable pagers so agent/terminal commands don't hang behind interactive UIs
|
||||
// Disables paging for `git` and hopefully other commands
|
||||
env.insert("PAGER".into(), "".into());
|
||||
// Override user core.pager (e.g. delta) which Git prefers over PAGER
|
||||
env.insert("GIT_PAGER".into(), "cat".into());
|
||||
env.extend(env_vars);
|
||||
|
||||
// Use remote shell or default system shell, as appropriate
|
||||
|
||||
@@ -371,13 +371,13 @@ impl AcpTools {
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
code_block_overflow_x_scroll: true,
|
||||
code_block: StyleRefinement {
|
||||
text: TextStyleRefinement {
|
||||
text: Some(TextStyleRefinement {
|
||||
font_family: Some(
|
||||
theme_settings.buffer_font.family.clone(),
|
||||
),
|
||||
font_size: Some((base_size * 0.8).into()),
|
||||
..Default::default()
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
||||
@@ -33,8 +33,7 @@ use gpui::{
|
||||
use language_model::{LanguageModel, LanguageModelProvider, LanguageModelRegistry};
|
||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||
use prompt_store::{
|
||||
ProjectContext, PromptStore, RULES_FILE_NAMES, RulesFileContext, UserRulesContext,
|
||||
WorktreeContext,
|
||||
ProjectContext, PromptStore, RulesFileContext, UserRulesContext, WorktreeContext,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{LanguageModelSelection, update_settings_file};
|
||||
@@ -52,6 +51,18 @@ pub struct ProjectSnapshot {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
const RULES_FILE_NAMES: [&str; 9] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
".clinerules",
|
||||
".github/copilot-instructions.md",
|
||||
"CLAUDE.md",
|
||||
"AGENT.md",
|
||||
"AGENTS.md",
|
||||
"GEMINI.md",
|
||||
];
|
||||
|
||||
pub struct RulesLoadingError {
|
||||
pub message: SharedString,
|
||||
}
|
||||
@@ -1208,15 +1219,6 @@ impl TerminalHandle for AcpTerminalHandle {
|
||||
self.terminal
|
||||
.read_with(cx, |term, cx| term.current_output(cx))
|
||||
}
|
||||
|
||||
fn kill(&self, cx: &AsyncApp) -> Result<()> {
|
||||
cx.update(|cx| {
|
||||
self.terminal.update(cx, |terminal, cx| {
|
||||
terminal.kill(cx);
|
||||
});
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1343,7 +1343,6 @@ fn run_eval(eval: EvalInput) -> eval_utils::EvalOutput<EditEvalMetadata> {
|
||||
let test = EditAgentTest::new(&mut cx).await;
|
||||
test.eval(eval, &mut cx).await
|
||||
});
|
||||
cx.quit();
|
||||
match result {
|
||||
Ok(output) => eval_utils::EvalOutput {
|
||||
data: output.to_string(),
|
||||
|
||||
@@ -16,7 +16,7 @@ You are a highly skilled software engineer with extensive knowledge in many prog
|
||||
3. DO NOT use tools to access items that are already available in the context section.
|
||||
4. Use only the tools that are currently available.
|
||||
5. DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
|
||||
6. When running commands that may run indefinitely or for a long time (such as build scripts, tests, servers, or file watchers), specify `timeout_ms` to bound runtime. If the command times out, the user can always ask you to run it again with a longer timeout or no timeout if they're willing to wait or cancel manually.
|
||||
6. NEVER run commands that don't terminate on their own such as web servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers.
|
||||
7. Avoid HTML entity escaping - use plain characters instead.
|
||||
|
||||
## Searching and Reading
|
||||
|
||||
@@ -9,16 +9,14 @@ use collections::IndexMap;
|
||||
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
|
||||
use fs::{FakeFs, Fs};
|
||||
use futures::{
|
||||
FutureExt as _, StreamExt,
|
||||
StreamExt,
|
||||
channel::{
|
||||
mpsc::{self, UnboundedReceiver},
|
||||
oneshot,
|
||||
},
|
||||
future::{Fuse, Shared},
|
||||
};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, Entity, Task, TestAppContext, UpdateGlobal,
|
||||
http_client::FakeHttpClient,
|
||||
App, AppContext, Entity, Task, TestAppContext, UpdateGlobal, http_client::FakeHttpClient,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language_model::{
|
||||
@@ -37,109 +35,12 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{
|
||||
path::Path,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||
use util::path;
|
||||
|
||||
mod test_tools;
|
||||
use test_tools::*;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
});
|
||||
}
|
||||
|
||||
struct FakeTerminalHandle {
|
||||
killed: Arc<AtomicBool>,
|
||||
wait_for_exit: Shared<Task<acp::TerminalExitStatus>>,
|
||||
output: acp::TerminalOutputResponse,
|
||||
id: acp::TerminalId,
|
||||
}
|
||||
|
||||
impl FakeTerminalHandle {
|
||||
fn new_never_exits(cx: &mut App) -> Self {
|
||||
let killed = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let killed_for_task = killed.clone();
|
||||
let wait_for_exit = cx
|
||||
.spawn(async move |cx| {
|
||||
loop {
|
||||
if killed_for_task.load(Ordering::SeqCst) {
|
||||
return acp::TerminalExitStatus::new();
|
||||
}
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(1))
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
|
||||
Self {
|
||||
killed,
|
||||
wait_for_exit,
|
||||
output: acp::TerminalOutputResponse::new("partial output".to_string(), false),
|
||||
id: acp::TerminalId::new("fake_terminal".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn was_killed(&self) -> bool {
|
||||
self.killed.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::TerminalHandle for FakeTerminalHandle {
|
||||
fn id(&self, _cx: &AsyncApp) -> Result<acp::TerminalId> {
|
||||
Ok(self.id.clone())
|
||||
}
|
||||
|
||||
fn current_output(&self, _cx: &AsyncApp) -> Result<acp::TerminalOutputResponse> {
|
||||
Ok(self.output.clone())
|
||||
}
|
||||
|
||||
fn wait_for_exit(&self, _cx: &AsyncApp) -> Result<Shared<Task<acp::TerminalExitStatus>>> {
|
||||
Ok(self.wait_for_exit.clone())
|
||||
}
|
||||
|
||||
fn kill(&self, _cx: &AsyncApp) -> Result<()> {
|
||||
self.killed.store(true, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct FakeThreadEnvironment {
|
||||
handle: Rc<FakeTerminalHandle>,
|
||||
}
|
||||
|
||||
impl crate::ThreadEnvironment for FakeThreadEnvironment {
|
||||
fn create_terminal(
|
||||
&self,
|
||||
_command: String,
|
||||
_cwd: Option<std::path::PathBuf>,
|
||||
_output_byte_limit: Option<u64>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Task<Result<Rc<dyn crate::TerminalHandle>>> {
|
||||
Task::ready(Ok(self.handle.clone() as Rc<dyn crate::TerminalHandle>))
|
||||
}
|
||||
}
|
||||
|
||||
fn always_allow_tools(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let mut settings = agent_settings::AgentSettings::get_global(cx).clone();
|
||||
settings.always_allow_tool_actions = true;
|
||||
agent_settings::AgentSettings::override_global(settings, cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_echo(cx: &mut TestAppContext) {
|
||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
||||
@@ -170,120 +71,6 @@ async fn test_echo(cx: &mut TestAppContext) {
|
||||
assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_terminal_tool_timeout_kills_handle(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
always_allow_tools(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
let handle = Rc::new(cx.update(|cx| FakeTerminalHandle::new_never_exits(cx)));
|
||||
let environment = Rc::new(FakeThreadEnvironment {
|
||||
handle: handle.clone(),
|
||||
});
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
let tool = Arc::new(crate::TerminalTool::new(project, environment));
|
||||
let (event_stream, mut rx) = crate::ToolCallEventStream::test();
|
||||
|
||||
let task = cx.update(|cx| {
|
||||
tool.run(
|
||||
crate::TerminalToolInput {
|
||||
command: "sleep 1000".to_string(),
|
||||
cd: ".".to_string(),
|
||||
timeout_ms: Some(5),
|
||||
},
|
||||
event_stream,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let update = rx.expect_update_fields().await;
|
||||
assert!(
|
||||
update.content.iter().any(|blocks| {
|
||||
blocks
|
||||
.iter()
|
||||
.any(|c| matches!(c, acp::ToolCallContent::Terminal(_)))
|
||||
}),
|
||||
"expected tool call update to include terminal content"
|
||||
);
|
||||
|
||||
let mut task_future: Pin<Box<Fuse<Task<Result<String>>>>> = Box::pin(task.fuse());
|
||||
|
||||
let deadline = std::time::Instant::now() + Duration::from_millis(500);
|
||||
loop {
|
||||
if let Some(result) = task_future.as_mut().now_or_never() {
|
||||
let result = result.expect("terminal tool task should complete");
|
||||
|
||||
assert!(
|
||||
handle.was_killed(),
|
||||
"expected terminal handle to be killed on timeout"
|
||||
);
|
||||
assert!(
|
||||
result.contains("partial output"),
|
||||
"expected result to include terminal output, got: {result}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if std::time::Instant::now() >= deadline {
|
||||
panic!("timed out waiting for terminal tool task to complete");
|
||||
}
|
||||
|
||||
cx.run_until_parked();
|
||||
cx.background_executor.timer(Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
#[ignore]
|
||||
async fn test_terminal_tool_without_timeout_does_not_kill_handle(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
always_allow_tools(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
let handle = Rc::new(cx.update(|cx| FakeTerminalHandle::new_never_exits(cx)));
|
||||
let environment = Rc::new(FakeThreadEnvironment {
|
||||
handle: handle.clone(),
|
||||
});
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
let tool = Arc::new(crate::TerminalTool::new(project, environment));
|
||||
let (event_stream, mut rx) = crate::ToolCallEventStream::test();
|
||||
|
||||
let _task = cx.update(|cx| {
|
||||
tool.run(
|
||||
crate::TerminalToolInput {
|
||||
command: "sleep 1000".to_string(),
|
||||
cd: ".".to_string(),
|
||||
timeout_ms: None,
|
||||
},
|
||||
event_stream,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let update = rx.expect_update_fields().await;
|
||||
assert!(
|
||||
update.content.iter().any(|blocks| {
|
||||
blocks
|
||||
.iter()
|
||||
.any(|c| matches!(c, acp::ToolCallContent::Terminal(_)))
|
||||
}),
|
||||
"expected tool call update to include terminal content"
|
||||
);
|
||||
|
||||
smol::Timer::after(Duration::from_millis(25)).await;
|
||||
|
||||
assert!(
|
||||
!handle.was_killed(),
|
||||
"did not expect terminal handle to be killed without a timeout"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_thinking(cx: &mut TestAppContext) {
|
||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
||||
|
||||
@@ -530,7 +530,6 @@ pub trait TerminalHandle {
|
||||
fn id(&self, cx: &AsyncApp) -> Result<acp::TerminalId>;
|
||||
fn current_output(&self, cx: &AsyncApp) -> Result<acp::TerminalOutputResponse>;
|
||||
fn wait_for_exit(&self, cx: &AsyncApp) -> Result<Shared<Task<acp::TerminalExitStatus>>>;
|
||||
fn kill(&self, cx: &AsyncApp) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait ThreadEnvironment {
|
||||
@@ -2659,6 +2658,7 @@ impl From<UserMessageContent> for acp::ContentBlock {
|
||||
fn convert_image(image_content: acp::ImageContent) -> LanguageModelImage {
|
||||
LanguageModelImage {
|
||||
source: image_content.data.into(),
|
||||
size: None,
|
||||
// TODO: make this optional?
|
||||
size: gpui::Size::new(0.into(), 0.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{App, AppContext, Entity, SharedString, Task};
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -9,7 +8,6 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
@@ -27,17 +25,13 @@ const COMMAND_OUTPUT_LIMIT: u64 = 16 * 1024;
|
||||
///
|
||||
/// Do not use this tool for commands that run indefinitely, such as servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers that don't terminate on their own.
|
||||
///
|
||||
/// For potentially long-running commands, prefer specifying `timeout_ms` to bound runtime and prevent indefinite hangs.
|
||||
///
|
||||
/// Remember that each invocation of this tool will spawn a new shell process, so you can't rely on any state from previous invocations.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct TerminalToolInput {
|
||||
/// The one-liner command to execute.
|
||||
pub command: String,
|
||||
command: String,
|
||||
/// Working directory for the command. This must be one of the root directories of the project.
|
||||
pub cd: String,
|
||||
/// Optional maximum runtime (in milliseconds). If exceeded, the running terminal task is killed.
|
||||
pub timeout_ms: Option<u64>,
|
||||
cd: String,
|
||||
}
|
||||
|
||||
pub struct TerminalTool {
|
||||
@@ -122,26 +116,7 @@ impl AgentTool for TerminalTool {
|
||||
acp::ToolCallContent::Terminal(acp::Terminal::new(terminal_id)),
|
||||
]));
|
||||
|
||||
let timeout = input.timeout_ms.map(Duration::from_millis);
|
||||
|
||||
let exit_status = match timeout {
|
||||
Some(timeout) => {
|
||||
let wait_for_exit = terminal.wait_for_exit(cx)?;
|
||||
let timeout_task = cx.background_spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
});
|
||||
|
||||
futures::select! {
|
||||
status = wait_for_exit.clone().fuse() => status,
|
||||
_ = timeout_task.fuse() => {
|
||||
terminal.kill(cx)?;
|
||||
wait_for_exit.await
|
||||
}
|
||||
}
|
||||
}
|
||||
None => terminal.wait_for_exit(cx)?.await,
|
||||
};
|
||||
|
||||
let exit_status = terminal.wait_for_exit(cx)?.await;
|
||||
let output = terminal.current_output(cx)?;
|
||||
|
||||
Ok(process_content(output, &input.command, exit_status))
|
||||
|
||||
@@ -11,6 +11,8 @@ use project::agent_server_store::AgentServerCommand;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings as _;
|
||||
use task::ShellBuilder;
|
||||
#[cfg(windows)]
|
||||
use task::ShellKind;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use std::path::PathBuf;
|
||||
@@ -89,9 +91,24 @@ impl AcpConnection {
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?;
|
||||
let builder = ShellBuilder::new(&shell, cfg!(windows)).non_interactive();
|
||||
let mut child =
|
||||
builder.build_command(Some(command.path.display().to_string()), &command.args);
|
||||
let builder = ShellBuilder::new(&shell, cfg!(windows));
|
||||
#[cfg(windows)]
|
||||
let kind = builder.kind();
|
||||
let (cmd, args) = builder.build(Some(command.path.display().to_string()), &command.args);
|
||||
|
||||
let mut child = util::command::new_smol_command(cmd);
|
||||
#[cfg(windows)]
|
||||
if kind == ShellKind::Cmd {
|
||||
use smol::process::windows::CommandExt;
|
||||
for arg in args {
|
||||
child.raw_arg(arg);
|
||||
}
|
||||
} else {
|
||||
child.args(args);
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
child.args(args);
|
||||
|
||||
child
|
||||
.envs(command.env.iter().flatten())
|
||||
.stdin(std::process::Stdio::piped())
|
||||
|
||||
@@ -9,7 +9,7 @@ use project::DisableAiSettings;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
DefaultAgentView, DockPosition, DockSide, LanguageModelParameters, LanguageModelSelection,
|
||||
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
|
||||
NotifyWhenAgentWaiting, RegisterSetting, Settings,
|
||||
};
|
||||
|
||||
@@ -24,12 +24,10 @@ pub struct AgentSettings {
|
||||
pub enabled: bool,
|
||||
pub button: bool,
|
||||
pub dock: DockPosition,
|
||||
pub agents_panel_dock: DockSide,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_model: Option<LanguageModelSelection>,
|
||||
pub inline_assistant_model: Option<LanguageModelSelection>,
|
||||
pub inline_assistant_use_streaming_tools: bool,
|
||||
pub commit_message_model: Option<LanguageModelSelection>,
|
||||
pub thread_summary_model: Option<LanguageModelSelection>,
|
||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||
@@ -153,14 +151,10 @@ impl Settings for AgentSettings {
|
||||
enabled: agent.enabled.unwrap(),
|
||||
button: agent.button.unwrap(),
|
||||
dock: agent.dock.unwrap(),
|
||||
agents_panel_dock: agent.agents_panel_dock.unwrap(),
|
||||
default_width: px(agent.default_width.unwrap()),
|
||||
default_height: px(agent.default_height.unwrap()),
|
||||
default_model: Some(agent.default_model.unwrap()),
|
||||
inline_assistant_model: agent.inline_assistant_model,
|
||||
inline_assistant_use_streaming_tools: agent
|
||||
.inline_assistant_use_streaming_tools
|
||||
.unwrap_or(true),
|
||||
commit_message_model: agent.commit_message_model,
|
||||
thread_summary_model: agent.thread_summary_model,
|
||||
inline_alternatives: agent.inline_alternatives.unwrap_or_default(),
|
||||
|
||||
@@ -13,7 +13,7 @@ path = "src/agent_ui.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["assistant_text_thread/test-support", "eval_utils", "gpui/test-support", "language/test-support", "reqwest_client", "workspace/test-support"]
|
||||
test-support = ["gpui/test-support", "language/test-support", "reqwest_client"]
|
||||
unit-eval = []
|
||||
|
||||
[dependencies]
|
||||
@@ -40,7 +40,6 @@ component.workspace = true
|
||||
context_server.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
eval_utils = { workspace = true, optional = true }
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
feature_flags.workspace = true
|
||||
@@ -72,7 +71,6 @@ postage.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
rules_library.workspace = true
|
||||
@@ -86,6 +84,7 @@ smol.workspace = true
|
||||
streaming_diff.workspace = true
|
||||
task.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal.workspace = true
|
||||
terminal_view.workspace = true
|
||||
text.workspace = true
|
||||
@@ -121,6 +120,7 @@ language_model = { workspace = true, "features" = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
semver.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -12,11 +12,14 @@ use gpui::{
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{DocumentationAside, DocumentationEdge, DocumentationSide, prelude::*};
|
||||
use ui::{
|
||||
DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, KeyBinding, ListItem,
|
||||
ListItemSpacing, prelude::*,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use zed_actions::agent::OpenSettings;
|
||||
|
||||
use crate::ui::{HoldForDefault, ModelSelectorFooter, ModelSelectorHeader, ModelSelectorListItem};
|
||||
use crate::ui::HoldForDefault;
|
||||
|
||||
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
|
||||
|
||||
@@ -233,19 +236,39 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
is_focused: bool,
|
||||
selected: bool,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
match self.filtered_entries.get(ix)? {
|
||||
AcpModelPickerEntry::Separator(title) => {
|
||||
Some(ModelSelectorHeader::new(title, ix > 1).into_any_element())
|
||||
}
|
||||
AcpModelPickerEntry::Separator(title) => Some(
|
||||
div()
|
||||
.px_2()
|
||||
.pb_1()
|
||||
.when(ix > 1, |this| {
|
||||
this.mt_1()
|
||||
.pt_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
})
|
||||
.child(
|
||||
Label::new(title)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
AcpModelPickerEntry::Model(model_info) => {
|
||||
let is_selected = Some(model_info) == self.selected_model.as_ref();
|
||||
let default_model = self.agent_server.default_model(cx);
|
||||
let is_default = default_model.as_ref() == Some(&model_info.id);
|
||||
|
||||
let model_icon_color = if is_selected {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
};
|
||||
|
||||
Some(
|
||||
div()
|
||||
.id(("model-picker-menu-child", ix))
|
||||
@@ -261,10 +284,30 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
}))
|
||||
})
|
||||
.child(
|
||||
ModelSelectorListItem::new(ix, model_info.name.clone())
|
||||
.is_focused(is_focused)
|
||||
.is_selected(is_selected)
|
||||
.when_some(model_info.icon, |this, icon| this.icon(icon)),
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.when_some(model_info.icon, |this, icon| {
|
||||
this.child(
|
||||
Icon::new(icon)
|
||||
.color(model_icon_color)
|
||||
.size(IconSize::Small)
|
||||
)
|
||||
})
|
||||
.child(Label::new(model_info.name.clone()).truncate()),
|
||||
)
|
||||
.end_slot(div().pr_3().when(is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})),
|
||||
)
|
||||
.into_any_element()
|
||||
)
|
||||
@@ -300,7 +343,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
fn render_footer(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<AnyElement> {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
@@ -308,7 +351,26 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ModelSelectorFooter::new(OpenSettings.boxed_clone(), focus_handle).into_any_element())
|
||||
Some(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Button::new("configure", "Configure")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&OpenSettings, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(OpenSettings.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +403,9 @@ async fn fuzzy_search(
|
||||
let candidates = model_list
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, model)| StringMatchCandidate::new(ix, model.name.as_ref()))
|
||||
.map(|(ix, model)| {
|
||||
StringMatchCandidate::new(ix, &format!("{}/{}", model.id, model.name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut matches = match_strings(
|
||||
&candidates,
|
||||
|
||||
@@ -63,7 +63,10 @@ use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
|
||||
use crate::agent_diff::AgentDiff;
|
||||
use crate::profile_selector::{ProfileProvider, ProfileSelector};
|
||||
|
||||
use crate::ui::{AgentNotification, AgentNotificationEvent, BurnModeTooltip, UsageCallout};
|
||||
use crate::ui::{
|
||||
AgentNotification, AgentNotificationEvent, BurnModeTooltip, UnavailableEditingTooltip,
|
||||
UsageCallout,
|
||||
};
|
||||
use crate::{
|
||||
AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, ContinueThread, ContinueWithBurnMode,
|
||||
CycleModeSelector, ExpandMessageEditor, Follow, KeepAll, NewThread, OpenAgentDiff, OpenHistory,
|
||||
@@ -690,7 +693,7 @@ impl AcpThreadView {
|
||||
this.new_server_version_available = Some(new_version.into());
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2088,23 +2091,10 @@ impl AcpThreadView {
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.tooltip(Tooltip::element({
|
||||
move |_, _| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(Label::new("Unavailable Editing")).child(
|
||||
div().max_w_64().child(
|
||||
Label::new(format!(
|
||||
"Editing previous messages is not available for {} yet.",
|
||||
agent_name.clone()
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}))
|
||||
.tooltip(move |_window, cx| {
|
||||
cx.new(|_| UnavailableEditingTooltip::new(agent_name.clone()))
|
||||
.into()
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -4218,11 +4208,7 @@ impl AcpThreadView {
|
||||
}
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &CycleModeSelector, window, cx| {
|
||||
if let Some(profile_selector) = this.profile_selector.as_ref() {
|
||||
profile_selector.update(cx, |profile_selector, cx| {
|
||||
profile_selector.cycle_profile(cx);
|
||||
});
|
||||
} else if let Some(mode_selector) = this.mode_selector() {
|
||||
if let Some(mode_selector) = this.mode_selector() {
|
||||
mode_selector.update(cx, |mode_selector, cx| {
|
||||
mode_selector.cycle_mode(window, cx);
|
||||
});
|
||||
@@ -4873,32 +4859,6 @@ impl AcpThreadView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn scroll_to_most_recent_user_prompt(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(thread) = self.thread() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let entries = thread.read(cx).entries();
|
||||
if entries.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the most recent user message and scroll it to the top of the viewport.
|
||||
// (Fallback: if no user message exists, scroll to the bottom.)
|
||||
if let Some(ix) = entries
|
||||
.iter()
|
||||
.rposition(|entry| matches!(entry, AgentThreadEntry::UserMessage(_)))
|
||||
{
|
||||
self.list_state.scroll_to(ListOffset {
|
||||
item_ix: ix,
|
||||
offset_in_item: px(0.0),
|
||||
});
|
||||
cx.notify();
|
||||
} else {
|
||||
self.scroll_to_bottom(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(thread) = self.thread() {
|
||||
let entry_count = thread.read(cx).entries().len();
|
||||
@@ -5117,16 +5077,6 @@ impl AcpThreadView {
|
||||
}
|
||||
}));
|
||||
|
||||
let scroll_to_recent_user_prompt =
|
||||
IconButton::new("scroll_to_recent_user_prompt", IconName::ForwardArrow)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Ignored)
|
||||
.tooltip(Tooltip::text("Scroll To Most Recent User Prompt"))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.scroll_to_most_recent_user_prompt(cx);
|
||||
}));
|
||||
|
||||
let scroll_to_top = IconButton::new("scroll_to_top", IconName::ArrowUp)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -5203,7 +5153,6 @@ impl AcpThreadView {
|
||||
|
||||
container
|
||||
.child(open_as_markdown)
|
||||
.child(scroll_to_recent_user_prompt)
|
||||
.child(scroll_to_top)
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -6104,13 +6053,13 @@ fn default_markdown_style(
|
||||
},
|
||||
border_color: Some(colors.border_variant),
|
||||
background: Some(colors.editor_background.into()),
|
||||
text: TextStyleRefinement {
|
||||
text: Some(TextStyleRefinement {
|
||||
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||
font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.buffer_font.features.clone()),
|
||||
font_size: Some(buffer_font_size.into()),
|
||||
..Default::default()
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
inline_code: TextStyleRefinement {
|
||||
@@ -6836,70 +6785,6 @@ pub(crate) mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_scroll_to_most_recent_user_prompt(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
|
||||
// Each user prompt will result in a user message entry plus an agent message entry.
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk::new("Response 1".into()),
|
||||
)]);
|
||||
|
||||
let (thread_view, cx) =
|
||||
setup_thread_view(StubAgentServer::new(connection.clone()), cx).await;
|
||||
|
||||
let thread = thread_view
|
||||
.read_with(cx, |view, _| view.thread().cloned())
|
||||
.unwrap();
|
||||
|
||||
thread
|
||||
.update(cx, |thread, cx| thread.send_raw("Prompt 1", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk::new("Response 2".into()),
|
||||
)]);
|
||||
|
||||
thread
|
||||
.update(cx, |thread, cx| thread.send_raw("Prompt 2", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
// Move somewhere else first so we're not trivially already on the last user prompt.
|
||||
thread_view.update(cx, |view, cx| {
|
||||
view.scroll_to_top(cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
thread_view.update(cx, |view, cx| {
|
||||
view.scroll_to_most_recent_user_prompt(cx);
|
||||
let scroll_top = view.list_state.logical_scroll_top();
|
||||
// Entries layout is: [User1, Assistant1, User2, Assistant2]
|
||||
assert_eq!(scroll_top.item_ix, 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_scroll_to_most_recent_user_prompt_falls_back_to_bottom_without_user_messages(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await;
|
||||
|
||||
// With no entries, scrolling should be a no-op and must not panic.
|
||||
thread_view.update(cx, |view, cx| {
|
||||
view.scroll_to_most_recent_user_prompt(cx);
|
||||
let scroll_top = view.list_state.logical_scroll_top();
|
||||
assert_eq!(scroll_top.item_ix, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_message_editing_cancel(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -34,9 +34,9 @@ use project::{
|
||||
};
|
||||
use settings::{Settings, SettingsStore, update_settings_file};
|
||||
use ui::{
|
||||
ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure, Divider,
|
||||
DividerColor, ElevationIndex, Indicator, LabelSize, PopoverMenu, Switch, Tooltip,
|
||||
WithScrollbar, prelude::*,
|
||||
Button, ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure,
|
||||
Divider, DividerColor, ElevationIndex, IconName, IconPosition, IconSize, Indicator, LabelSize,
|
||||
PopoverMenu, Switch, Tooltip, WithScrollbar, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{Workspace, create_and_open_local_file};
|
||||
@@ -975,7 +975,7 @@ impl AgentConfiguration {
|
||||
let icon = if let Some(icon_path) = agent_server_store.agent_icon(&name) {
|
||||
AgentIcon::Path(icon_path)
|
||||
} else {
|
||||
AgentIcon::Name(IconName::Sparkle)
|
||||
AgentIcon::Name(IconName::Ai)
|
||||
};
|
||||
let display_name = agent_server_store
|
||||
.agent_display_name(&name)
|
||||
@@ -1137,7 +1137,6 @@ impl AgentConfiguration {
|
||||
) -> impl IntoElement {
|
||||
let id = id.into();
|
||||
let display_name = display_name.into();
|
||||
|
||||
let icon = match icon {
|
||||
AgentIcon::Name(icon_name) => Icon::new(icon_name)
|
||||
.size(IconSize::Small)
|
||||
|
||||
@@ -8,7 +8,6 @@ use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
|
||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||
use settings::SettingsStore;
|
||||
use settings::{
|
||||
LanguageModelProviderSetting, LanguageModelSelection, Settings as _, update_settings_file,
|
||||
};
|
||||
@@ -95,7 +94,6 @@ pub struct ViewProfileMode {
|
||||
configure_default_model: NavigableEntry,
|
||||
configure_tools: NavigableEntry,
|
||||
configure_mcps: NavigableEntry,
|
||||
delete_profile: NavigableEntry,
|
||||
cancel_item: NavigableEntry,
|
||||
}
|
||||
|
||||
@@ -111,7 +109,6 @@ pub struct ManageProfilesModal {
|
||||
active_model: Option<Arc<dyn LanguageModel>>,
|
||||
focus_handle: FocusHandle,
|
||||
mode: Mode,
|
||||
_settings_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl ManageProfilesModal {
|
||||
@@ -151,23 +148,12 @@ impl ManageProfilesModal {
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
// Keep this modal in sync with settings changes (including profile deletion).
|
||||
let settings_subscription =
|
||||
cx.observe_global_in::<SettingsStore>(window, |this, window, cx| {
|
||||
if matches!(this.mode, Mode::ChooseProfile(_)) {
|
||||
this.mode = Mode::choose_profile(window, cx);
|
||||
this.focus_handle(cx).focus(window);
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
fs,
|
||||
active_model,
|
||||
context_server_registry,
|
||||
focus_handle,
|
||||
mode: Mode::choose_profile(window, cx),
|
||||
_settings_subscription: settings_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +192,6 @@ impl ManageProfilesModal {
|
||||
configure_default_model: NavigableEntry::focusable(cx),
|
||||
configure_tools: NavigableEntry::focusable(cx),
|
||||
configure_mcps: NavigableEntry::focusable(cx),
|
||||
delete_profile: NavigableEntry::focusable(cx),
|
||||
cancel_item: NavigableEntry::focusable(cx),
|
||||
});
|
||||
self.focus_handle(cx).focus(window);
|
||||
@@ -384,42 +369,6 @@ impl ManageProfilesModal {
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_profile(
|
||||
&mut self,
|
||||
profile_id: AgentProfileId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if builtin_profiles::is_builtin(&profile_id) {
|
||||
self.view_profile(profile_id, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
|
||||
update_settings_file(fs, cx, move |settings, _cx| {
|
||||
let Some(agent_settings) = settings.agent.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(profiles) = agent_settings.profiles.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
profiles.shift_remove(profile_id.0.as_ref());
|
||||
|
||||
if agent_settings
|
||||
.default_profile
|
||||
.as_deref()
|
||||
.is_some_and(|default_profile| default_profile == profile_id.0.as_ref())
|
||||
{
|
||||
agent_settings.default_profile = Some(AgentProfileId::default().0);
|
||||
}
|
||||
});
|
||||
|
||||
self.choose_profile(window, cx);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match &self.mode {
|
||||
Mode::ChooseProfile { .. } => {
|
||||
@@ -807,40 +756,6 @@ impl ManageProfilesModal {
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("delete-profile")
|
||||
.track_focus(&mode.delete_profile.focus_handle)
|
||||
.on_action({
|
||||
let profile_id = mode.profile_id.clone();
|
||||
cx.listener(move |this, _: &menu::Confirm, window, cx| {
|
||||
this.delete_profile(profile_id.clone(), window, cx);
|
||||
})
|
||||
})
|
||||
.child(
|
||||
ListItem::new("delete-profile")
|
||||
.toggle_state(
|
||||
mode.delete_profile
|
||||
.focus_handle
|
||||
.contains_focused(window, cx),
|
||||
)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(
|
||||
Icon::new(IconName::Trash)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.child(Label::new("Delete Profile").color(Color::Error))
|
||||
.disabled(builtin_profiles::is_builtin(&mode.profile_id))
|
||||
.on_click({
|
||||
let profile_id = mode.profile_id.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.delete_profile(profile_id.clone(), window, cx);
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(ListSeparator)
|
||||
.child(
|
||||
div()
|
||||
@@ -890,7 +805,6 @@ impl ManageProfilesModal {
|
||||
.entry(mode.configure_default_model)
|
||||
.entry(mode.configure_tools)
|
||||
.entry(mode.configure_mcps)
|
||||
.entry(mode.delete_profile)
|
||||
.entry(mode.cancel_item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,12 +130,7 @@ impl AgentDiffPane {
|
||||
.action_log()
|
||||
.read(cx)
|
||||
.changed_buffers(cx);
|
||||
let mut paths_to_delete = self
|
||||
.multibuffer
|
||||
.read(cx)
|
||||
.paths()
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
|
||||
|
||||
for (buffer, diff_handle) in changed_buffers {
|
||||
if buffer.read(cx).file().is_none() {
|
||||
|
||||
@@ -259,7 +259,7 @@ impl AgentType {
|
||||
Self::Gemini => Some(IconName::AiGemini),
|
||||
Self::ClaudeCode => Some(IconName::AiClaude),
|
||||
Self::Codex => Some(IconName::AiOpenAi),
|
||||
Self::Custom { .. } => Some(IconName::Sparkle),
|
||||
Self::Custom { .. } => Some(IconName::Terminal),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1851,17 +1851,14 @@ impl AgentPanel {
|
||||
let agent_server_store = self.project.read(cx).agent_server_store().clone();
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
let (selected_agent_custom_icon, selected_agent_label) =
|
||||
// Get custom icon path for selected agent before building menu (to avoid borrow issues)
|
||||
let selected_agent_custom_icon =
|
||||
if let AgentType::Custom { name, .. } = &self.selected_agent {
|
||||
let store = agent_server_store.read(cx);
|
||||
let icon = store.agent_icon(&ExternalAgentServerName(name.clone()));
|
||||
|
||||
let label = store
|
||||
.agent_display_name(&ExternalAgentServerName(name.clone()))
|
||||
.unwrap_or_else(|| self.selected_agent.label());
|
||||
(icon, label)
|
||||
agent_server_store
|
||||
.read(cx)
|
||||
.agent_icon(&ExternalAgentServerName(name.clone()))
|
||||
} else {
|
||||
(None, self.selected_agent.label())
|
||||
None
|
||||
};
|
||||
|
||||
let active_thread = match &self.active_view {
|
||||
@@ -2093,7 +2090,7 @@ impl AgentPanel {
|
||||
if let Some(icon_path) = icon_path {
|
||||
entry = entry.custom_icon_svg(icon_path);
|
||||
} else {
|
||||
entry = entry.icon(IconName::Sparkle);
|
||||
entry = entry.icon(IconName::Terminal);
|
||||
}
|
||||
entry = entry
|
||||
.when(
|
||||
@@ -2157,6 +2154,8 @@ impl AgentPanel {
|
||||
}
|
||||
});
|
||||
|
||||
let selected_agent_label = self.selected_agent.label();
|
||||
|
||||
let is_thread_loading = self
|
||||
.active_thread_view()
|
||||
.map(|thread| thread.read(cx).is_loading())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod acp;
|
||||
mod acp;
|
||||
mod agent_configuration;
|
||||
mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
@@ -7,6 +7,8 @@ mod buffer_codegen;
|
||||
mod completion_provider;
|
||||
mod context;
|
||||
mod context_server_configuration;
|
||||
#[cfg(test)]
|
||||
mod evals;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
@@ -26,7 +28,7 @@ use agent_settings::{AgentProfileId, AgentSettings};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _};
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use fs::Fs;
|
||||
use gpui::{Action, App, Entity, SharedString, actions};
|
||||
use language::{
|
||||
@@ -214,7 +216,7 @@ pub fn init(
|
||||
is_eval: bool,
|
||||
cx: &mut App,
|
||||
) {
|
||||
assistant_text_thread::init(client, cx);
|
||||
assistant_text_thread::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
if !is_eval {
|
||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||
@@ -227,8 +229,13 @@ pub fn init(
|
||||
TextThreadEditor::init(cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(fs.clone(), prompt_builder.clone(), cx);
|
||||
terminal_inline_assistant::init(fs.clone(), prompt_builder, cx);
|
||||
inline_assistant::init(
|
||||
fs.clone(),
|
||||
prompt_builder.clone(),
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
terminal_inline_assistant::init(fs.clone(), prompt_builder, client.telemetry().clone(), cx);
|
||||
cx.observe_new(move |workspace, window, cx| {
|
||||
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
||||
})
|
||||
@@ -244,31 +251,23 @@ pub fn init(
|
||||
update_command_palette_filter(app_cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_flags_ready(|_, cx| {
|
||||
update_command_palette_filter(cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_command_palette_filter(cx: &mut App) {
|
||||
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
|
||||
let agent_enabled = AgentSettings::get_global(cx).enabled;
|
||||
let agent_v2_enabled = cx.has_flag::<AgentV2FeatureFlag>();
|
||||
let edit_prediction_provider = AllLanguageSettings::get_global(cx)
|
||||
.edit_predictions
|
||||
.provider;
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _| {
|
||||
use editor::actions::{
|
||||
AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction,
|
||||
NextEditPrediction, PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
|
||||
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
|
||||
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
|
||||
};
|
||||
let edit_prediction_actions = [
|
||||
TypeId::of::<AcceptEditPrediction>(),
|
||||
TypeId::of::<AcceptNextWordEditPrediction>(),
|
||||
TypeId::of::<AcceptNextLineEditPrediction>(),
|
||||
TypeId::of::<AcceptEditPrediction>(),
|
||||
TypeId::of::<AcceptPartialEditPrediction>(),
|
||||
TypeId::of::<ShowEditPrediction>(),
|
||||
TypeId::of::<NextEditPrediction>(),
|
||||
TypeId::of::<PreviousEditPrediction>(),
|
||||
@@ -277,7 +276,6 @@ fn update_command_palette_filter(cx: &mut App) {
|
||||
|
||||
if disable_ai {
|
||||
filter.hide_namespace("agent");
|
||||
filter.hide_namespace("agents");
|
||||
filter.hide_namespace("assistant");
|
||||
filter.hide_namespace("copilot");
|
||||
filter.hide_namespace("supermaven");
|
||||
@@ -289,10 +287,8 @@ fn update_command_palette_filter(cx: &mut App) {
|
||||
} else {
|
||||
if agent_enabled {
|
||||
filter.show_namespace("agent");
|
||||
filter.show_namespace("agents");
|
||||
} else {
|
||||
filter.hide_namespace("agent");
|
||||
filter.hide_namespace("agents");
|
||||
}
|
||||
|
||||
filter.show_namespace("assistant");
|
||||
@@ -328,9 +324,6 @@ fn update_command_palette_filter(cx: &mut App) {
|
||||
|
||||
filter.show_namespace("zed_predict_onboarding");
|
||||
filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
|
||||
if !agent_v2_enabled {
|
||||
filter.hide_action_types(&[TypeId::of::<zed_actions::agent::ToggleAgentPane>()]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -429,7 +422,7 @@ mod tests {
|
||||
use gpui::{BorrowAppContext, TestAppContext, px};
|
||||
use project::DisableAiSettings;
|
||||
use settings::{
|
||||
DefaultAgentView, DockPosition, DockSide, NotifyWhenAgentWaiting, Settings, SettingsStore,
|
||||
DefaultAgentView, DockPosition, NotifyWhenAgentWaiting, Settings, SettingsStore,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
@@ -448,12 +441,10 @@ mod tests {
|
||||
enabled: true,
|
||||
button: true,
|
||||
dock: DockPosition::Right,
|
||||
agents_panel_dock: DockSide::Left,
|
||||
default_width: px(300.),
|
||||
default_height: px(600.),
|
||||
default_model: None,
|
||||
inline_assistant_model: None,
|
||||
inline_assistant_use_streaming_tools: false,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: vec![],
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
use crate::{context::LoadedContext, inline_prompt_editor::CodegenStatus};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use uuid::Uuid;
|
||||
|
||||
use client::telemetry::Telemetry;
|
||||
use cloud_llm_client::CompletionIntent;
|
||||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
use feature_flags::{FeatureFlagAppExt as _, InlineAssistantUseToolFeatureFlag};
|
||||
use feature_flags::{FeatureFlagAppExt as _, InlineAssistantV2FeatureFlag};
|
||||
use futures::{
|
||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||
channel::mpsc,
|
||||
future::{LocalBoxFuture, Shared},
|
||||
join,
|
||||
stream::BoxStream,
|
||||
};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{Buffer, IndentKind, LanguageName, Point, TransactionId, line_diff};
|
||||
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, LanguageModelTextStream, LanguageModelToolChoice,
|
||||
LanguageModelToolUse, Role, TokenUsage,
|
||||
LanguageModel, LanguageModelCompletionError, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelTextStream, Role,
|
||||
report_assistant_event,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
@@ -28,7 +25,6 @@ use prompt_store::PromptBuilder;
|
||||
use rope::Rope;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings as _;
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
cmp,
|
||||
@@ -41,24 +37,28 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use ui::SharedString;
|
||||
|
||||
/// Use this tool when you cannot or should not make a rewrite. This includes:
|
||||
/// - The user's request is unclear, ambiguous, or nonsensical
|
||||
/// - The requested change cannot be made by only editing the <rewrite_this> section
|
||||
/// Use this tool to provide a message to the user when you're unable to complete a task.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct FailureMessageInput {
|
||||
/// A brief message to the user explaining why you're unable to fulfill the request or to ask a question about the request.
|
||||
#[serde(default)]
|
||||
///
|
||||
/// The message may use markdown formatting if you wish.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.
|
||||
/// Only use this tool when you are confident you understand the user's request and can fulfill it
|
||||
/// by editing the marked section.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RewriteSectionInput {
|
||||
/// A brief description of the edit you have made.
|
||||
///
|
||||
/// The description may use markdown formatting if you wish.
|
||||
/// This is optional - if the edit is simple or obvious, you should leave it empty.
|
||||
pub description: String,
|
||||
|
||||
/// The text to replace the section with.
|
||||
#[serde(default)]
|
||||
pub replacement_text: String,
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ pub struct BufferCodegen {
|
||||
buffer: Entity<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
pub is_insertion: bool,
|
||||
session_id: Uuid,
|
||||
}
|
||||
|
||||
impl BufferCodegen {
|
||||
@@ -80,7 +80,7 @@ impl BufferCodegen {
|
||||
buffer: Entity<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
session_id: Uuid,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -89,8 +89,8 @@ impl BufferCodegen {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
false,
|
||||
Some(telemetry.clone()),
|
||||
builder.clone(),
|
||||
session_id,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -103,8 +103,8 @@ impl BufferCodegen {
|
||||
buffer,
|
||||
range,
|
||||
initial_transaction_id,
|
||||
telemetry,
|
||||
builder,
|
||||
session_id,
|
||||
};
|
||||
this.activate(0, cx);
|
||||
this
|
||||
@@ -127,10 +127,6 @@ impl BufferCodegen {
|
||||
&self.alternatives[self.active_alternative]
|
||||
}
|
||||
|
||||
pub fn language_name(&self, cx: &App) -> Option<LanguageName> {
|
||||
self.active_alternative().read(cx).language_name(cx)
|
||||
}
|
||||
|
||||
pub fn status<'a>(&self, cx: &'a App) -> &'a CodegenStatus {
|
||||
&self.active_alternative().read(cx).status
|
||||
}
|
||||
@@ -189,8 +185,8 @@ impl BufferCodegen {
|
||||
self.buffer.clone(),
|
||||
self.range.clone(),
|
||||
false,
|
||||
Some(self.telemetry.clone()),
|
||||
self.builder.clone(),
|
||||
self.session_id,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
@@ -253,10 +249,6 @@ impl BufferCodegen {
|
||||
pub fn selected_text<'a>(&self, cx: &'a App) -> Option<&'a str> {
|
||||
self.active_alternative().read(cx).selected_text()
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> Uuid {
|
||||
self.session_id
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||
@@ -272,6 +264,7 @@ pub struct CodegenAlternative {
|
||||
status: CodegenStatus,
|
||||
generation: Task<()>,
|
||||
diff: Diff,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
_subscription: gpui::Subscription,
|
||||
builder: Arc<PromptBuilder>,
|
||||
active: bool,
|
||||
@@ -281,9 +274,7 @@ pub struct CodegenAlternative {
|
||||
completion: Option<String>,
|
||||
selected_text: Option<String>,
|
||||
pub message_id: Option<String>,
|
||||
session_id: Uuid,
|
||||
pub description: Option<String>,
|
||||
pub failure: Option<String>,
|
||||
pub model_explanation: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
||||
@@ -293,8 +284,8 @@ impl CodegenAlternative {
|
||||
buffer: Entity<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
active: bool,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
session_id: Uuid,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
@@ -333,6 +324,7 @@ impl CodegenAlternative {
|
||||
status: CodegenStatus::Idle,
|
||||
generation: Task::ready(()),
|
||||
diff: Diff::default(),
|
||||
telemetry,
|
||||
builder,
|
||||
active: active,
|
||||
edits: Vec::new(),
|
||||
@@ -341,20 +333,11 @@ impl CodegenAlternative {
|
||||
elapsed_time: None,
|
||||
completion: None,
|
||||
selected_text: None,
|
||||
session_id,
|
||||
description: None,
|
||||
failure: None,
|
||||
model_explanation: None,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_name(&self, cx: &App) -> Option<LanguageName> {
|
||||
self.old_buffer
|
||||
.read(cx)
|
||||
.language()
|
||||
.map(|language| language.name())
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, active: bool, cx: &mut Context<Self>) {
|
||||
if active != self.active {
|
||||
self.active = active;
|
||||
@@ -396,12 +379,6 @@ impl CodegenAlternative {
|
||||
&self.last_equal_ranges
|
||||
}
|
||||
|
||||
pub fn use_streaming_tools(model: &dyn LanguageModel, cx: &App) -> bool {
|
||||
model.supports_streaming_tools()
|
||||
&& cx.has_flag::<InlineAssistantUseToolFeatureFlag>()
|
||||
&& AgentSettings::get_global(cx).inline_assistant_use_streaming_tools
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
&mut self,
|
||||
user_prompt: String,
|
||||
@@ -409,9 +386,6 @@ impl CodegenAlternative {
|
||||
model: Arc<dyn LanguageModel>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
// Clear the model explanation since the user has started a new generation.
|
||||
self.description = None;
|
||||
|
||||
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
buffer.undo_transaction(transformation_transaction_id, cx);
|
||||
@@ -420,34 +394,33 @@ impl CodegenAlternative {
|
||||
|
||||
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
|
||||
|
||||
if Self::use_streaming_tools(model.as_ref(), cx) {
|
||||
let api_key = model.api_key(cx);
|
||||
let telemetry_id = model.telemetry_id();
|
||||
let provider_id = model.provider_id();
|
||||
|
||||
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
let completion_events = cx.spawn({
|
||||
let model = model.clone();
|
||||
async move |_, cx| model.stream_completion(request.await, cx).await
|
||||
});
|
||||
self.generation = self.handle_completion(model, completion_events, cx);
|
||||
let tool_use =
|
||||
cx.spawn(async move |_, cx| model.stream_completion_tool(request.await, cx).await);
|
||||
self.handle_tool_use(telemetry_id, provider_id.to_string(), api_key, tool_use, cx);
|
||||
} else {
|
||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||
if user_prompt.trim().to_lowercase() == "delete" {
|
||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||
} else {
|
||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||
cx.spawn({
|
||||
let model = model.clone();
|
||||
async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||
}
|
||||
cx.spawn(async move |_, cx| {
|
||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||
})
|
||||
.boxed_local()
|
||||
};
|
||||
self.generation = self.handle_stream(model, stream, cx);
|
||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_request_tools(
|
||||
fn build_request_v2(
|
||||
&self,
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
user_prompt: String,
|
||||
@@ -483,7 +456,7 @@ impl CodegenAlternative {
|
||||
|
||||
let system_prompt = self
|
||||
.builder
|
||||
.generate_inline_transformation_prompt_tools(
|
||||
.generate_inline_transformation_prompt_v2(
|
||||
language_name,
|
||||
buffer,
|
||||
range.start.0..range.end.0,
|
||||
@@ -493,9 +466,6 @@ impl CodegenAlternative {
|
||||
let temperature = AgentSettings::temperature_for_model(model, cx);
|
||||
|
||||
let tool_input_format = model.tool_input_format();
|
||||
let tool_choice = model
|
||||
.supports_tool_choice(LanguageModelToolChoice::Any)
|
||||
.then_some(LanguageModelToolChoice::Any);
|
||||
|
||||
Ok(cx.spawn(async move |_cx| {
|
||||
let mut messages = vec![LanguageModelRequestMessage {
|
||||
@@ -538,7 +508,7 @@ impl CodegenAlternative {
|
||||
intent: Some(CompletionIntent::InlineAssist),
|
||||
mode: None,
|
||||
tools,
|
||||
tool_choice,
|
||||
tool_choice: None,
|
||||
stop: Vec::new(),
|
||||
temperature,
|
||||
messages,
|
||||
@@ -554,8 +524,8 @@ impl CodegenAlternative {
|
||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<LanguageModelRequest>> {
|
||||
if Self::use_streaming_tools(model.as_ref(), cx) {
|
||||
return self.build_request_tools(model, user_prompt, context_task, cx);
|
||||
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||
return self.build_request_v2(model, user_prompt, context_task, cx);
|
||||
}
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
@@ -628,14 +598,12 @@ impl CodegenAlternative {
|
||||
|
||||
pub fn handle_stream(
|
||||
&mut self,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
model_telemetry_id: String,
|
||||
model_provider_id: String,
|
||||
model_api_key: Option<String>,
|
||||
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<()> {
|
||||
let anthropic_reporter = language_model::AnthropicEventReporter::new(&model, cx);
|
||||
let session_id = self.session_id;
|
||||
let model_telemetry_id = model.telemetry_id();
|
||||
let model_provider_id = model.provider_id().to_string();
|
||||
) {
|
||||
let start_time = Instant::now();
|
||||
|
||||
// Make a new snapshot and re-resolve anchor in case the document was modified.
|
||||
@@ -673,6 +641,8 @@ impl CodegenAlternative {
|
||||
}
|
||||
}
|
||||
|
||||
let http_client = cx.http_client();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let language_name = {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
@@ -689,8 +659,7 @@ impl CodegenAlternative {
|
||||
let completion = Arc::new(Mutex::new(String::new()));
|
||||
let completion_clone = completion.clone();
|
||||
|
||||
cx.notify();
|
||||
cx.spawn(async move |codegen, cx| {
|
||||
self.generation = cx.spawn(async move |codegen, cx| {
|
||||
let stream = stream.await;
|
||||
|
||||
let token_usage = stream
|
||||
@@ -705,11 +674,10 @@ impl CodegenAlternative {
|
||||
let model_telemetry_id = model_telemetry_id.clone();
|
||||
let model_provider_id = model_provider_id.clone();
|
||||
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
||||
let executor = cx.background_executor().clone();
|
||||
let message_id = message_id.clone();
|
||||
let line_based_stream_diff: Task<anyhow::Result<()>> = cx.background_spawn({
|
||||
let anthropic_reporter = anthropic_reporter.clone();
|
||||
let language_name = language_name.clone();
|
||||
async move {
|
||||
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
||||
cx.background_spawn(async move {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
let diff = async {
|
||||
@@ -717,7 +685,6 @@ impl CodegenAlternative {
|
||||
stream?.stream.map_err(|error| error.into()),
|
||||
);
|
||||
futures::pin_mut!(chunks);
|
||||
|
||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||
let mut line_diff = LineDiff::default();
|
||||
|
||||
@@ -806,30 +773,27 @@ impl CodegenAlternative {
|
||||
let result = diff.await;
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
telemetry::event!(
|
||||
"Assistant Responded",
|
||||
kind = "inline",
|
||||
phase = "response",
|
||||
session_id = session_id.to_string(),
|
||||
model = model_telemetry_id,
|
||||
model_provider = model_provider_id,
|
||||
language_name = language_name.as_ref().map(|n| n.to_string()),
|
||||
message_id = message_id.as_deref(),
|
||||
response_latency = response_latency,
|
||||
error_message = error_message.as_deref(),
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
message_id,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Response,
|
||||
model: model_telemetry_id,
|
||||
model_provider: model_provider_id,
|
||||
response_latency,
|
||||
error_message,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
},
|
||||
telemetry,
|
||||
http_client,
|
||||
model_api_key,
|
||||
&executor,
|
||||
);
|
||||
|
||||
anthropic_reporter.report(language_model::AnthropicEventData {
|
||||
completion_type: language_model::AnthropicCompletionType::Editor,
|
||||
event: language_model::AnthropicEventType::Response,
|
||||
language_name: language_name.map(|n| n.to_string()),
|
||||
message_id,
|
||||
});
|
||||
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
||||
codegen.update(cx, |codegen, cx| {
|
||||
@@ -912,23 +876,14 @@ impl CodegenAlternative {
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn current_completion(&self) -> Option<String> {
|
||||
self.completion.clone()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn current_description(&self) -> Option<String> {
|
||||
self.description.clone()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn current_failure(&self) -> Option<String> {
|
||||
self.failure.clone()
|
||||
}
|
||||
|
||||
pub fn selected_text(&self) -> Option<&str> {
|
||||
self.selected_text.as_deref()
|
||||
}
|
||||
@@ -1105,27 +1060,21 @@ impl CodegenAlternative {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_completion(
|
||||
fn handle_tool_use(
|
||||
&mut self,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
completion_stream: Task<
|
||||
Result<
|
||||
BoxStream<
|
||||
'static,
|
||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
>,
|
||||
LanguageModelCompletionError,
|
||||
>,
|
||||
_telemetry_id: String,
|
||||
_provider_id: String,
|
||||
_api_key: Option<String>,
|
||||
tool_use: impl 'static
|
||||
+ Future<
|
||||
Output = Result<language_model::LanguageModelToolUse, LanguageModelCompletionError>,
|
||||
>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<()> {
|
||||
) {
|
||||
self.diff = Diff::default();
|
||||
self.status = CodegenStatus::Pending;
|
||||
|
||||
cx.notify();
|
||||
// Leaving this in generation so that STOP equivalent events are respected even
|
||||
// while we're still pre-processing the completion event
|
||||
cx.spawn(async move |codegen, cx| {
|
||||
self.generation = cx.spawn(async move |codegen, cx| {
|
||||
let finish_with_status = |status: CodegenStatus, cx: &mut AsyncApp| {
|
||||
let _ = codegen.update(cx, |this, cx| {
|
||||
this.status = status;
|
||||
@@ -1134,188 +1083,76 @@ impl CodegenAlternative {
|
||||
});
|
||||
};
|
||||
|
||||
let mut completion_events = match completion_stream.await {
|
||||
Ok(events) => events,
|
||||
Err(err) => {
|
||||
finish_with_status(CodegenStatus::Error(err.into()), cx);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let tool_use = tool_use.await;
|
||||
|
||||
enum ToolUseOutput {
|
||||
Rewrite {
|
||||
text: String,
|
||||
description: Option<String>,
|
||||
},
|
||||
Failure(String),
|
||||
}
|
||||
match tool_use {
|
||||
Ok(tool_use) if tool_use.name.as_ref() == "rewrite_section" => {
|
||||
// Parse the input JSON into RewriteSectionInput
|
||||
match serde_json::from_value::<RewriteSectionInput>(tool_use.input) {
|
||||
Ok(input) => {
|
||||
// Store the description if non-empty
|
||||
let description = if !input.description.trim().is_empty() {
|
||||
Some(input.description.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
enum ModelUpdate {
|
||||
Description(String),
|
||||
Failure(String),
|
||||
}
|
||||
// Apply the replacement text to the buffer and compute diff
|
||||
let batch_diff_task = codegen
|
||||
.update(cx, |this, cx| {
|
||||
this.model_explanation = description.map(Into::into);
|
||||
let range = this.range.clone();
|
||||
this.apply_edits(
|
||||
std::iter::once((range, input.replacement_text)),
|
||||
cx,
|
||||
);
|
||||
this.reapply_batch_diff(cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
let chars_read_so_far = Arc::new(Mutex::new(0usize));
|
||||
let process_tool_use = move |tool_use: LanguageModelToolUse| -> Option<ToolUseOutput> {
|
||||
let mut chars_read_so_far = chars_read_so_far.lock();
|
||||
match tool_use.name.as_ref() {
|
||||
"rewrite_section" => {
|
||||
let Ok(input) =
|
||||
serde_json::from_value::<RewriteSectionInput>(tool_use.input)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let text = input.replacement_text[*chars_read_so_far..].to_string();
|
||||
*chars_read_so_far = input.replacement_text.len();
|
||||
Some(ToolUseOutput::Rewrite {
|
||||
text,
|
||||
description: None,
|
||||
})
|
||||
}
|
||||
"failure_message" => {
|
||||
let Ok(mut input) =
|
||||
serde_json::from_value::<FailureMessageInput>(tool_use.input)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some(ToolUseOutput::Failure(std::mem::take(&mut input.message)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded::<ModelUpdate>();
|
||||
|
||||
cx.spawn({
|
||||
let codegen = codegen.clone();
|
||||
async move |cx| {
|
||||
while let Some(update) = message_rx.next().await {
|
||||
let _ = codegen.update(cx, |this, _cx| match update {
|
||||
ModelUpdate::Description(d) => this.description = Some(d),
|
||||
ModelUpdate::Failure(f) => this.failure = Some(f),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let mut message_id = None;
|
||||
let mut first_text = None;
|
||||
let last_token_usage = Arc::new(Mutex::new(TokenUsage::default()));
|
||||
let total_text = Arc::new(Mutex::new(String::new()));
|
||||
|
||||
loop {
|
||||
if let Some(first_event) = completion_events.next().await {
|
||||
match first_event {
|
||||
Ok(LanguageModelCompletionEvent::StartMessage { message_id: id }) => {
|
||||
message_id = Some(id);
|
||||
}
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(tool_use)) => {
|
||||
if let Some(output) = process_tool_use(tool_use) {
|
||||
let (text, update) = match output {
|
||||
ToolUseOutput::Rewrite { text, description } => {
|
||||
(Some(text), description.map(ModelUpdate::Description))
|
||||
}
|
||||
ToolUseOutput::Failure(message) => {
|
||||
(None, Some(ModelUpdate::Failure(message)))
|
||||
}
|
||||
};
|
||||
if let Some(update) = update {
|
||||
let _ = message_tx.unbounded_send(update);
|
||||
}
|
||||
first_text = text;
|
||||
if first_text.is_some() {
|
||||
break;
|
||||
}
|
||||
// Wait for the diff computation to complete
|
||||
if let Some(diff_task) = batch_diff_task {
|
||||
diff_task.await;
|
||||
}
|
||||
}
|
||||
Ok(LanguageModelCompletionEvent::UsageUpdate(token_usage)) => {
|
||||
*last_token_usage.lock() = token_usage;
|
||||
}
|
||||
Ok(LanguageModelCompletionEvent::Text(text)) => {
|
||||
let mut lock = total_text.lock();
|
||||
lock.push_str(&text);
|
||||
}
|
||||
Ok(e) => {
|
||||
log::warn!("Unexpected event: {:?}", e);
|
||||
break;
|
||||
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(first_text) = first_text else {
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
};
|
||||
|
||||
let move_last_token_usage = last_token_usage.clone();
|
||||
|
||||
let text_stream = Box::pin(futures::stream::once(async { Ok(first_text) }).chain(
|
||||
completion_events.filter_map(move |e| {
|
||||
let process_tool_use = process_tool_use.clone();
|
||||
let last_token_usage = move_last_token_usage.clone();
|
||||
let total_text = total_text.clone();
|
||||
let mut message_tx = message_tx.clone();
|
||||
async move {
|
||||
match e {
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(tool_use)) => {
|
||||
let Some(output) = process_tool_use(tool_use) else {
|
||||
return None;
|
||||
};
|
||||
let (text, update) = match output {
|
||||
ToolUseOutput::Rewrite { text, description } => {
|
||||
(Some(text), description.map(ModelUpdate::Description))
|
||||
}
|
||||
ToolUseOutput::Failure(message) => {
|
||||
(None, Some(ModelUpdate::Failure(message)))
|
||||
}
|
||||
};
|
||||
if let Some(update) = update {
|
||||
let _ = message_tx.send(update).await;
|
||||
}
|
||||
text.map(Ok)
|
||||
}
|
||||
Ok(LanguageModelCompletionEvent::UsageUpdate(token_usage)) => {
|
||||
*last_token_usage.lock() = token_usage;
|
||||
None
|
||||
}
|
||||
Ok(LanguageModelCompletionEvent::Text(text)) => {
|
||||
let mut lock = total_text.lock();
|
||||
lock.push_str(&text);
|
||||
None
|
||||
}
|
||||
Ok(LanguageModelCompletionEvent::Stop(_reason)) => None,
|
||||
e => {
|
||||
log::error!("UNEXPECTED EVENT {:?}", e);
|
||||
None
|
||||
}
|
||||
Ok(tool_use) if tool_use.name.as_ref() == "failure_message" => {
|
||||
// Handle failure message tool use
|
||||
match serde_json::from_value::<FailureMessageInput>(tool_use.input) {
|
||||
Ok(input) => {
|
||||
let _ = codegen.update(cx, |this, _cx| {
|
||||
// Store the failure message as the tool description
|
||||
this.model_explanation = Some(input.message.into());
|
||||
});
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}),
|
||||
));
|
||||
|
||||
let language_model_text_stream = LanguageModelTextStream {
|
||||
message_id: message_id,
|
||||
stream: text_stream,
|
||||
last_token_usage,
|
||||
};
|
||||
|
||||
let Some(task) = codegen
|
||||
.update(cx, move |codegen, cx| {
|
||||
codegen.handle_stream(model, async { Ok(language_model_text_stream) }, cx)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
task.await;
|
||||
})
|
||||
}
|
||||
Ok(_tool_use) => {
|
||||
// Unexpected tool.
|
||||
finish_with_status(CodegenStatus::Done, cx);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1479,7 +1316,6 @@ mod tests {
|
||||
use gpui::TestAppContext;
|
||||
use indoc::indoc;
|
||||
use language::{Buffer, Point};
|
||||
use language_model::fake_provider::FakeLanguageModel;
|
||||
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||
use languages::rust_lang;
|
||||
use rand::prelude::*;
|
||||
@@ -1510,8 +1346,8 @@ mod tests {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
prompt_builder,
|
||||
Uuid::new_v4(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1572,8 +1408,8 @@ mod tests {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
prompt_builder,
|
||||
Uuid::new_v4(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1636,8 +1472,8 @@ mod tests {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
prompt_builder,
|
||||
Uuid::new_v4(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1700,8 +1536,8 @@ mod tests {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
true,
|
||||
None,
|
||||
prompt_builder,
|
||||
Uuid::new_v4(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1752,8 +1588,8 @@ mod tests {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
false,
|
||||
None,
|
||||
prompt_builder,
|
||||
Uuid::new_v4(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1842,10 +1678,11 @@ mod tests {
|
||||
cx: &mut TestAppContext,
|
||||
) -> mpsc::UnboundedSender<String> {
|
||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||
let model = Arc::new(FakeLanguageModel::default());
|
||||
codegen.update(cx, |codegen, cx| {
|
||||
codegen.generation = codegen.handle_stream(
|
||||
model,
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
String::new(),
|
||||
None,
|
||||
future::ready(Ok(LanguageModelTextStream {
|
||||
message_id: None,
|
||||
stream: chunks_rx.map(Ok).boxed(),
|
||||
|
||||
89
crates/agent_ui/src/evals.rs
Normal file
89
crates/agent_ui/src/evals.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::inline_assistant::test::run_inline_assistant_test;
|
||||
|
||||
use eval_utils::{EvalOutput, NoProcessor};
|
||||
use gpui::TestAppContext;
|
||||
use language_model::{LanguageModelRegistry, SelectedModel};
|
||||
use rand::{SeedableRng as _, rngs::StdRng};
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
fn eval_single_cursor_edit() {
|
||||
eval_utils::eval(20, 1.0, NoProcessor, move || {
|
||||
run_eval(
|
||||
&EvalInput {
|
||||
prompt: "Rename this variable to buffer_text".to_string(),
|
||||
buffer: indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
text: Strˇing,
|
||||
prompt: String,
|
||||
}
|
||||
"}
|
||||
.to_string(),
|
||||
},
|
||||
&|_, output| {
|
||||
let expected = indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
buffer_text: String,
|
||||
prompt: String,
|
||||
}
|
||||
"};
|
||||
if output == expected {
|
||||
EvalOutput {
|
||||
outcome: eval_utils::OutcomeKind::Passed,
|
||||
data: "Passed!".to_string(),
|
||||
metadata: (),
|
||||
}
|
||||
} else {
|
||||
EvalOutput {
|
||||
outcome: eval_utils::OutcomeKind::Failed,
|
||||
data: format!("Failed to rename variable, output: {}", output),
|
||||
metadata: (),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
struct EvalInput {
|
||||
buffer: String,
|
||||
prompt: String,
|
||||
}
|
||||
|
||||
fn run_eval(
|
||||
input: &EvalInput,
|
||||
judge: &dyn Fn(&EvalInput, &str) -> eval_utils::EvalOutput<()>,
|
||||
) -> eval_utils::EvalOutput<()> {
|
||||
let dispatcher = gpui::TestDispatcher::new(StdRng::from_os_rng());
|
||||
let mut cx = TestAppContext::build(dispatcher, None);
|
||||
cx.skip_drawing();
|
||||
|
||||
let buffer_text = run_inline_assistant_test(
|
||||
input.buffer.clone(),
|
||||
input.prompt.clone(),
|
||||
|cx| {
|
||||
// Reconfigure to use a real model instead of the fake one
|
||||
let model_name = std::env::var("ZED_AGENT_MODEL")
|
||||
.unwrap_or("anthropic/claude-sonnet-4-latest".into());
|
||||
|
||||
let selected_model = SelectedModel::from_str(&model_name)
|
||||
.expect("Invalid model format. Use 'provider/model-id'");
|
||||
|
||||
log::info!("Selected model: {selected_model:?}");
|
||||
|
||||
cx.update(|_, cx| {
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_inline_assistant_model(Some(&selected_model), cx);
|
||||
});
|
||||
});
|
||||
},
|
||||
|_cx| {
|
||||
log::info!("Waiting for actual response from the LLM...");
|
||||
},
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
judge(input, &buffer_text)
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
use language_model::AnthropicEventData;
|
||||
use language_model::report_anthropic_event;
|
||||
use std::cmp;
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::context::load_context;
|
||||
use crate::mention_set::MentionSet;
|
||||
@@ -18,6 +15,7 @@ use crate::{
|
||||
use agent::HistoryStore;
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::EditorSnapshot;
|
||||
use editor::MultiBufferOffset;
|
||||
@@ -40,13 +38,15 @@ use gpui::{
|
||||
WeakEntity, Window, point,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use language_model::{ConfigurationError, ConfiguredModel, LanguageModelRegistry};
|
||||
use language_model::{
|
||||
ConfigurationError, ConfiguredModel, LanguageModelRegistry, report_assistant_event,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, DisableAiSettings, LspAction, Project, ProjectTransaction};
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use settings::{Settings, SettingsStore};
|
||||
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use ui::prelude::*;
|
||||
@@ -54,8 +54,13 @@ use util::{RangeExt, ResultExt, maybe};
|
||||
use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
|
||||
use zed_actions::agent::OpenSettings;
|
||||
|
||||
pub fn init(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, cx: &mut App) {
|
||||
cx.set_global(InlineAssistant::new(fs, prompt_builder));
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
|
||||
|
||||
cx.observe_global::<SettingsStore>(|cx| {
|
||||
if DisableAiSettings::get_global(cx).disable_ai {
|
||||
@@ -95,6 +100,7 @@ pub struct InlineAssistant {
|
||||
confirmed_assists: HashMap<InlineAssistId, Entity<CodegenAlternative>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
_inline_assistant_completions: Option<mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>>,
|
||||
}
|
||||
@@ -102,7 +108,11 @@ pub struct InlineAssistant {
|
||||
impl Global for InlineAssistant {}
|
||||
|
||||
impl InlineAssistant {
|
||||
pub fn new(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>) -> Self {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
next_assist_id: InlineAssistId::default(),
|
||||
next_assist_group_id: InlineAssistGroupId::default(),
|
||||
@@ -112,11 +122,20 @@ impl InlineAssistant {
|
||||
confirmed_assists: HashMap::default(),
|
||||
prompt_history: VecDeque::default(),
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
fs,
|
||||
_inline_assistant_completions: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_completion_receiver(
|
||||
&mut self,
|
||||
sender: mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>,
|
||||
) {
|
||||
self._inline_assistant_completions = Some(sender);
|
||||
}
|
||||
|
||||
pub fn register_workspace(
|
||||
&mut self,
|
||||
workspace: &Entity<Workspace>,
|
||||
@@ -438,25 +457,17 @@ impl InlineAssistant {
|
||||
codegen_ranges.push(anchor_range);
|
||||
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {
|
||||
telemetry::event!(
|
||||
"Assistant Invoked",
|
||||
kind = "inline",
|
||||
phase = "invoked",
|
||||
model = model.model.telemetry_id(),
|
||||
model_provider = model.provider.id().to_string(),
|
||||
language_name = buffer.language().map(|language| language.name().to_proto())
|
||||
);
|
||||
|
||||
report_anthropic_event(
|
||||
&model.model,
|
||||
AnthropicEventData {
|
||||
completion_type: language_model::AnthropicCompletionType::Editor,
|
||||
event: language_model::AnthropicEventType::Invoked,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
message_id: None,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
self.telemetry.report_assistant_event(AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.model.telemetry_id(),
|
||||
model_provider: model.provider.id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,7 +491,6 @@ impl InlineAssistant {
|
||||
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||
|
||||
let assist_group_id = self.next_assist_group_id.post_inc();
|
||||
let session_id = Uuid::new_v4();
|
||||
let prompt_buffer = cx.new(|cx| {
|
||||
MultiBuffer::singleton(
|
||||
cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)),
|
||||
@@ -498,7 +508,7 @@ impl InlineAssistant {
|
||||
editor.read(cx).buffer().clone(),
|
||||
range.clone(),
|
||||
initial_transaction_id,
|
||||
session_id,
|
||||
self.telemetry.clone(),
|
||||
self.prompt_builder.clone(),
|
||||
cx,
|
||||
)
|
||||
@@ -512,7 +522,6 @@ impl InlineAssistant {
|
||||
self.prompt_history.clone(),
|
||||
prompt_buffer.clone(),
|
||||
codegen.clone(),
|
||||
session_id,
|
||||
self.fs.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_store.clone(),
|
||||
@@ -1060,6 +1069,8 @@ impl InlineAssistant {
|
||||
}
|
||||
|
||||
let active_alternative = assist.codegen.read(cx).active_alternative().clone();
|
||||
let message_id = active_alternative.read(cx).message_id.clone();
|
||||
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {
|
||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||
@@ -1068,49 +1079,28 @@ impl InlineAssistant {
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.language())
|
||||
.map(|language| language.name().0.to_string())
|
||||
.map(|language| language.name())
|
||||
});
|
||||
|
||||
let codegen = assist.codegen.read(cx);
|
||||
let session_id = codegen.session_id();
|
||||
let message_id = active_alternative.read(cx).message_id.clone();
|
||||
let model_telemetry_id = model.model.telemetry_id();
|
||||
let model_provider_id = model.model.provider_id().to_string();
|
||||
|
||||
let (phase, event_type, anthropic_event_type) = if undo {
|
||||
(
|
||||
"rejected",
|
||||
"Assistant Response Rejected",
|
||||
language_model::AnthropicEventType::Reject,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"accepted",
|
||||
"Assistant Response Accepted",
|
||||
language_model::AnthropicEventType::Accept,
|
||||
)
|
||||
};
|
||||
|
||||
telemetry::event!(
|
||||
event_type,
|
||||
phase,
|
||||
session_id = session_id.to_string(),
|
||||
kind = "inline",
|
||||
model = model_telemetry_id,
|
||||
model_provider = model_provider_id,
|
||||
language_name = language_name,
|
||||
message_id = message_id.as_deref(),
|
||||
);
|
||||
|
||||
report_anthropic_event(
|
||||
&model.model,
|
||||
language_model::AnthropicEventData {
|
||||
completion_type: language_model::AnthropicCompletionType::Editor,
|
||||
event: anthropic_event_type,
|
||||
language_name,
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
message_id,
|
||||
phase: if undo {
|
||||
AssistantPhase::Rejected
|
||||
} else {
|
||||
AssistantPhase::Accepted
|
||||
},
|
||||
model: model.model.telemetry_id(),
|
||||
model_provider: model.model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
},
|
||||
cx,
|
||||
Some(self.telemetry.clone()),
|
||||
cx.http_client(),
|
||||
model.model.api_key(cx),
|
||||
cx.background_executor(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1465,8 +1455,60 @@ impl InlineAssistant {
|
||||
let old_snapshot = codegen.snapshot(cx);
|
||||
let old_buffer = codegen.old_buffer(cx);
|
||||
let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
|
||||
// let model_explanation = codegen.model_explanation(cx);
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
// Update tool description block
|
||||
// if let Some(description) = model_explanation {
|
||||
// if let Some(block_id) = decorations.model_explanation {
|
||||
// editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
// let new_block_id = editor.insert_blocks(
|
||||
// [BlockProperties {
|
||||
// style: BlockStyle::Flex,
|
||||
// placement: BlockPlacement::Below(assist.range.end),
|
||||
// height: Some(1),
|
||||
// render: Arc::new({
|
||||
// let description = description.clone();
|
||||
// move |cx| {
|
||||
// div()
|
||||
// .w_full()
|
||||
// .py_1()
|
||||
// .px_2()
|
||||
// .bg(cx.theme().colors().editor_background)
|
||||
// .border_y_1()
|
||||
// .border_color(cx.theme().status().info_border)
|
||||
// .child(
|
||||
// Label::new(description.clone())
|
||||
// .color(Color::Muted)
|
||||
// .size(LabelSize::Small),
|
||||
// )
|
||||
// .into_any_element()
|
||||
// }
|
||||
// }),
|
||||
// priority: 0,
|
||||
// }],
|
||||
// None,
|
||||
// cx,
|
||||
// );
|
||||
// decorations.model_explanation = new_block_id.into_iter().next();
|
||||
// }
|
||||
// } else if let Some(block_id) = decorations.model_explanation {
|
||||
// // Hide the block if there's no description
|
||||
// editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||
// let new_block_id = editor.insert_blocks(
|
||||
// [BlockProperties {
|
||||
// style: BlockStyle::Flex,
|
||||
// placement: BlockPlacement::Below(assist.range.end),
|
||||
// height: Some(0),
|
||||
// render: Arc::new(|_cx| div().into_any_element()),
|
||||
// priority: 0,
|
||||
// }],
|
||||
// None,
|
||||
// cx,
|
||||
// );
|
||||
// decorations.model_explanation = new_block_id.into_iter().next();
|
||||
// }
|
||||
|
||||
let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
|
||||
editor.remove_blocks(old_blocks, None, cx);
|
||||
|
||||
@@ -1585,27 +1627,6 @@ impl InlineAssistant {
|
||||
.map(InlineAssistTarget::Terminal)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_completion_receiver(
|
||||
&mut self,
|
||||
sender: mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>,
|
||||
) {
|
||||
self._inline_assistant_completions = Some(sender);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn get_codegen(
|
||||
&mut self,
|
||||
assist_id: InlineAssistId,
|
||||
cx: &mut App,
|
||||
) -> Option<Entity<CodegenAlternative>> {
|
||||
self.assists.get(&assist_id).map(|inline_assist| {
|
||||
inline_assist
|
||||
.codegen
|
||||
.update(cx, |codegen, _cx| codegen.active_alternative().clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct EditorInlineAssists {
|
||||
@@ -2027,10 +2048,8 @@ fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "unit-eval"))]
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test {
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::HistoryStore;
|
||||
@@ -2041,6 +2060,7 @@ pub mod test {
|
||||
use futures::channel::mpsc;
|
||||
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
|
||||
use language::Buffer;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use project::Project;
|
||||
use prompt_store::PromptBuilder;
|
||||
use smol::stream::StreamExt as _;
|
||||
@@ -2049,32 +2069,13 @@ pub mod test {
|
||||
|
||||
use crate::InlineAssistant;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InlineAssistantOutput {
|
||||
Success {
|
||||
completion: Option<String>,
|
||||
description: Option<String>,
|
||||
full_buffer_text: String,
|
||||
},
|
||||
Failure {
|
||||
failure: String,
|
||||
},
|
||||
// These fields are used for logging
|
||||
#[allow(unused)]
|
||||
Malformed {
|
||||
completion: Option<String>,
|
||||
description: Option<String>,
|
||||
failure: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn run_inline_assistant_test<SetupF, TestF>(
|
||||
base_buffer: String,
|
||||
prompt: String,
|
||||
setup: SetupF,
|
||||
test: TestF,
|
||||
cx: &mut TestAppContext,
|
||||
) -> InlineAssistantOutput
|
||||
) -> String
|
||||
where
|
||||
SetupF: FnOnce(&mut gpui::VisualTestContext),
|
||||
TestF: FnOnce(&mut gpui::VisualTestContext),
|
||||
@@ -2087,7 +2088,8 @@ pub mod test {
|
||||
cx.set_http_client(http);
|
||||
Client::production(cx)
|
||||
});
|
||||
let mut inline_assistant = InlineAssistant::new(fs.clone(), prompt_builder);
|
||||
let mut inline_assistant =
|
||||
InlineAssistant::new(fs.clone(), prompt_builder, client.telemetry().clone());
|
||||
|
||||
let (tx, mut completion_rx) = mpsc::unbounded();
|
||||
inline_assistant.set_completion_receiver(tx);
|
||||
@@ -2166,217 +2168,39 @@ pub mod test {
|
||||
|
||||
test(cx);
|
||||
|
||||
let assist_id = cx
|
||||
.executor()
|
||||
.block_test(async { completion_rx.next().await })
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx.executor()
|
||||
.block_test(async { completion_rx.next().await });
|
||||
|
||||
let (completion, description, failure) = cx.update(|_, cx| {
|
||||
InlineAssistant::update_global(cx, |inline_assistant, cx| {
|
||||
let codegen = inline_assistant.get_codegen(assist_id, cx).unwrap();
|
||||
buffer.read_with(cx, |buffer, _| buffer.text())
|
||||
}
|
||||
|
||||
let completion = codegen.read(cx).current_completion();
|
||||
let description = codegen.read(cx).current_description();
|
||||
let failure = codegen.read(cx).current_failure();
|
||||
#[allow(unused)]
|
||||
pub fn test_inline_assistant(
|
||||
base_buffer: &'static str,
|
||||
llm_output: &'static str,
|
||||
cx: &mut TestAppContext,
|
||||
) -> String {
|
||||
run_inline_assistant_test(
|
||||
base_buffer.to_string(),
|
||||
"Prompt doesn't matter because we're using a fake model".to_string(),
|
||||
|cx| {
|
||||
cx.update(|_, cx| LanguageModelRegistry::test(cx));
|
||||
},
|
||||
|cx| {
|
||||
let fake_model = cx.update(|_, cx| {
|
||||
LanguageModelRegistry::global(cx)
|
||||
.update(cx, |registry, _| registry.fake_model())
|
||||
});
|
||||
let fake = fake_model.as_fake();
|
||||
|
||||
(completion, description, failure)
|
||||
})
|
||||
});
|
||||
// let fake = fake_model;
|
||||
fake.send_last_completion_stream_text_chunk(llm_output.to_string());
|
||||
fake.end_last_completion_stream();
|
||||
|
||||
if failure.is_some() && (completion.is_some() || description.is_some()) {
|
||||
InlineAssistantOutput::Malformed {
|
||||
completion,
|
||||
description,
|
||||
failure,
|
||||
}
|
||||
} else if let Some(failure) = failure {
|
||||
InlineAssistantOutput::Failure { failure }
|
||||
} else {
|
||||
InlineAssistantOutput::Success {
|
||||
completion,
|
||||
description,
|
||||
full_buffer_text: buffer.read_with(cx, |buffer, _| buffer.text()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "unit-eval"))]
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
pub mod evals {
|
||||
use std::str::FromStr;
|
||||
|
||||
use eval_utils::{EvalOutput, NoProcessor};
|
||||
use gpui::TestAppContext;
|
||||
use language_model::{LanguageModelRegistry, SelectedModel};
|
||||
use rand::{SeedableRng as _, rngs::StdRng};
|
||||
|
||||
use crate::inline_assistant::test::{InlineAssistantOutput, run_inline_assistant_test};
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
fn eval_single_cursor_edit() {
|
||||
run_eval(
|
||||
20,
|
||||
1.0,
|
||||
"Rename this variable to buffer_text".to_string(),
|
||||
indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
text: Strˇing,
|
||||
prompt: String,
|
||||
}
|
||||
"}
|
||||
.to_string(),
|
||||
exact_buffer_match(indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
buffer_text: String,
|
||||
prompt: String,
|
||||
}
|
||||
"}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
fn eval_cant_do() {
|
||||
run_eval(
|
||||
20,
|
||||
0.95,
|
||||
"Rename the struct to EvalExampleStructNope",
|
||||
indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
text: Strˇing,
|
||||
prompt: String,
|
||||
}
|
||||
"},
|
||||
uncertain_output,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(feature = "unit-eval"), ignore)]
|
||||
fn eval_unclear() {
|
||||
run_eval(
|
||||
20,
|
||||
0.95,
|
||||
"Make exactly the change I want you to make",
|
||||
indoc::indoc! {"
|
||||
struct EvalExampleStruct {
|
||||
text: Strˇing,
|
||||
prompt: String,
|
||||
}
|
||||
"},
|
||||
uncertain_output,
|
||||
);
|
||||
}
|
||||
|
||||
fn run_eval(
|
||||
iterations: usize,
|
||||
expected_pass_ratio: f32,
|
||||
prompt: impl Into<String>,
|
||||
buffer: impl Into<String>,
|
||||
judge: impl Fn(InlineAssistantOutput) -> eval_utils::EvalOutput<()> + Send + Sync + 'static,
|
||||
) {
|
||||
let buffer = buffer.into();
|
||||
let prompt = prompt.into();
|
||||
|
||||
eval_utils::eval(iterations, expected_pass_ratio, NoProcessor, move || {
|
||||
let dispatcher = gpui::TestDispatcher::new(StdRng::from_os_rng());
|
||||
let mut cx = TestAppContext::build(dispatcher, None);
|
||||
cx.skip_drawing();
|
||||
|
||||
let output = run_inline_assistant_test(
|
||||
buffer.clone(),
|
||||
prompt.clone(),
|
||||
|cx| {
|
||||
// Reconfigure to use a real model instead of the fake one
|
||||
let model_name = std::env::var("ZED_AGENT_MODEL")
|
||||
.unwrap_or("anthropic/claude-sonnet-4-latest".into());
|
||||
|
||||
let selected_model = SelectedModel::from_str(&model_name)
|
||||
.expect("Invalid model format. Use 'provider/model-id'");
|
||||
|
||||
log::info!("Selected model: {selected_model:?}");
|
||||
|
||||
cx.update(|_, cx| {
|
||||
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
|
||||
registry.select_inline_assistant_model(Some(&selected_model), cx);
|
||||
});
|
||||
});
|
||||
},
|
||||
|_cx| {
|
||||
log::info!("Waiting for actual response from the LLM...");
|
||||
},
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
cx.quit();
|
||||
|
||||
judge(output)
|
||||
});
|
||||
}
|
||||
|
||||
fn uncertain_output(output: InlineAssistantOutput) -> EvalOutput<()> {
|
||||
match &output {
|
||||
o @ InlineAssistantOutput::Success {
|
||||
completion,
|
||||
description,
|
||||
..
|
||||
} => {
|
||||
if description.is_some() && completion.is_none() {
|
||||
EvalOutput::passed(format!(
|
||||
"Assistant produced no completion, but a description:\n{}",
|
||||
description.as_ref().unwrap()
|
||||
))
|
||||
} else {
|
||||
EvalOutput::failed(format!("Assistant produced a completion:\n{:?}", o))
|
||||
}
|
||||
}
|
||||
InlineAssistantOutput::Failure {
|
||||
failure: error_message,
|
||||
} => EvalOutput::passed(format!(
|
||||
"Assistant produced a failure message: {}",
|
||||
error_message
|
||||
)),
|
||||
o @ InlineAssistantOutput::Malformed { .. } => {
|
||||
EvalOutput::failed(format!("Assistant produced a malformed response:\n{:?}", o))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exact_buffer_match(
|
||||
correct_output: impl Into<String>,
|
||||
) -> impl Fn(InlineAssistantOutput) -> EvalOutput<()> {
|
||||
let correct_output = correct_output.into();
|
||||
move |output| match output {
|
||||
InlineAssistantOutput::Success {
|
||||
description,
|
||||
full_buffer_text,
|
||||
..
|
||||
} => {
|
||||
if full_buffer_text == correct_output && description.is_none() {
|
||||
EvalOutput::passed("Assistant output matches")
|
||||
} else if full_buffer_text == correct_output {
|
||||
EvalOutput::failed(format!(
|
||||
"Assistant output produced an unescessary description description:\n{:?}",
|
||||
description
|
||||
))
|
||||
} else {
|
||||
EvalOutput::failed(format!(
|
||||
"Assistant output does not match expected output:\n{:?}\ndescription:\n{:?}",
|
||||
full_buffer_text, description
|
||||
))
|
||||
}
|
||||
}
|
||||
o @ InlineAssistantOutput::Failure { .. } => EvalOutput::failed(format!(
|
||||
"Assistant output does not match expected output: {:?}",
|
||||
o
|
||||
)),
|
||||
o @ InlineAssistantOutput::Malformed { .. } => EvalOutput::failed(format!(
|
||||
"Assistant output does not match expected output: {:?}",
|
||||
o
|
||||
)),
|
||||
}
|
||||
// Run again to process the model's response
|
||||
cx.run_until_parked();
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use editor::{
|
||||
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
|
||||
actions::{MoveDown, MoveUp},
|
||||
};
|
||||
use feature_flags::{FeatureFlagAppExt, InlineAssistantUseToolFeatureFlag};
|
||||
use feature_flags::{FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
AnyElement, App, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
@@ -20,10 +20,10 @@ use parking_lot::Mutex;
|
||||
use project::Project;
|
||||
use prompt_store::PromptStore;
|
||||
use settings::Settings;
|
||||
use std::cmp;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{cmp, mem};
|
||||
use theme::ThemeSettings;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
@@ -33,7 +33,7 @@ use workspace::{Toast, Workspace};
|
||||
use zed_actions::agent::ToggleModelSelector;
|
||||
|
||||
use crate::agent_model_selector::AgentModelSelector;
|
||||
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative};
|
||||
use crate::buffer_codegen::BufferCodegen;
|
||||
use crate::completion_provider::{
|
||||
PromptCompletionProvider, PromptCompletionProviderDelegate, PromptContextType,
|
||||
};
|
||||
@@ -44,15 +44,54 @@ use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext}
|
||||
|
||||
actions!(inline_assistant, [ThumbsUpResult, ThumbsDownResult]);
|
||||
|
||||
enum CompletionState {
|
||||
Pending,
|
||||
Generated { completion_text: Option<String> },
|
||||
Rated,
|
||||
pub struct InlineAssistRatingFeatureFlag;
|
||||
|
||||
impl FeatureFlag for InlineAssistRatingFeatureFlag {
|
||||
const NAME: &'static str = "inline-assist-rating";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionState {
|
||||
session_id: Uuid,
|
||||
completion: CompletionState,
|
||||
enum RatingState {
|
||||
Pending,
|
||||
GeneratedCompletion(Option<String>),
|
||||
Rated(Uuid),
|
||||
}
|
||||
|
||||
impl RatingState {
|
||||
fn is_pending(&self) -> bool {
|
||||
matches!(self, RatingState::Pending)
|
||||
}
|
||||
|
||||
fn rating_id(&self) -> Option<Uuid> {
|
||||
match self {
|
||||
RatingState::Pending => None,
|
||||
RatingState::GeneratedCompletion(_) => None,
|
||||
RatingState::Rated(id) => Some(*id),
|
||||
}
|
||||
}
|
||||
|
||||
fn rate(&mut self) -> (Uuid, Option<String>) {
|
||||
let id = Uuid::new_v4();
|
||||
let old_state = mem::replace(self, RatingState::Rated(id));
|
||||
let completion = match old_state {
|
||||
RatingState::Pending => None,
|
||||
RatingState::GeneratedCompletion(completion) => completion,
|
||||
RatingState::Rated(_) => None,
|
||||
};
|
||||
|
||||
(id, completion)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = RatingState::Pending;
|
||||
}
|
||||
|
||||
fn generated_completion(&mut self, generated_completion: Option<String>) {
|
||||
*self = RatingState::GeneratedCompletion(generated_completion);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PromptEditor<T> {
|
||||
@@ -70,7 +109,7 @@ pub struct PromptEditor<T> {
|
||||
_codegen_subscription: Subscription,
|
||||
editor_subscriptions: Vec<Subscription>,
|
||||
show_rate_limit_notice: bool,
|
||||
session_state: SessionState,
|
||||
rated: RatingState,
|
||||
_phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -101,11 +140,11 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
let left_gutter_width = gutter.full_width() + (gutter.margin / 2.0);
|
||||
let right_padding = editor_margins.right + RIGHT_PADDING;
|
||||
|
||||
let active_alternative = codegen.active_alternative().read(cx);
|
||||
let explanation = active_alternative
|
||||
.description
|
||||
.clone()
|
||||
.or_else(|| active_alternative.failure.clone());
|
||||
let explanation = codegen
|
||||
.active_alternative()
|
||||
.read(cx)
|
||||
.model_explanation
|
||||
.clone();
|
||||
|
||||
(left_gutter_width, right_padding, explanation)
|
||||
}
|
||||
@@ -139,7 +178,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
|
||||
if let Some(explanation) = &explanation {
|
||||
markdown.update(cx, |markdown, cx| {
|
||||
markdown.reset(SharedString::from(explanation), cx);
|
||||
markdown.reset(explanation.clone(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,7 +487,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
}
|
||||
|
||||
self.edited_since_done = true;
|
||||
self.session_state.completion = CompletionState::Pending;
|
||||
self.rated.reset();
|
||||
cx.notify();
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
@@ -520,179 +559,109 @@ impl<T: 'static> PromptEditor<T> {
|
||||
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle => {
|
||||
self.fire_started_telemetry(cx);
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}
|
||||
CodegenStatus::Pending => {}
|
||||
CodegenStatus::Done => {
|
||||
if self.edited_since_done {
|
||||
self.fire_started_telemetry(cx);
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
} else {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
}
|
||||
}
|
||||
CodegenStatus::Error(_) => {
|
||||
self.fire_started_telemetry(cx);
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fire_started_telemetry(&self, cx: &Context<Self>) {
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() else {
|
||||
fn thumbs_up(&mut self, _: &ThumbsUpResult, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.rated.is_pending() {
|
||||
self.toast("Still generating...", None, cx);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
let model_telemetry_id = model.model.telemetry_id();
|
||||
let model_provider_id = model.provider.id().to_string();
|
||||
if let Some(rating_id) = self.rated.rating_id() {
|
||||
self.toast("Already rated this completion", Some(rating_id), cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let (kind, language_name) = match &self.mode {
|
||||
let (rating_id, completion) = self.rated.rate();
|
||||
|
||||
let selected_text = match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
let codegen = codegen.read(cx);
|
||||
(
|
||||
"inline",
|
||||
codegen.language_name(cx).map(|name| name.to_string()),
|
||||
)
|
||||
codegen.read(cx).selected_text(cx).map(|s| s.to_string())
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => ("inline_terminal", None),
|
||||
PromptEditorMode::Terminal { .. } => None,
|
||||
};
|
||||
|
||||
let model_info = self.model_selector.read(cx).active_model(cx);
|
||||
let model_id = {
|
||||
let Some(configured_model) = model_info else {
|
||||
self.toast("No configured model", None, cx);
|
||||
return;
|
||||
};
|
||||
|
||||
configured_model.model.telemetry_id()
|
||||
};
|
||||
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
|
||||
telemetry::event!(
|
||||
"Assistant Started",
|
||||
session_id = self.session_state.session_id.to_string(),
|
||||
kind = kind,
|
||||
phase = "started",
|
||||
model = model_telemetry_id,
|
||||
model_provider = model_provider_id,
|
||||
language_name = language_name,
|
||||
"Inline Assistant Rated",
|
||||
rating = "positive",
|
||||
model = model_id,
|
||||
prompt = prompt,
|
||||
completion = completion,
|
||||
selected_text = selected_text,
|
||||
rating_id = rating_id.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
fn thumbs_up(&mut self, _: &ThumbsUpResult, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
match &self.session_state.completion {
|
||||
CompletionState::Pending => {
|
||||
self.toast("Can't rate, still generating...", None, cx);
|
||||
return;
|
||||
}
|
||||
CompletionState::Rated => {
|
||||
self.toast(
|
||||
"Already rated this completion",
|
||||
Some(self.session_state.session_id),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
CompletionState::Generated { completion_text } => {
|
||||
let model_info = self.model_selector.read(cx).active_model(cx);
|
||||
let (model_id, use_streaming_tools) = {
|
||||
let Some(configured_model) = model_info else {
|
||||
self.toast("No configured model", None, cx);
|
||||
return;
|
||||
};
|
||||
(
|
||||
configured_model.model.telemetry_id(),
|
||||
CodegenAlternative::use_streaming_tools(
|
||||
configured_model.model.as_ref(),
|
||||
cx,
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let selected_text = match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
codegen.read(cx).selected_text(cx).map(|s| s.to_string())
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => None,
|
||||
};
|
||||
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
|
||||
let kind = match &self.mode {
|
||||
PromptEditorMode::Buffer { .. } => "inline",
|
||||
PromptEditorMode::Terminal { .. } => "inline_terminal",
|
||||
};
|
||||
|
||||
telemetry::event!(
|
||||
"Inline Assistant Rated",
|
||||
rating = "positive",
|
||||
session_id = self.session_state.session_id.to_string(),
|
||||
kind = kind,
|
||||
model = model_id,
|
||||
prompt = prompt,
|
||||
completion = completion_text,
|
||||
selected_text = selected_text,
|
||||
use_streaming_tools
|
||||
);
|
||||
|
||||
self.session_state.completion = CompletionState::Rated;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn thumbs_down(&mut self, _: &ThumbsDownResult, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
match &self.session_state.completion {
|
||||
CompletionState::Pending => {
|
||||
self.toast("Can't rate, still generating...", None, cx);
|
||||
return;
|
||||
}
|
||||
CompletionState::Rated => {
|
||||
self.toast(
|
||||
"Already rated this completion",
|
||||
Some(self.session_state.session_id),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
CompletionState::Generated { completion_text } => {
|
||||
let model_info = self.model_selector.read(cx).active_model(cx);
|
||||
let (model_telemetry_id, use_streaming_tools) = {
|
||||
let Some(configured_model) = model_info else {
|
||||
self.toast("No configured model", None, cx);
|
||||
return;
|
||||
};
|
||||
(
|
||||
configured_model.model.telemetry_id(),
|
||||
CodegenAlternative::use_streaming_tools(
|
||||
configured_model.model.as_ref(),
|
||||
cx,
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let selected_text = match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
codegen.read(cx).selected_text(cx).map(|s| s.to_string())
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => None,
|
||||
};
|
||||
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
|
||||
let kind = match &self.mode {
|
||||
PromptEditorMode::Buffer { .. } => "inline",
|
||||
PromptEditorMode::Terminal { .. } => "inline_terminal",
|
||||
};
|
||||
|
||||
telemetry::event!(
|
||||
"Inline Assistant Rated",
|
||||
rating = "negative",
|
||||
session_id = self.session_state.session_id.to_string(),
|
||||
kind = kind,
|
||||
model = model_telemetry_id,
|
||||
prompt = prompt,
|
||||
completion = completion_text,
|
||||
selected_text = selected_text,
|
||||
use_streaming_tools
|
||||
);
|
||||
|
||||
self.session_state.completion = CompletionState::Rated;
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
if self.rated.is_pending() {
|
||||
self.toast("Still generating...", None, cx);
|
||||
return;
|
||||
}
|
||||
if let Some(rating_id) = self.rated.rating_id() {
|
||||
self.toast("Already rated this completion", Some(rating_id), cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let (rating_id, completion) = self.rated.rate();
|
||||
|
||||
let selected_text = match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
codegen.read(cx).selected_text(cx).map(|s| s.to_string())
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => None,
|
||||
};
|
||||
|
||||
let model_info = self.model_selector.read(cx).active_model(cx);
|
||||
let model_telemetry_id = {
|
||||
let Some(configured_model) = model_info else {
|
||||
self.toast("No configured model", None, cx);
|
||||
return;
|
||||
};
|
||||
|
||||
configured_model.model.telemetry_id()
|
||||
};
|
||||
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
|
||||
telemetry::event!(
|
||||
"Inline Assistant Rated",
|
||||
rating = "negative",
|
||||
model = model_telemetry_id,
|
||||
prompt = prompt,
|
||||
completion = completion,
|
||||
selected_text = selected_text,
|
||||
rating_id = rating_id.to_string()
|
||||
);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toast(&mut self, msg: &str, uuid: Option<Uuid>, cx: &mut Context<'_, PromptEditor<T>>) {
|
||||
@@ -826,8 +795,8 @@ impl<T: 'static> PromptEditor<T> {
|
||||
.into_any_element(),
|
||||
]
|
||||
} else {
|
||||
let show_rating_buttons = cx.has_flag::<InlineAssistantUseToolFeatureFlag>();
|
||||
let rated = matches!(self.session_state.completion, CompletionState::Rated);
|
||||
let show_rating_buttons = cx.has_flag::<InlineAssistRatingFeatureFlag>();
|
||||
let rated = self.rated.rating_id().is_some();
|
||||
|
||||
let accept = IconButton::new("accept", IconName::Check)
|
||||
.icon_color(Color::Info)
|
||||
@@ -1151,7 +1120,6 @@ impl PromptEditor<BufferCodegen> {
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_buffer: Entity<MultiBuffer>,
|
||||
codegen: Entity<BufferCodegen>,
|
||||
session_id: Uuid,
|
||||
fs: Arc<dyn Fs>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
@@ -1222,10 +1190,7 @@ impl PromptEditor<BufferCodegen> {
|
||||
editor_subscriptions: Vec::new(),
|
||||
show_rate_limit_notice: false,
|
||||
mode,
|
||||
session_state: SessionState {
|
||||
session_id,
|
||||
completion: CompletionState::Pending,
|
||||
},
|
||||
rated: RatingState::Pending,
|
||||
_phantom: Default::default(),
|
||||
};
|
||||
|
||||
@@ -1245,15 +1210,13 @@ impl PromptEditor<BufferCodegen> {
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
self.session_state.completion = CompletionState::Pending;
|
||||
self.rated.reset();
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(true));
|
||||
}
|
||||
CodegenStatus::Done => {
|
||||
let completion = codegen.read(cx).active_completion(cx);
|
||||
self.session_state.completion = CompletionState::Generated {
|
||||
completion_text: completion,
|
||||
};
|
||||
self.rated.generated_completion(completion);
|
||||
self.edited_since_done = false;
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
@@ -1309,7 +1272,6 @@ impl PromptEditor<TerminalCodegen> {
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_buffer: Entity<MultiBuffer>,
|
||||
codegen: Entity<TerminalCodegen>,
|
||||
session_id: Uuid,
|
||||
fs: Arc<dyn Fs>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
@@ -1375,10 +1337,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||
editor_subscriptions: Vec::new(),
|
||||
mode,
|
||||
show_rate_limit_notice: false,
|
||||
session_state: SessionState {
|
||||
session_id,
|
||||
completion: CompletionState::Pending,
|
||||
},
|
||||
rated: RatingState::Pending,
|
||||
_phantom: Default::default(),
|
||||
};
|
||||
this.count_lines(cx);
|
||||
@@ -1418,14 +1377,13 @@ impl PromptEditor<TerminalCodegen> {
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
self.session_state.completion = CompletionState::Pending;
|
||||
self.rated = RatingState::Pending;
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(true));
|
||||
}
|
||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
self.session_state.completion = CompletionState::Generated {
|
||||
completion_text: codegen.read(cx).completion(),
|
||||
};
|
||||
self.rated
|
||||
.generated_completion(codegen.read(cx).completion());
|
||||
self.edited_since_done = false;
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
|
||||
@@ -11,11 +11,9 @@ use language_model::{
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::prelude::*;
|
||||
use ui::{KeyBinding, ListItem, ListItemSpacing, prelude::*};
|
||||
use zed_actions::agent::OpenSettings;
|
||||
|
||||
use crate::ui::{ModelSelectorFooter, ModelSelectorHeader, ModelSelectorListItem};
|
||||
|
||||
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
|
||||
type GetActiveModel = Arc<dyn Fn(&App) -> Option<ConfiguredModel> + 'static>;
|
||||
|
||||
@@ -461,14 +459,28 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
is_focused: bool,
|
||||
selected: bool,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
match self.filtered_entries.get(ix)? {
|
||||
LanguageModelPickerEntry::Separator(title) => {
|
||||
Some(ModelSelectorHeader::new(title, ix > 1).into_any_element())
|
||||
}
|
||||
LanguageModelPickerEntry::Separator(title) => Some(
|
||||
div()
|
||||
.px_2()
|
||||
.pb_1()
|
||||
.when(ix > 1, |this| {
|
||||
this.mt_1()
|
||||
.pt_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
})
|
||||
.child(
|
||||
Label::new(title)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
LanguageModelPickerEntry::Model(model_info) => {
|
||||
let active_model = (self.get_active_model)(cx);
|
||||
let active_provider_id = active_model.as_ref().map(|m| m.provider.id());
|
||||
@@ -477,11 +489,35 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
let is_selected = Some(model_info.model.provider_id()) == active_provider_id
|
||||
&& Some(model_info.model.id()) == active_model_id;
|
||||
|
||||
let model_icon_color = if is_selected {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
};
|
||||
|
||||
Some(
|
||||
ModelSelectorListItem::new(ix, model_info.model.name().0)
|
||||
.is_focused(is_focused)
|
||||
.is_selected(is_selected)
|
||||
.icon(model_info.icon)
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(model_info.icon)
|
||||
.color(model_icon_color)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
.child(Label::new(model_info.model.name().0).truncate()),
|
||||
)
|
||||
.end_slot(div().pr_3().when(is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
@@ -491,15 +527,34 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
fn render_footer(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<gpui::AnyElement> {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
if !self.popover_styles {
|
||||
return None;
|
||||
}
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
Some(ModelSelectorFooter::new(OpenSettings.boxed_clone(), focus_handle).into_any_element())
|
||||
Some(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Button::new("configure", "Configure")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(&OpenSettings, &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(OpenSettings.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{CycleModeSelector, ManageProfiles, ToggleProfileSelector};
|
||||
use crate::{ManageProfiles, ToggleProfileSelector};
|
||||
use agent_settings::{
|
||||
AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles, builtin_profiles,
|
||||
};
|
||||
@@ -70,29 +70,6 @@ impl ProfileSelector {
|
||||
self.picker_handle.clone()
|
||||
}
|
||||
|
||||
pub fn cycle_profile(&mut self, cx: &mut Context<Self>) {
|
||||
if !self.provider.profiles_supported(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profiles = AgentProfile::available_profiles(cx);
|
||||
if profiles.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let current_profile_id = self.provider.profile_id(cx);
|
||||
let current_index = profiles
|
||||
.keys()
|
||||
.position(|id| id == ¤t_profile_id)
|
||||
.unwrap_or(0);
|
||||
|
||||
let next_index = (current_index + 1) % profiles.len();
|
||||
|
||||
if let Some((next_profile_id, _)) = profiles.get_index(next_index) {
|
||||
self.provider.set_profile(next_profile_id.clone(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_picker(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
@@ -186,29 +163,14 @@ impl Render for ProfileSelector {
|
||||
PickerPopoverMenu::new(
|
||||
picker,
|
||||
trigger_button,
|
||||
Tooltip::element({
|
||||
move |_window, cx| {
|
||||
let container = || h_flex().gap_1().justify_between();
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
container()
|
||||
.pb_1()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(Label::new("Cycle Through Profiles"))
|
||||
.child(KeyBinding::for_action_in(
|
||||
&CycleModeSelector,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(container().child(Label::new("Toggle Profile Menu")).child(
|
||||
KeyBinding::for_action_in(&ToggleProfileSelector, &focus_handle, cx),
|
||||
))
|
||||
.into_any()
|
||||
}
|
||||
}),
|
||||
move |_window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle Profile Menu",
|
||||
&ToggleProfileSelector,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use client::telemetry::Telemetry;
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
||||
use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelRequest};
|
||||
use std::time::Instant;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, report_assistant_event,
|
||||
};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct TerminalCodegen {
|
||||
pub status: CodegenStatus,
|
||||
pub telemetry: Option<Arc<Telemetry>>,
|
||||
terminal: Entity<Terminal>,
|
||||
generation: Task<()>,
|
||||
pub message_id: Option<String>,
|
||||
transaction: Option<TerminalTransaction>,
|
||||
session_id: Uuid,
|
||||
}
|
||||
|
||||
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
|
||||
|
||||
impl TerminalCodegen {
|
||||
pub fn new(terminal: Entity<Terminal>, session_id: Uuid) -> Self {
|
||||
pub fn new(terminal: Entity<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
|
||||
Self {
|
||||
terminal,
|
||||
telemetry,
|
||||
status: CodegenStatus::Idle,
|
||||
generation: Task::ready(()),
|
||||
message_id: None,
|
||||
transaction: None,
|
||||
session_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> Uuid {
|
||||
self.session_id
|
||||
}
|
||||
|
||||
pub fn start(&mut self, prompt_task: Task<LanguageModelRequest>, cx: &mut Context<Self>) {
|
||||
let Some(ConfiguredModel { model, .. }) =
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
@@ -40,15 +39,15 @@ impl TerminalCodegen {
|
||||
return;
|
||||
};
|
||||
|
||||
let anthropic_reporter = language_model::AnthropicEventReporter::new(&model, cx);
|
||||
let session_id = self.session_id;
|
||||
let model_telemetry_id = model.telemetry_id();
|
||||
let model_provider_id = model.provider_id().to_string();
|
||||
|
||||
let model_api_key = model.api_key(cx);
|
||||
let http_client = cx.http_client();
|
||||
let telemetry = self.telemetry.clone();
|
||||
self.status = CodegenStatus::Pending;
|
||||
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
|
||||
self.generation = cx.spawn(async move |this, cx| {
|
||||
let prompt = prompt_task.await;
|
||||
let model_telemetry_id = model.telemetry_id();
|
||||
let model_provider_id = model.provider_id();
|
||||
let response = model.stream_completion_text(prompt, cx).await;
|
||||
let generate = async {
|
||||
let message_id = response
|
||||
@@ -60,7 +59,7 @@ impl TerminalCodegen {
|
||||
|
||||
let task = cx.background_spawn({
|
||||
let message_id = message_id.clone();
|
||||
let anthropic_reporter = anthropic_reporter.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
async move {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
@@ -80,27 +79,24 @@ impl TerminalCodegen {
|
||||
let result = task.await;
|
||||
|
||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||
|
||||
telemetry::event!(
|
||||
"Assistant Responded",
|
||||
session_id = session_id.to_string(),
|
||||
kind = "inline_terminal",
|
||||
phase = "response",
|
||||
model = model_telemetry_id,
|
||||
model_provider = model_provider_id,
|
||||
language_name = Option::<&str>::None,
|
||||
message_id = message_id,
|
||||
response_latency = response_latency,
|
||||
error_message = error_message,
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id,
|
||||
phase: AssistantPhase::Response,
|
||||
model: model_telemetry_id,
|
||||
model_provider: model_provider_id.to_string(),
|
||||
response_latency,
|
||||
error_message,
|
||||
language_name: None,
|
||||
},
|
||||
telemetry,
|
||||
http_client,
|
||||
model_api_key,
|
||||
&executor,
|
||||
);
|
||||
|
||||
anthropic_reporter.report(language_model::AnthropicEventData {
|
||||
completion_type: language_model::AnthropicCompletionType::Terminal,
|
||||
event: language_model::AnthropicEventType::Response,
|
||||
language_name: None,
|
||||
message_id,
|
||||
});
|
||||
|
||||
result?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
use agent::HistoryStore;
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use client::telemetry::Telemetry;
|
||||
use cloud_llm_client::CompletionIntent;
|
||||
use collections::{HashMap, VecDeque};
|
||||
use editor::{MultiBuffer, actions::SelectAll};
|
||||
@@ -17,19 +17,24 @@ use gpui::{App, Entity, Focusable, Global, Subscription, Task, UpdateGlobal, Wea
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
Role, report_anthropic_event,
|
||||
Role, report_assistant_event,
|
||||
};
|
||||
use project::Project;
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use std::sync::Arc;
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use terminal_view::TerminalView;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use uuid::Uuid;
|
||||
use workspace::{Toast, Workspace, notifications::NotificationId};
|
||||
|
||||
pub fn init(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, cx: &mut App) {
|
||||
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder));
|
||||
pub fn init(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
|
||||
}
|
||||
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
@@ -39,6 +44,7 @@ pub struct TerminalInlineAssistant {
|
||||
next_assist_id: TerminalInlineAssistId,
|
||||
assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>,
|
||||
prompt_history: VecDeque<String>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
}
|
||||
@@ -46,11 +52,16 @@ pub struct TerminalInlineAssistant {
|
||||
impl Global for TerminalInlineAssistant {}
|
||||
|
||||
impl TerminalInlineAssistant {
|
||||
pub fn new(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>) -> Self {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
next_assist_id: TerminalInlineAssistId::default(),
|
||||
assists: HashMap::default(),
|
||||
prompt_history: VecDeque::default(),
|
||||
telemetry: Some(telemetry),
|
||||
fs,
|
||||
prompt_builder,
|
||||
}
|
||||
@@ -69,14 +80,13 @@ impl TerminalInlineAssistant {
|
||||
) {
|
||||
let terminal = terminal_view.read(cx).terminal().clone();
|
||||
let assist_id = self.next_assist_id.post_inc();
|
||||
let session_id = Uuid::new_v4();
|
||||
let prompt_buffer = cx.new(|cx| {
|
||||
MultiBuffer::singleton(
|
||||
cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let codegen = cx.new(|_| TerminalCodegen::new(terminal, session_id));
|
||||
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
PromptEditor::new_terminal(
|
||||
@@ -84,7 +94,6 @@ impl TerminalInlineAssistant {
|
||||
self.prompt_history.clone(),
|
||||
prompt_buffer.clone(),
|
||||
codegen,
|
||||
session_id,
|
||||
self.fs.clone(),
|
||||
thread_store.clone(),
|
||||
prompt_store.clone(),
|
||||
@@ -300,45 +309,27 @@ impl TerminalInlineAssistant {
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
{
|
||||
let codegen = assist.codegen.read(cx);
|
||||
let session_id = codegen.session_id();
|
||||
let message_id = codegen.message_id.clone();
|
||||
let model_telemetry_id = model.telemetry_id();
|
||||
let model_provider_id = model.provider_id().to_string();
|
||||
|
||||
let (phase, event_type, anthropic_event_type) = if undo {
|
||||
(
|
||||
"rejected",
|
||||
"Assistant Response Rejected",
|
||||
language_model::AnthropicEventType::Reject,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"accepted",
|
||||
"Assistant Response Accepted",
|
||||
language_model::AnthropicEventType::Accept,
|
||||
)
|
||||
};
|
||||
|
||||
// Fire Zed telemetry
|
||||
telemetry::event!(
|
||||
event_type,
|
||||
kind = "inline_terminal",
|
||||
phase = phase,
|
||||
model = model_telemetry_id,
|
||||
model_provider = model_provider_id,
|
||||
message_id = message_id,
|
||||
session_id = session_id,
|
||||
);
|
||||
|
||||
report_anthropic_event(
|
||||
&model,
|
||||
language_model::AnthropicEventData {
|
||||
completion_type: language_model::AnthropicCompletionType::Terminal,
|
||||
event: anthropic_event_type,
|
||||
let executor = cx.background_executor().clone();
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::InlineTerminal,
|
||||
message_id: codegen.message_id.clone(),
|
||||
phase: if undo {
|
||||
AssistantPhase::Rejected
|
||||
} else {
|
||||
AssistantPhase::Accepted
|
||||
},
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: None,
|
||||
message_id,
|
||||
},
|
||||
cx,
|
||||
codegen.telemetry.clone(),
|
||||
cx.http_client(),
|
||||
model.api_key(cx),
|
||||
&executor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3324,6 +3324,7 @@ mod tests {
|
||||
let mut text_thread = TextThread::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
|
||||
@@ -4,8 +4,8 @@ mod burn_mode_tooltip;
|
||||
mod claude_code_onboarding_modal;
|
||||
mod end_trial_upsell;
|
||||
mod hold_for_default;
|
||||
mod model_selector_components;
|
||||
mod onboarding_modal;
|
||||
mod unavailable_editing_tooltip;
|
||||
mod usage_callout;
|
||||
|
||||
pub use acp_onboarding_modal::*;
|
||||
@@ -14,6 +14,6 @@ pub use burn_mode_tooltip::*;
|
||||
pub use claude_code_onboarding_modal::*;
|
||||
pub use end_trial_upsell::*;
|
||||
pub use hold_for_default::*;
|
||||
pub use model_selector_components::*;
|
||||
pub use onboarding_modal::*;
|
||||
pub use unavailable_editing_tooltip::*;
|
||||
pub use usage_callout::*;
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
use gpui::{Action, FocusHandle, prelude::*};
|
||||
use ui::{KeyBinding, ListItem, ListItemSpacing, prelude::*};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModelSelectorHeader {
|
||||
title: SharedString,
|
||||
has_border: bool,
|
||||
}
|
||||
|
||||
impl ModelSelectorHeader {
|
||||
pub fn new(title: impl Into<SharedString>, has_border: bool) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
has_border,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ModelSelectorHeader {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
div()
|
||||
.px_2()
|
||||
.pb_1()
|
||||
.when(self.has_border, |this| {
|
||||
this.mt_1()
|
||||
.pt_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
})
|
||||
.child(
|
||||
Label::new(self.title)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModelSelectorListItem {
|
||||
index: usize,
|
||||
title: SharedString,
|
||||
icon: Option<IconName>,
|
||||
is_selected: bool,
|
||||
is_focused: bool,
|
||||
}
|
||||
|
||||
impl ModelSelectorListItem {
|
||||
pub fn new(index: usize, title: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
index,
|
||||
title: title.into(),
|
||||
icon: None,
|
||||
is_selected: false,
|
||||
is_focused: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: IconName) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_selected(mut self, is_selected: bool) -> Self {
|
||||
self.is_selected = is_selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_focused(mut self, is_focused: bool) -> Self {
|
||||
self.is_focused = is_focused;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ModelSelectorListItem {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let model_icon_color = if self.is_selected {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
};
|
||||
|
||||
ListItem::new(self.index)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(self.is_focused)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.when_some(self.icon, |this, icon| {
|
||||
this.child(
|
||||
Icon::new(icon)
|
||||
.color(model_icon_color)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})
|
||||
.child(Label::new(self.title).truncate()),
|
||||
)
|
||||
.end_slot(div().pr_2().when(self.is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModelSelectorFooter {
|
||||
action: Box<dyn Action>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ModelSelectorFooter {
|
||||
pub fn new(action: Box<dyn Action>, focus_handle: FocusHandle) -> Self {
|
||||
Self {
|
||||
action,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ModelSelectorFooter {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let action = self.action;
|
||||
let focus_handle = self.focus_handle;
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Button::new("configure", "Configure")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(action.as_ref(), &focus_handle, cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
29
crates/agent_ui/src/ui/unavailable_editing_tooltip.rs
Normal file
29
crates/agent_ui/src/ui/unavailable_editing_tooltip.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use gpui::{Context, IntoElement, Render, Window};
|
||||
use ui::{prelude::*, tooltip_container};
|
||||
|
||||
pub struct UnavailableEditingTooltip {
|
||||
agent_name: SharedString,
|
||||
}
|
||||
|
||||
impl UnavailableEditingTooltip {
|
||||
pub fn new(agent_name: SharedString) -> Self {
|
||||
Self { agent_name }
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for UnavailableEditingTooltip {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
tooltip_container(cx, |this, _| {
|
||||
this.child(Label::new("Unavailable Editing")).child(
|
||||
div().max_w_64().child(
|
||||
Label::new(format!(
|
||||
"Editing previous messages is not available for {} yet.",
|
||||
self.agent_name
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
[package]
|
||||
name = "agent_ui_v2"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/agent_ui_v2.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
agent.workspace = true
|
||||
agent_servers.workspace = true
|
||||
agent_settings.workspace = true
|
||||
agent_ui.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_text_thread.workspace = true
|
||||
chrono.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
project.workspace = true
|
||||
prompt_store.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
LICENSE-GPL
|
||||
@@ -1,287 +0,0 @@
|
||||
use agent::{HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer};
|
||||
use agent_servers::AgentServer;
|
||||
use agent_settings::AgentSettings;
|
||||
use agent_ui::acp::AcpThreadView;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Entity, EventEmitter, Focusable, Pixels, SharedString, Subscription, WeakEntity, prelude::*,
|
||||
};
|
||||
use project::Project;
|
||||
use prompt_store::PromptStore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::DockSide;
|
||||
use settings::Settings as _;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use ui::{Tab, Tooltip, prelude::*};
|
||||
use workspace::{
|
||||
Workspace,
|
||||
dock::{ClosePane, MinimizePane, UtilityPane, UtilityPanePosition},
|
||||
utility_pane::UtilityPaneSlot,
|
||||
};
|
||||
|
||||
pub const DEFAULT_UTILITY_PANE_WIDTH: Pixels = gpui::px(400.0);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum SerializedHistoryEntryId {
|
||||
AcpThread(String),
|
||||
TextThread(String),
|
||||
}
|
||||
|
||||
impl From<HistoryEntryId> for SerializedHistoryEntryId {
|
||||
fn from(id: HistoryEntryId) -> Self {
|
||||
match id {
|
||||
HistoryEntryId::AcpThread(session_id) => {
|
||||
SerializedHistoryEntryId::AcpThread(session_id.0.to_string())
|
||||
}
|
||||
HistoryEntryId::TextThread(path) => {
|
||||
SerializedHistoryEntryId::TextThread(path.to_string_lossy().to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SerializedAgentThreadPane {
|
||||
pub expanded: bool,
|
||||
pub width: Option<Pixels>,
|
||||
pub thread_id: Option<SerializedHistoryEntryId>,
|
||||
}
|
||||
|
||||
pub enum AgentsUtilityPaneEvent {
|
||||
StateChanged,
|
||||
}
|
||||
|
||||
impl EventEmitter<AgentsUtilityPaneEvent> for AgentThreadPane {}
|
||||
impl EventEmitter<MinimizePane> for AgentThreadPane {}
|
||||
impl EventEmitter<ClosePane> for AgentThreadPane {}
|
||||
|
||||
struct ActiveThreadView {
|
||||
view: Entity<AcpThreadView>,
|
||||
thread_id: HistoryEntryId,
|
||||
_notify: Subscription,
|
||||
}
|
||||
|
||||
pub struct AgentThreadPane {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
expanded: bool,
|
||||
width: Option<Pixels>,
|
||||
thread_view: Option<ActiveThreadView>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
}
|
||||
|
||||
impl AgentThreadPane {
|
||||
pub fn new(workspace: WeakEntity<Workspace>, cx: &mut ui::Context<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
Self {
|
||||
focus_handle,
|
||||
expanded: false,
|
||||
width: None,
|
||||
thread_view: None,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn thread_id(&self) -> Option<HistoryEntryId> {
|
||||
self.thread_view.as_ref().map(|tv| tv.thread_id.clone())
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> SerializedAgentThreadPane {
|
||||
SerializedAgentThreadPane {
|
||||
expanded: self.expanded,
|
||||
width: self.width,
|
||||
thread_id: self.thread_id().map(SerializedHistoryEntryId::from),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_thread(
|
||||
&mut self,
|
||||
entry: HistoryEntry,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let thread_id = entry.id();
|
||||
|
||||
let resume_thread = match &entry {
|
||||
HistoryEntry::AcpThread(thread) => Some(thread.clone()),
|
||||
HistoryEntry::TextThread(_) => None,
|
||||
};
|
||||
|
||||
let agent: Rc<dyn AgentServer> = Rc::new(NativeAgentServer::new(fs, history_store.clone()));
|
||||
|
||||
let thread_view = cx.new(|cx| {
|
||||
AcpThreadView::new(
|
||||
agent,
|
||||
resume_thread,
|
||||
None,
|
||||
workspace,
|
||||
project,
|
||||
history_store,
|
||||
prompt_store,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let notify = cx.observe(&thread_view, |_, _, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
self.thread_view = Some(ActiveThreadView {
|
||||
view: thread_view,
|
||||
thread_id,
|
||||
_notify: notify,
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn title(&self, cx: &App) -> SharedString {
|
||||
if let Some(active_thread_view) = &self.thread_view {
|
||||
let thread_view = active_thread_view.view.read(cx);
|
||||
if let Some(thread) = thread_view.thread() {
|
||||
let title = thread.read(cx).title();
|
||||
if !title.is_empty() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
thread_view.title(cx)
|
||||
} else {
|
||||
"Thread".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let position = self.position(window, cx);
|
||||
let slot = match position {
|
||||
UtilityPanePosition::Left => UtilityPaneSlot::Left,
|
||||
UtilityPanePosition::Right => UtilityPaneSlot::Right,
|
||||
};
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let toggle_icon = self.toggle_icon(cx);
|
||||
let title = self.title(cx);
|
||||
|
||||
let pane_toggle_button = |workspace: WeakEntity<Workspace>| {
|
||||
IconButton::new("toggle_utility_pane", toggle_icon)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Toggle Agent Pane"))
|
||||
.on_click(move |_, window, cx| {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.toggle_utility_pane(slot, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("utility-pane-header")
|
||||
.w_full()
|
||||
.h(Tab::container_height(cx))
|
||||
.px_1p5()
|
||||
.gap(DynamicSpacing::Base06.rems(cx))
|
||||
.when(slot == UtilityPaneSlot::Right, |this| {
|
||||
this.flex_row_reverse()
|
||||
})
|
||||
.flex_none()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(pane_toggle_button(workspace))
|
||||
.child(
|
||||
h_flex()
|
||||
.size_full()
|
||||
.min_w_0()
|
||||
.gap_1()
|
||||
.map(|this| {
|
||||
if slot == UtilityPaneSlot::Right {
|
||||
this.flex_row_reverse().justify_start()
|
||||
} else {
|
||||
this.justify_between()
|
||||
}
|
||||
})
|
||||
.child(Label::new(title).truncate())
|
||||
.child(
|
||||
IconButton::new("close_btn", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::text("Close Agent Pane"))
|
||||
.on_click(cx.listener(|this, _: &gpui::ClickEvent, _window, cx| {
|
||||
cx.emit(ClosePane);
|
||||
this.thread_view = None;
|
||||
cx.notify()
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AgentThreadPane {
|
||||
fn focus_handle(&self, cx: &ui::App) -> gpui::FocusHandle {
|
||||
if let Some(thread_view) = &self.thread_view {
|
||||
thread_view.view.focus_handle(cx)
|
||||
} else {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UtilityPane for AgentThreadPane {
|
||||
fn position(&self, _window: &Window, cx: &App) -> UtilityPanePosition {
|
||||
match AgentSettings::get_global(cx).agents_panel_dock {
|
||||
DockSide::Left => UtilityPanePosition::Left,
|
||||
DockSide::Right => UtilityPanePosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_icon(&self, _cx: &App) -> IconName {
|
||||
IconName::Thread
|
||||
}
|
||||
|
||||
fn expanded(&self, _cx: &App) -> bool {
|
||||
self.expanded
|
||||
}
|
||||
|
||||
fn set_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
|
||||
self.expanded = expanded;
|
||||
cx.emit(AgentsUtilityPaneEvent::StateChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn width(&self, _cx: &App) -> Pixels {
|
||||
self.width.unwrap_or(DEFAULT_UTILITY_PANE_WIDTH)
|
||||
}
|
||||
|
||||
fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>) {
|
||||
self.width = width;
|
||||
cx.emit(AgentsUtilityPaneEvent::StateChanged);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AgentThreadPane {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let content = if let Some(thread_view) = &self.thread_view {
|
||||
div().size_full().child(thread_view.view.clone())
|
||||
} else {
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.child(Label::new("Select a thread to view details").size(LabelSize::Default))
|
||||
};
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.render_header(window, cx))
|
||||
.child(content)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
mod agent_thread_pane;
|
||||
mod thread_history;
|
||||
|
||||
pub mod agents_panel;
|
||||
@@ -1,437 +0,0 @@
|
||||
use agent::{HistoryEntry, HistoryEntryId, HistoryStore};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use assistant_text_thread::TextThreadStore;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, AsyncWindowContext, Entity, EventEmitter, Focusable, Pixels, Subscription, Task,
|
||||
WeakEntity, actions, prelude::*,
|
||||
};
|
||||
use project::Project;
|
||||
use prompt_store::{PromptBuilder, PromptStore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use std::sync::Arc;
|
||||
use ui::{App, Context, IconName, IntoElement, ParentElement, Render, Styled, Window};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
Panel, Workspace,
|
||||
dock::{ClosePane, DockPosition, PanelEvent, UtilityPane},
|
||||
utility_pane::{UtilityPaneSlot, utility_slot_for_dock_position},
|
||||
};
|
||||
|
||||
use crate::agent_thread_pane::{
|
||||
AgentThreadPane, AgentsUtilityPaneEvent, SerializedAgentThreadPane, SerializedHistoryEntryId,
|
||||
};
|
||||
use crate::thread_history::{AcpThreadHistory, ThreadHistoryEvent};
|
||||
|
||||
const AGENTS_PANEL_KEY: &str = "agents_panel";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct SerializedAgentsPanel {
|
||||
width: Option<Pixels>,
|
||||
pane: Option<SerializedAgentThreadPane>,
|
||||
}
|
||||
|
||||
actions!(
|
||||
agents,
|
||||
[
|
||||
/// Toggle the visibility of the agents panel.
|
||||
ToggleAgentsPanel
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action(|workspace, _: &ToggleAgentsPanel, window, cx| {
|
||||
workspace.toggle_panel_focus::<AgentsPanel>(window, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct AgentsPanel {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
agent_thread_pane: Option<Entity<AgentThreadPane>>,
|
||||
history: Entity<AcpThreadHistory>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
width: Option<Pixels>,
|
||||
pending_serialization: Task<Option<()>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl AgentsPanel {
|
||||
pub fn load(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<Entity<Self>, anyhow::Error>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let serialized_panel = cx
|
||||
.background_spawn(async move {
|
||||
KEY_VALUE_STORE
|
||||
.read_kvp(AGENTS_PANEL_KEY)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|panel| {
|
||||
serde_json::from_str::<SerializedAgentsPanel>(&panel).ok()
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
let (fs, project, prompt_builder) = workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let project = workspace.project().clone();
|
||||
let prompt_builder = PromptBuilder::load(fs.clone(), false, cx);
|
||||
(fs, project, prompt_builder)
|
||||
})?;
|
||||
|
||||
let text_thread_store = workspace
|
||||
.update(cx, |_, cx| {
|
||||
TextThreadStore::new(
|
||||
project.clone(),
|
||||
prompt_builder.clone(),
|
||||
Default::default(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let prompt_store = workspace
|
||||
.update(cx, |_, cx| PromptStore::global(cx))?
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
workspace.update_in(cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut panel = Self::new(
|
||||
workspace.clone(),
|
||||
fs,
|
||||
project,
|
||||
prompt_store,
|
||||
text_thread_store,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.width = serialized_panel.width;
|
||||
if let Some(serialized_pane) = serialized_panel.pane {
|
||||
panel.restore_utility_pane(serialized_pane, window, cx);
|
||||
}
|
||||
}
|
||||
panel
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
fs: Arc<dyn Fs>,
|
||||
project: Entity<Project>,
|
||||
prompt_store: Option<Entity<PromptStore>>,
|
||||
text_thread_store: Entity<TextThreadStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut ui::Context<Self>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
|
||||
let history = cx.new(|cx| AcpThreadHistory::new(history_store.clone(), window, cx));
|
||||
|
||||
let this = cx.weak_entity();
|
||||
let subscriptions = vec![
|
||||
cx.subscribe_in(&history, window, Self::handle_history_event),
|
||||
cx.on_flags_ready(move |_, cx| {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
];
|
||||
|
||||
Self {
|
||||
focus_handle,
|
||||
workspace,
|
||||
project,
|
||||
agent_thread_pane: None,
|
||||
history,
|
||||
history_store,
|
||||
prompt_store,
|
||||
fs,
|
||||
width: None,
|
||||
pending_serialization: Task::ready(None),
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_utility_pane(
|
||||
&mut self,
|
||||
serialized_pane: SerializedAgentThreadPane,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(thread_id) = &serialized_pane.thread_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let entry = self
|
||||
.history_store
|
||||
.read(cx)
|
||||
.entries()
|
||||
.find(|e| match (&e.id(), thread_id) {
|
||||
(
|
||||
HistoryEntryId::AcpThread(session_id),
|
||||
SerializedHistoryEntryId::AcpThread(id),
|
||||
) => session_id.to_string() == *id,
|
||||
(HistoryEntryId::TextThread(path), SerializedHistoryEntryId::TextThread(id)) => {
|
||||
path.to_string_lossy() == *id
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if let Some(entry) = entry {
|
||||
self.open_thread(
|
||||
entry,
|
||||
serialized_pane.expanded,
|
||||
serialized_pane.width,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_utility_pane_event(
|
||||
&mut self,
|
||||
_utility_pane: Entity<AgentThreadPane>,
|
||||
event: &AgentsUtilityPaneEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
AgentsUtilityPaneEvent::StateChanged => {
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_close_pane_event(
|
||||
&mut self,
|
||||
_utility_pane: Entity<AgentThreadPane>,
|
||||
_event: &ClosePane,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.agent_thread_pane = None;
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_history_event(
|
||||
&mut self,
|
||||
_history: &Entity<AcpThreadHistory>,
|
||||
event: &ThreadHistoryEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ThreadHistoryEvent::Open(entry) => {
|
||||
self.open_thread(entry.clone(), true, None, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_thread(
|
||||
&mut self,
|
||||
entry: HistoryEntry,
|
||||
expanded: bool,
|
||||
width: Option<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let entry_id = entry.id();
|
||||
|
||||
if let Some(existing_pane) = &self.agent_thread_pane {
|
||||
if existing_pane.read(cx).thread_id() == Some(entry_id) {
|
||||
existing_pane.update(cx, |pane, cx| {
|
||||
pane.set_expanded(true, cx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let project = self.project.clone();
|
||||
let history_store = self.history_store.clone();
|
||||
let prompt_store = self.prompt_store.clone();
|
||||
|
||||
let agent_thread_pane = cx.new(|cx| {
|
||||
let mut pane = AgentThreadPane::new(workspace.clone(), cx);
|
||||
pane.open_thread(
|
||||
entry,
|
||||
fs,
|
||||
workspace.clone(),
|
||||
project,
|
||||
history_store,
|
||||
prompt_store,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if let Some(width) = width {
|
||||
pane.set_width(Some(width), cx);
|
||||
}
|
||||
pane.set_expanded(expanded, cx);
|
||||
pane
|
||||
});
|
||||
|
||||
let state_subscription = cx.subscribe(&agent_thread_pane, Self::handle_utility_pane_event);
|
||||
let close_subscription = cx.subscribe(&agent_thread_pane, Self::handle_close_pane_event);
|
||||
|
||||
self._subscriptions.push(state_subscription);
|
||||
self._subscriptions.push(close_subscription);
|
||||
|
||||
let slot = self.utility_slot(window, cx);
|
||||
let panel_id = cx.entity_id();
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.register_utility_pane(slot, panel_id, agent_thread_pane.clone(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
self.agent_thread_pane = Some(agent_thread_pane);
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn utility_slot(&self, window: &Window, cx: &App) -> UtilityPaneSlot {
|
||||
let position = self.position(window, cx);
|
||||
utility_slot_for_dock_position(position)
|
||||
}
|
||||
|
||||
fn re_register_utility_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(pane) = &self.agent_thread_pane {
|
||||
let slot = self.utility_slot(window, cx);
|
||||
let panel_id = cx.entity_id();
|
||||
let pane = pane.clone();
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.register_utility_pane(slot, panel_id, pane, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize(&mut self, cx: &mut Context<Self>) {
|
||||
let width = self.width;
|
||||
let pane = self
|
||||
.agent_thread_pane
|
||||
.as_ref()
|
||||
.map(|pane| pane.read(cx).serialize());
|
||||
|
||||
self.pending_serialization = cx.background_spawn(async move {
|
||||
KEY_VALUE_STORE
|
||||
.write_kvp(
|
||||
AGENTS_PANEL_KEY.into(),
|
||||
serde_json::to_string(&SerializedAgentsPanel { width, pane }).unwrap(),
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for AgentsPanel {}
|
||||
|
||||
impl Focusable for AgentsPanel {
|
||||
fn focus_handle(&self, _cx: &ui::App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for AgentsPanel {
|
||||
fn persistent_name() -> &'static str {
|
||||
"AgentsPanel"
|
||||
}
|
||||
|
||||
fn panel_key() -> &'static str {
|
||||
AGENTS_PANEL_KEY
|
||||
}
|
||||
|
||||
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
|
||||
match AgentSettings::get_global(cx).agents_panel_dock {
|
||||
settings::DockSide::Left => DockPosition::Left,
|
||||
settings::DockSide::Right => DockPosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
fn position_is_valid(&self, position: DockPosition) -> bool {
|
||||
position != DockPosition::Bottom
|
||||
}
|
||||
|
||||
fn set_position(
|
||||
&mut self,
|
||||
position: DockPosition,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.agent.get_or_insert_default().agents_panel_dock = Some(match position {
|
||||
DockPosition::Left => settings::DockSide::Left,
|
||||
DockPosition::Right | DockPosition::Bottom => settings::DockSide::Right,
|
||||
});
|
||||
});
|
||||
self.re_register_utility_pane(window, cx);
|
||||
}
|
||||
|
||||
fn size(&self, window: &Window, cx: &App) -> Pixels {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left | DockPosition::Right => {
|
||||
self.width.unwrap_or(settings.default_width)
|
||||
}
|
||||
DockPosition::Bottom => self.width.unwrap_or(settings.default_height),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left | DockPosition::Right => self.width = size,
|
||||
DockPosition::Bottom => {}
|
||||
}
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
|
||||
(self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAgentTwo)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
|
||||
Some("Agents Panel")
|
||||
}
|
||||
|
||||
fn toggle_action(&self) -> Box<dyn Action> {
|
||||
Box::new(ToggleAgentsPanel)
|
||||
}
|
||||
|
||||
fn activation_priority(&self) -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
fn enabled(&self, cx: &App) -> bool {
|
||||
AgentSettings::get_global(cx).enabled(cx) && cx.has_flag::<AgentV2FeatureFlag>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AgentsPanel {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
gpui::div().size_full().child(self.history.clone())
|
||||
}
|
||||
}
|
||||
@@ -1,735 +0,0 @@
|
||||
use agent::{HistoryEntry, HistoryStore};
|
||||
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
App, Entity, EventEmitter, FocusHandle, Focusable, ScrollStrategy, Task,
|
||||
UniformListScrollHandle, Window, actions, uniform_list,
|
||||
};
|
||||
use std::{fmt::Display, ops::Range};
|
||||
use text::Bias;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip, WithScrollbar,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
actions!(
|
||||
agents,
|
||||
[
|
||||
/// Removes all thread history.
|
||||
RemoveHistory,
|
||||
/// Removes the currently selected thread.
|
||||
RemoveSelectedThread,
|
||||
]
|
||||
);
|
||||
|
||||
pub struct AcpThreadHistory {
|
||||
pub(crate) history_store: Entity<HistoryStore>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_index: usize,
|
||||
hovered_index: Option<usize>,
|
||||
search_editor: Entity<Editor>,
|
||||
search_query: SharedString,
|
||||
visible_items: Vec<ListItemType>,
|
||||
local_timezone: UtcOffset,
|
||||
confirming_delete_history: bool,
|
||||
_update_task: Task<()>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
}
|
||||
|
||||
enum ListItemType {
|
||||
BucketSeparator(TimeBucket),
|
||||
Entry {
|
||||
entry: HistoryEntry,
|
||||
format: EntryTimeFormat,
|
||||
},
|
||||
SearchResult {
|
||||
entry: HistoryEntry,
|
||||
positions: Vec<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ListItemType {
|
||||
fn history_entry(&self) -> Option<&HistoryEntry> {
|
||||
match self {
|
||||
ListItemType::Entry { entry, .. } => Some(entry),
|
||||
ListItemType::SearchResult { entry, .. } => Some(entry),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum ThreadHistoryEvent {
|
||||
Open(HistoryEntry),
|
||||
}
|
||||
|
||||
impl EventEmitter<ThreadHistoryEvent> for AcpThreadHistory {}
|
||||
|
||||
impl AcpThreadHistory {
|
||||
pub fn new(
|
||||
history_store: Entity<agent::HistoryStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let search_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
editor.set_placeholder_text("Search threads...", window, cx);
|
||||
editor
|
||||
});
|
||||
|
||||
let search_editor_subscription =
|
||||
cx.subscribe(&search_editor, |this, search_editor, event, cx| {
|
||||
if let EditorEvent::BufferEdited = event {
|
||||
let query = search_editor.read(cx).text(cx);
|
||||
if this.search_query != query {
|
||||
this.search_query = query.into();
|
||||
this.update_visible_items(false, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let history_store_subscription = cx.observe(&history_store, |this, _, cx| {
|
||||
this.update_visible_items(true, cx);
|
||||
});
|
||||
|
||||
let scroll_handle = UniformListScrollHandle::default();
|
||||
|
||||
let mut this = Self {
|
||||
history_store,
|
||||
scroll_handle,
|
||||
selected_index: 0,
|
||||
hovered_index: None,
|
||||
visible_items: Default::default(),
|
||||
search_editor,
|
||||
local_timezone: UtcOffset::from_whole_seconds(
|
||||
chrono::Local::now().offset().local_minus_utc(),
|
||||
)
|
||||
.unwrap(),
|
||||
search_query: SharedString::default(),
|
||||
confirming_delete_history: false,
|
||||
_subscriptions: vec![search_editor_subscription, history_store_subscription],
|
||||
_update_task: Task::ready(()),
|
||||
};
|
||||
this.update_visible_items(false, cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn update_visible_items(&mut self, preserve_selected_item: bool, cx: &mut Context<Self>) {
|
||||
let entries = self
|
||||
.history_store
|
||||
.update(cx, |store, _| store.entries().collect());
|
||||
let new_list_items = if self.search_query.is_empty() {
|
||||
self.add_list_separators(entries, cx)
|
||||
} else {
|
||||
self.filter_search_results(entries, cx)
|
||||
};
|
||||
let selected_history_entry = if preserve_selected_item {
|
||||
self.selected_history_entry().cloned()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self._update_task = cx.spawn(async move |this, cx| {
|
||||
let new_visible_items = new_list_items.await;
|
||||
this.update(cx, |this, cx| {
|
||||
let new_selected_index = if let Some(history_entry) = selected_history_entry {
|
||||
let history_entry_id = history_entry.id();
|
||||
new_visible_items
|
||||
.iter()
|
||||
.position(|visible_entry| {
|
||||
visible_entry
|
||||
.history_entry()
|
||||
.is_some_and(|entry| entry.id() == history_entry_id)
|
||||
})
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
this.visible_items = new_visible_items;
|
||||
this.set_selected_index(new_selected_index, Bias::Right, cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
fn add_list_separators(&self, entries: Vec<HistoryEntry>, cx: &App) -> Task<Vec<ListItemType>> {
|
||||
cx.background_spawn(async move {
|
||||
let mut items = Vec::with_capacity(entries.len() + 1);
|
||||
let mut bucket = None;
|
||||
let today = Local::now().naive_local().date();
|
||||
|
||||
for entry in entries.into_iter() {
|
||||
let entry_date = entry
|
||||
.updated_at()
|
||||
.with_timezone(&Local)
|
||||
.naive_local()
|
||||
.date();
|
||||
let entry_bucket = TimeBucket::from_dates(today, entry_date);
|
||||
|
||||
if Some(entry_bucket) != bucket {
|
||||
bucket = Some(entry_bucket);
|
||||
items.push(ListItemType::BucketSeparator(entry_bucket));
|
||||
}
|
||||
|
||||
items.push(ListItemType::Entry {
|
||||
entry,
|
||||
format: entry_bucket.into(),
|
||||
});
|
||||
}
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
fn filter_search_results(
|
||||
&self,
|
||||
entries: Vec<HistoryEntry>,
|
||||
cx: &App,
|
||||
) -> Task<Vec<ListItemType>> {
|
||||
let query = self.search_query.clone();
|
||||
cx.background_spawn({
|
||||
let executor = cx.background_executor().clone();
|
||||
async move {
|
||||
let mut candidates = Vec::with_capacity(entries.len());
|
||||
|
||||
for (idx, entry) in entries.iter().enumerate() {
|
||||
candidates.push(StringMatchCandidate::new(idx, entry.title()));
|
||||
}
|
||||
|
||||
const MAX_MATCHES: usize = 100;
|
||||
|
||||
let matches = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
false,
|
||||
true,
|
||||
MAX_MATCHES,
|
||||
&Default::default(),
|
||||
executor,
|
||||
)
|
||||
.await;
|
||||
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|search_match| ListItemType::SearchResult {
|
||||
entry: entries[search_match.candidate_id].clone(),
|
||||
positions: search_match.positions,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn search_produced_no_matches(&self) -> bool {
|
||||
self.visible_items.is_empty() && !self.search_query.is_empty()
|
||||
}
|
||||
|
||||
fn selected_history_entry(&self) -> Option<&HistoryEntry> {
|
||||
self.get_history_entry(self.selected_index)
|
||||
}
|
||||
|
||||
fn get_history_entry(&self, visible_items_ix: usize) -> Option<&HistoryEntry> {
|
||||
self.visible_items.get(visible_items_ix)?.history_entry()
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, mut index: usize, bias: Bias, cx: &mut Context<Self>) {
|
||||
if self.visible_items.is_empty() {
|
||||
self.selected_index = 0;
|
||||
return;
|
||||
}
|
||||
while matches!(
|
||||
self.visible_items.get(index),
|
||||
None | Some(ListItemType::BucketSeparator(..))
|
||||
) {
|
||||
index = match bias {
|
||||
Bias::Left => {
|
||||
if index == 0 {
|
||||
self.visible_items.len() - 1
|
||||
} else {
|
||||
index - 1
|
||||
}
|
||||
}
|
||||
Bias::Right => {
|
||||
if index >= self.visible_items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
index + 1
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
self.selected_index = index;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Top);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn select_previous(
|
||||
&mut self,
|
||||
_: &menu::SelectPrevious,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.selected_index == 0 {
|
||||
self.set_selected_index(self.visible_items.len() - 1, Bias::Left, cx);
|
||||
} else {
|
||||
self.set_selected_index(self.selected_index - 1, Bias::Left, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_next(
|
||||
&mut self,
|
||||
_: &menu::SelectNext,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.selected_index == self.visible_items.len() - 1 {
|
||||
self.set_selected_index(0, Bias::Right, cx);
|
||||
} else {
|
||||
self.set_selected_index(self.selected_index + 1, Bias::Right, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_first(
|
||||
&mut self,
|
||||
_: &menu::SelectFirst,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.set_selected_index(0, Bias::Right, cx);
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.set_selected_index(self.visible_items.len() - 1, Bias::Left, cx);
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.confirm_entry(self.selected_index, cx);
|
||||
}
|
||||
|
||||
fn confirm_entry(&mut self, ix: usize, cx: &mut Context<Self>) {
|
||||
let Some(entry) = self.get_history_entry(ix) else {
|
||||
return;
|
||||
};
|
||||
cx.emit(ThreadHistoryEvent::Open(entry.clone()));
|
||||
}
|
||||
|
||||
fn remove_selected_thread(
|
||||
&mut self,
|
||||
_: &RemoveSelectedThread,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.remove_thread(self.selected_index, cx)
|
||||
}
|
||||
|
||||
fn remove_thread(&mut self, visible_item_ix: usize, cx: &mut Context<Self>) {
|
||||
let Some(entry) = self.get_history_entry(visible_item_ix) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let task = match entry {
|
||||
HistoryEntry::AcpThread(thread) => self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)),
|
||||
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
|
||||
this.delete_text_thread(text_thread.path.clone(), cx)
|
||||
}),
|
||||
};
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn remove_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.history_store.update(cx, |store, cx| {
|
||||
store.delete_threads(cx).detach_and_log_err(cx)
|
||||
});
|
||||
self.confirming_delete_history = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn prompt_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.confirming_delete_history = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn cancel_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.confirming_delete_history = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_list_items(
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Vec<AnyElement> {
|
||||
self.visible_items
|
||||
.get(range.clone())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| self.render_list_item(item, range.start + ix, cx))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_list_item(&self, item: &ListItemType, ix: usize, cx: &Context<Self>) -> AnyElement {
|
||||
match item {
|
||||
ListItemType::Entry { entry, format } => self
|
||||
.render_history_entry(entry, *format, ix, Vec::default(), cx)
|
||||
.into_any(),
|
||||
ListItemType::SearchResult { entry, positions } => self.render_history_entry(
|
||||
entry,
|
||||
EntryTimeFormat::DateAndTime,
|
||||
ix,
|
||||
positions.clone(),
|
||||
cx,
|
||||
),
|
||||
ListItemType::BucketSeparator(bucket) => div()
|
||||
.px(DynamicSpacing::Base06.rems(cx))
|
||||
.pt_2()
|
||||
.pb_1()
|
||||
.child(
|
||||
Label::new(bucket.to_string())
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_history_entry(
|
||||
&self,
|
||||
entry: &HistoryEntry,
|
||||
format: EntryTimeFormat,
|
||||
ix: usize,
|
||||
highlight_positions: Vec<usize>,
|
||||
cx: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
let selected = ix == self.selected_index;
|
||||
let hovered = Some(ix) == self.hovered_index;
|
||||
let timestamp = entry.updated_at().timestamp();
|
||||
let thread_timestamp = format.format_timestamp(timestamp, self.local_timezone);
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pb_1()
|
||||
.child(
|
||||
ListItem::new(ix)
|
||||
.rounded()
|
||||
.toggle_state(selected)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
HighlightedLabel::new(entry.title(), highlight_positions)
|
||||
.size(LabelSize::Small)
|
||||
.truncate(),
|
||||
)
|
||||
.child(
|
||||
Label::new(thread_timestamp)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.on_hover(cx.listener(move |this, is_hovered, _window, cx| {
|
||||
if *is_hovered {
|
||||
this.hovered_index = Some(ix);
|
||||
} else if this.hovered_index == Some(ix) {
|
||||
this.hovered_index = None;
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}))
|
||||
.end_slot::<IconButton>(if hovered {
|
||||
Some(
|
||||
IconButton::new("delete", IconName::Trash)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.remove_thread(ix, cx);
|
||||
cx.stop_propagation()
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, _, cx| this.confirm_entry(ix, cx))),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AcpThreadHistory {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.search_editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AcpThreadHistory {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let has_no_history = self.history_store.read(cx).is_empty(cx);
|
||||
|
||||
v_flex()
|
||||
.key_context("ThreadHistory")
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.on_action(cx.listener(Self::select_previous))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::remove_selected_thread))
|
||||
.on_action(cx.listener(|this, _: &RemoveHistory, window, cx| {
|
||||
this.remove_history(window, cx);
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.h(Tab::container_height(cx))
|
||||
.w_full()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
Icon::new(IconName::MagnifyingGlass)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
.child(self.search_editor.clone()),
|
||||
)
|
||||
.child({
|
||||
let view = v_flex()
|
||||
.id("list-container")
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.flex_grow();
|
||||
|
||||
if has_no_history {
|
||||
view.justify_center().items_center().child(
|
||||
Label::new("You don't have any past threads yet.")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
} else if self.search_produced_no_matches() {
|
||||
view.justify_center()
|
||||
.items_center()
|
||||
.child(Label::new("No threads match your search.").size(LabelSize::Small))
|
||||
} else {
|
||||
view.child(
|
||||
uniform_list(
|
||||
"thread-history",
|
||||
self.visible_items.len(),
|
||||
cx.processor(|this, range: Range<usize>, window, cx| {
|
||||
this.render_list_items(range, window, cx)
|
||||
}),
|
||||
)
|
||||
.p_1()
|
||||
.pr_4()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.flex_grow(),
|
||||
)
|
||||
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
|
||||
}
|
||||
})
|
||||
.when(!has_no_history, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.when(!self.confirming_delete_history, |this| {
|
||||
this.child(
|
||||
Button::new("delete_history", "Delete All History")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.prompt_delete_history(window, cx);
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(self.confirming_delete_history, |this| {
|
||||
this.w_full()
|
||||
.gap_2()
|
||||
.flex_wrap()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new("Delete all threads?")
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new("You won't be able to recover them later.")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Button::new("cancel_delete", "Cancel")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.cancel_delete_history(window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("confirm_delete", "Delete")
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Error))
|
||||
.color(Color::Error)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(RemoveHistory),
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EntryTimeFormat {
|
||||
DateAndTime,
|
||||
TimeOnly,
|
||||
}
|
||||
|
||||
impl EntryTimeFormat {
|
||||
fn format_timestamp(&self, timestamp: i64, timezone: UtcOffset) -> String {
|
||||
let timestamp = OffsetDateTime::from_unix_timestamp(timestamp).unwrap();
|
||||
|
||||
match self {
|
||||
EntryTimeFormat::DateAndTime => time_format::format_localized_timestamp(
|
||||
timestamp,
|
||||
OffsetDateTime::now_utc(),
|
||||
timezone,
|
||||
time_format::TimestampFormat::EnhancedAbsolute,
|
||||
),
|
||||
EntryTimeFormat::TimeOnly => time_format::format_time(timestamp.to_offset(timezone)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimeBucket> for EntryTimeFormat {
|
||||
fn from(bucket: TimeBucket) -> Self {
|
||||
match bucket {
|
||||
TimeBucket::Today => EntryTimeFormat::TimeOnly,
|
||||
TimeBucket::Yesterday => EntryTimeFormat::TimeOnly,
|
||||
TimeBucket::ThisWeek => EntryTimeFormat::DateAndTime,
|
||||
TimeBucket::PastWeek => EntryTimeFormat::DateAndTime,
|
||||
TimeBucket::All => EntryTimeFormat::DateAndTime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
enum TimeBucket {
|
||||
Today,
|
||||
Yesterday,
|
||||
ThisWeek,
|
||||
PastWeek,
|
||||
All,
|
||||
}
|
||||
|
||||
impl TimeBucket {
|
||||
fn from_dates(reference: NaiveDate, date: NaiveDate) -> Self {
|
||||
if date == reference {
|
||||
return TimeBucket::Today;
|
||||
}
|
||||
|
||||
if date == reference - TimeDelta::days(1) {
|
||||
return TimeBucket::Yesterday;
|
||||
}
|
||||
|
||||
let week = date.iso_week();
|
||||
|
||||
if reference.iso_week() == week {
|
||||
return TimeBucket::ThisWeek;
|
||||
}
|
||||
|
||||
let last_week = (reference - TimeDelta::days(7)).iso_week();
|
||||
|
||||
if week == last_week {
|
||||
return TimeBucket::PastWeek;
|
||||
}
|
||||
|
||||
TimeBucket::All
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TimeBucket {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TimeBucket::Today => write!(f, "Today"),
|
||||
TimeBucket::Yesterday => write!(f, "Yesterday"),
|
||||
TimeBucket::ThisWeek => write!(f, "This Week"),
|
||||
TimeBucket::PastWeek => write!(f, "Past Week"),
|
||||
TimeBucket::All => write!(f, "All"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
#[test]
|
||||
fn test_time_bucket_from_dates() {
|
||||
let today = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap();
|
||||
|
||||
let date = today;
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::Today);
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2023, 1, 14).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::Yesterday);
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2023, 1, 13).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::ThisWeek);
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2023, 1, 11).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::ThisWeek);
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2023, 1, 8).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::PastWeek);
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2023, 1, 5).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::PastWeek);
|
||||
|
||||
// All: not in this week or last week
|
||||
let date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::All);
|
||||
|
||||
// Test year boundary cases
|
||||
let new_year = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2022, 12, 31).unwrap();
|
||||
assert_eq!(
|
||||
TimeBucket::from_dates(new_year, date),
|
||||
TimeBucket::Yesterday
|
||||
);
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(2022, 12, 28).unwrap();
|
||||
assert_eq!(TimeBucket::from_dates(new_year, date), TimeBucket::ThisWeek);
|
||||
}
|
||||
}
|
||||
@@ -429,24 +429,10 @@ impl Model {
|
||||
let mut headers = vec![];
|
||||
|
||||
match self {
|
||||
Self::ClaudeOpus4
|
||||
| Self::ClaudeOpus4_1
|
||||
| Self::ClaudeOpus4_5
|
||||
| Self::ClaudeSonnet4
|
||||
| Self::ClaudeSonnet4_5
|
||||
| Self::ClaudeOpus4Thinking
|
||||
| Self::ClaudeOpus4_1Thinking
|
||||
| Self::ClaudeOpus4_5Thinking
|
||||
| Self::ClaudeSonnet4Thinking
|
||||
| Self::ClaudeSonnet4_5Thinking => {
|
||||
// Fine-grained tool streaming for newer models
|
||||
headers.push("fine-grained-tool-streaming-2025-05-14".to_string());
|
||||
}
|
||||
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => {
|
||||
// Try beta token-efficient tool use (supported in Claude 3.7 Sonnet only)
|
||||
// https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
|
||||
headers.push("token-efficient-tools-2025-02-19".to_string());
|
||||
headers.push("fine-grained-tool-streaming-2025-05-14".to_string());
|
||||
}
|
||||
Self::Custom {
|
||||
extra_beta_headers, ..
|
||||
|
||||
@@ -22,6 +22,7 @@ feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
globset.workspace = true
|
||||
gpui.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
|
||||
@@ -226,10 +226,10 @@ fn collect_files(
|
||||
let Ok(matchers) = glob_inputs
|
||||
.iter()
|
||||
.map(|glob_input| {
|
||||
util::paths::PathMatcher::new(&[glob_input.to_owned()], project.read(cx).path_style(cx))
|
||||
custom_path_matcher::PathMatcher::new(&[glob_input.to_owned()])
|
||||
.with_context(|| format!("invalid path {glob_input}"))
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<util::paths::PathMatcher>>>()
|
||||
.collect::<anyhow::Result<Vec<custom_path_matcher::PathMatcher>>>()
|
||||
else {
|
||||
return futures::stream::once(async {
|
||||
anyhow::bail!("invalid path");
|
||||
@@ -250,7 +250,6 @@ fn collect_files(
|
||||
let worktree_id = snapshot.id();
|
||||
let path_style = snapshot.path_style();
|
||||
let mut directory_stack: Vec<Arc<RelPath>> = Vec::new();
|
||||
let mut folded_directory_path: Option<Arc<RelPath>> = None;
|
||||
let mut folded_directory_names: Arc<RelPath> = RelPath::empty().into();
|
||||
let mut is_top_level_directory = true;
|
||||
|
||||
@@ -278,16 +277,6 @@ fn collect_files(
|
||||
)))?;
|
||||
}
|
||||
|
||||
if let Some(folded_path) = &folded_directory_path {
|
||||
if !entry.path.starts_with(folded_path) {
|
||||
folded_directory_names = RelPath::empty().into();
|
||||
folded_directory_path = None;
|
||||
if directory_stack.is_empty() {
|
||||
is_top_level_directory = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filename = entry.path.file_name().unwrap_or_default().to_string();
|
||||
|
||||
if entry.is_dir() {
|
||||
@@ -303,17 +292,13 @@ fn collect_files(
|
||||
folded_directory_names =
|
||||
folded_directory_names.join(RelPath::unix(&filename).unwrap());
|
||||
}
|
||||
folded_directory_path = Some(entry.path.clone());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Skip empty directories
|
||||
folded_directory_names = RelPath::empty().into();
|
||||
folded_directory_path = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Render the directory (either folded or normal)
|
||||
if folded_directory_names.is_empty() {
|
||||
let label = if is_top_level_directory {
|
||||
is_top_level_directory = false;
|
||||
@@ -349,8 +334,6 @@ fn collect_files(
|
||||
},
|
||||
)))?;
|
||||
directory_stack.push(entry.path.clone());
|
||||
folded_directory_names = RelPath::empty().into();
|
||||
folded_directory_path = None;
|
||||
}
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
@@ -464,6 +447,87 @@ pub fn build_entry_output_section(
|
||||
}
|
||||
}
|
||||
|
||||
/// This contains a small fork of the util::paths::PathMatcher, that is stricter about the prefix
|
||||
/// check. Only subpaths pass the prefix check, rather than any prefix.
|
||||
mod custom_path_matcher {
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use std::fmt::Debug as _;
|
||||
use util::{paths::SanitizedPath, rel_path::RelPath};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PathMatcher {
|
||||
sources: Vec<String>,
|
||||
sources_with_trailing_slash: Vec<String>,
|
||||
glob: GlobSet,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PathMatcher {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.sources.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PathMatcher {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.sources.eq(&other.sources)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for PathMatcher {}
|
||||
|
||||
impl PathMatcher {
|
||||
pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
|
||||
let globs = globs
|
||||
.iter()
|
||||
.map(|glob| Glob::new(&SanitizedPath::new(glob).to_string()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
|
||||
let sources_with_trailing_slash = globs
|
||||
.iter()
|
||||
.map(|glob| glob.glob().to_string() + "/")
|
||||
.collect();
|
||||
let mut glob_builder = GlobSetBuilder::new();
|
||||
for single_glob in globs {
|
||||
glob_builder.add(single_glob);
|
||||
}
|
||||
let glob = glob_builder.build()?;
|
||||
Ok(PathMatcher {
|
||||
glob,
|
||||
sources,
|
||||
sources_with_trailing_slash,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_match(&self, other: &RelPath) -> bool {
|
||||
self.sources
|
||||
.iter()
|
||||
.zip(self.sources_with_trailing_slash.iter())
|
||||
.any(|(source, with_slash)| {
|
||||
let as_bytes = other.as_unix_str().as_bytes();
|
||||
let with_slash = if source.ends_with('/') {
|
||||
source.as_bytes()
|
||||
} else {
|
||||
with_slash.as_bytes()
|
||||
};
|
||||
|
||||
as_bytes.starts_with(with_slash) || as_bytes.ends_with(source.as_bytes())
|
||||
})
|
||||
|| self.glob.is_match(other.as_std_path())
|
||||
|| self.check_with_end_separator(other)
|
||||
}
|
||||
|
||||
fn check_with_end_separator(&self, path: &RelPath) -> bool {
|
||||
let path_str = path.as_unix_str();
|
||||
let separator = "/";
|
||||
if path_str.ends_with(separator) {
|
||||
false
|
||||
} else {
|
||||
self.glob.is_match(path_str.to_string() + separator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_buffer_to_output(
|
||||
buffer: &BufferSnapshot,
|
||||
path: Option<&str>,
|
||||
|
||||
@@ -46,7 +46,7 @@ serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
text.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -50,6 +50,7 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
|
||||
TextThread::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
@@ -188,6 +189,7 @@ fn test_message_splitting(cx: &mut App) {
|
||||
TextThread::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
@@ -292,6 +294,7 @@ fn test_messages_for_offsets(cx: &mut App) {
|
||||
TextThread::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
@@ -402,6 +405,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
TextThread::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
@@ -673,6 +677,7 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
TextThread::local(
|
||||
registry.clone(),
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
@@ -719,6 +724,7 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -774,6 +780,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1034,6 +1041,7 @@ fn test_mark_cache_anchors(cx: &mut App) {
|
||||
TextThread::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
@@ -1360,6 +1368,7 @@ fn setup_context_editor_with_fake_model(
|
||||
TextThread::local(
|
||||
registry,
|
||||
None,
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
cx,
|
||||
|
||||
@@ -5,7 +5,7 @@ use assistant_slash_command::{
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
};
|
||||
use assistant_slash_commands::FileCommandMetadata;
|
||||
use client::{self, ModelRequestUsage, RequestUsage, proto};
|
||||
use client::{self, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use cloud_llm_client::{CompletionIntent, UsageLimit};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -19,11 +19,10 @@ use gpui::{
|
||||
use itertools::Itertools as _;
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
AnthropicCompletionType, AnthropicEventData, AnthropicEventType, LanguageModel,
|
||||
LanguageModelCacheConfiguration, LanguageModelCompletionEvent, LanguageModelImage,
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelToolUseId, MessageContent, PaymentRequiredError, Role, StopReason,
|
||||
report_anthropic_event,
|
||||
report_assistant_event,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::text_threads_dir;
|
||||
@@ -41,7 +40,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||
use text::{BufferSnapshot, ToPoint};
|
||||
use ui::IconName;
|
||||
use util::{ResultExt, TryFutureExt, post_inc};
|
||||
@@ -687,6 +686,7 @@ pub struct TextThread {
|
||||
pending_cache_warming_task: Task<Option<()>>,
|
||||
path: Option<Arc<Path>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project: Option<WeakEntity<Project>>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
@@ -709,6 +709,7 @@ impl TextThread {
|
||||
pub fn local(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project: Option<WeakEntity<Project>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -721,6 +722,7 @@ impl TextThread {
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
project,
|
||||
telemetry,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@@ -741,6 +743,7 @@ impl TextThread {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
project: Option<WeakEntity<Project>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let buffer = cx.new(|_cx| {
|
||||
@@ -781,6 +784,7 @@ impl TextThread {
|
||||
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
|
||||
path: None,
|
||||
buffer,
|
||||
telemetry,
|
||||
project,
|
||||
language_registry,
|
||||
slash_commands,
|
||||
@@ -870,6 +874,7 @@ impl TextThread {
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
project: Option<WeakEntity<Project>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let id = saved_context.id.clone().unwrap_or_else(TextThreadId::new);
|
||||
@@ -881,6 +886,7 @@ impl TextThread {
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
project,
|
||||
telemetry,
|
||||
cx,
|
||||
);
|
||||
this.path = Some(path);
|
||||
@@ -2206,26 +2212,24 @@ impl TextThread {
|
||||
.read(cx)
|
||||
.language()
|
||||
.map(|language| language.name());
|
||||
|
||||
telemetry::event!(
|
||||
"Assistant Responded",
|
||||
conversation_id = this.id.0.clone(),
|
||||
kind = "panel",
|
||||
phase = "response",
|
||||
model = model.telemetry_id(),
|
||||
model_provider = model.provider_id().to_string(),
|
||||
response_latency,
|
||||
error_message,
|
||||
language_name = language_name.as_ref().map(|name| name.to_proto()),
|
||||
report_assistant_event(
|
||||
AssistantEventData {
|
||||
conversation_id: Some(this.id.0.clone()),
|
||||
kind: AssistantKind::Panel,
|
||||
phase: AssistantPhase::Response,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency,
|
||||
error_message,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
},
|
||||
this.telemetry.clone(),
|
||||
cx.http_client(),
|
||||
model.api_key(cx),
|
||||
cx.background_executor(),
|
||||
);
|
||||
|
||||
report_anthropic_event(&model, AnthropicEventData {
|
||||
completion_type: AnthropicCompletionType::Panel,
|
||||
event: AnthropicEventType::Response,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
message_id: None,
|
||||
}, cx);
|
||||
|
||||
if let Ok(stop_reason) = result {
|
||||
match stop_reason {
|
||||
StopReason::ToolUse => {}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
||||
use client::{Client, TypedEnvelope, proto};
|
||||
use client::{Client, TypedEnvelope, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use context_server::ContextServerId;
|
||||
@@ -48,6 +48,7 @@ pub struct TextThreadStore {
|
||||
fs: Arc<dyn Fs>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
_watch_updates: Task<Option<()>>,
|
||||
client: Arc<Client>,
|
||||
project: WeakEntity<Project>,
|
||||
@@ -87,6 +88,7 @@ impl TextThreadStore {
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let languages = project.read(cx).languages().clone();
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
cx.spawn(async move |cx| {
|
||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let (mut events, _) = fs.watch(text_threads_dir(), CONTEXT_WATCH_DURATION).await;
|
||||
@@ -100,6 +102,7 @@ impl TextThreadStore {
|
||||
fs,
|
||||
languages,
|
||||
slash_commands,
|
||||
telemetry,
|
||||
_watch_updates: cx.spawn(async move |this, cx| {
|
||||
async move {
|
||||
while events.next().await.is_some() {
|
||||
@@ -140,6 +143,7 @@ impl TextThreadStore {
|
||||
fs: project.read(cx).fs().clone(),
|
||||
languages: project.read(cx).languages().clone(),
|
||||
slash_commands: Arc::default(),
|
||||
telemetry: project.read(cx).client().telemetry().clone(),
|
||||
_watch_updates: Task::ready(None),
|
||||
client: project.read(cx).client(),
|
||||
project: project.downgrade(),
|
||||
@@ -375,6 +379,7 @@ impl TextThreadStore {
|
||||
TextThread::local(
|
||||
self.languages.clone(),
|
||||
Some(self.project.clone()),
|
||||
Some(self.telemetry.clone()),
|
||||
self.prompt_builder.clone(),
|
||||
self.slash_commands.clone(),
|
||||
cx,
|
||||
@@ -397,7 +402,7 @@ impl TextThreadStore {
|
||||
let capability = project.capability();
|
||||
let language_registry = self.languages.clone();
|
||||
let project = self.project.clone();
|
||||
|
||||
let telemetry = self.telemetry.clone();
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let request = self.client.request(proto::CreateContext { project_id });
|
||||
@@ -414,6 +419,7 @@ impl TextThreadStore {
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
@@ -451,6 +457,7 @@ impl TextThreadStore {
|
||||
let fs = self.fs.clone();
|
||||
let languages = self.languages.clone();
|
||||
let project = self.project.clone();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let load = cx.background_spawn({
|
||||
let path = path.clone();
|
||||
async move {
|
||||
@@ -471,6 +478,7 @@ impl TextThreadStore {
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
@@ -560,6 +568,7 @@ impl TextThreadStore {
|
||||
let capability = project.capability();
|
||||
let language_registry = self.languages.clone();
|
||||
let project = self.project.clone();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let request = self.client.request(proto::OpenContext {
|
||||
project_id,
|
||||
context_id: text_thread_id.to_proto(),
|
||||
@@ -578,6 +587,7 @@ impl TextThreadStore {
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -305,7 +305,6 @@ impl Room {
|
||||
|
||||
pub(crate) fn leave(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
cx.notify();
|
||||
self.emit_video_track_unsubscribed_events(cx);
|
||||
self.leave_internal(cx)
|
||||
}
|
||||
|
||||
@@ -353,14 +352,6 @@ impl Room {
|
||||
self.maintain_connection.take();
|
||||
}
|
||||
|
||||
fn emit_video_track_unsubscribed_events(&self, cx: &mut Context<Self>) {
|
||||
for participant in self.remote_participants.values() {
|
||||
for sid in participant.video_tracks.keys() {
|
||||
cx.emit(Event::RemoteVideoTrackUnsubscribed { sid: sid.clone() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn maintain_connection(
|
||||
this: WeakEntity<Self>,
|
||||
client: Arc<Client>,
|
||||
@@ -891,9 +882,6 @@ impl Room {
|
||||
project_id: project.id,
|
||||
});
|
||||
}
|
||||
for sid in participant.video_tracks.keys() {
|
||||
cx.emit(Event::RemoteVideoTrackUnsubscribed { sid: sid.clone() });
|
||||
}
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
@@ -61,8 +61,6 @@ Examples:
|
||||
)]
|
||||
struct Args {
|
||||
/// Wait for all of the given paths to be opened/closed before exiting.
|
||||
///
|
||||
/// When opening a directory, waits until the created window is closed.
|
||||
#[arg(short, long)]
|
||||
wait: bool,
|
||||
/// Add files to the currently open workspace
|
||||
|
||||
@@ -53,7 +53,7 @@ text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http.workspace = true
|
||||
tokio-socks.workspace = true
|
||||
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io"] }
|
||||
tokio.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -150,8 +150,9 @@ pub fn init(client: &Arc<Client>, cx: &mut App) {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
});
|
||||
|
||||
cx.on_action({
|
||||
let client = client.clone();
|
||||
move |_: &SignOut, cx| {
|
||||
if let Some(client) = client.upgrade() {
|
||||
@@ -161,8 +162,9 @@ pub fn init(client: &Arc<Client>, cx: &mut App) {
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
});
|
||||
|
||||
cx.on_action({
|
||||
let client = client;
|
||||
move |_: &Reconnect, cx| {
|
||||
if let Some(client) = client.upgrade() {
|
||||
@@ -1730,59 +1732,23 @@ impl ProtoClient for Client {
|
||||
/// prefix for the zed:// url scheme
|
||||
pub const ZED_URL_SCHEME: &str = "zed";
|
||||
|
||||
/// A parsed Zed link that can be handled internally by the application.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ZedLink {
|
||||
/// Join a channel: `zed.dev/channel/channel-name-123` or `zed://channel/channel-name-123`
|
||||
Channel { channel_id: u64 },
|
||||
/// Open channel notes: `zed.dev/channel/channel-name-123/notes` or with heading `notes#heading`
|
||||
ChannelNotes {
|
||||
channel_id: u64,
|
||||
heading: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Parses the given link into a Zed link.
|
||||
///
|
||||
/// Returns a [`Some`] containing the parsed link if the link is a recognized Zed link
|
||||
/// that should be handled internally by the application.
|
||||
/// Returns [`None`] for links that should be opened in the browser.
|
||||
pub fn parse_zed_link(link: &str, cx: &App) -> Option<ZedLink> {
|
||||
/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link.
|
||||
/// Returns [`None`] otherwise.
|
||||
pub fn parse_zed_link<'a>(link: &'a str, cx: &App) -> Option<&'a str> {
|
||||
let server_url = &ClientSettings::get_global(cx).server_url;
|
||||
let path = link
|
||||
if let Some(stripped) = link
|
||||
.strip_prefix(server_url)
|
||||
.and_then(|result| result.strip_prefix('/'))
|
||||
.or_else(|| {
|
||||
link.strip_prefix(ZED_URL_SCHEME)
|
||||
.and_then(|result| result.strip_prefix("://"))
|
||||
})?;
|
||||
|
||||
let mut parts = path.split('/');
|
||||
|
||||
if parts.next() != Some("channel") {
|
||||
return None;
|
||||
{
|
||||
return Some(stripped);
|
||||
}
|
||||
|
||||
let slug = parts.next()?;
|
||||
let id_str = slug.split('-').next_back()?;
|
||||
let channel_id = id_str.parse::<u64>().ok()?;
|
||||
|
||||
let Some(next) = parts.next() else {
|
||||
return Some(ZedLink::Channel { channel_id });
|
||||
};
|
||||
|
||||
if let Some(heading) = next.strip_prefix("notes#") {
|
||||
return Some(ZedLink::ChannelNotes {
|
||||
channel_id,
|
||||
heading: Some(heading.to_string()),
|
||||
});
|
||||
}
|
||||
|
||||
if next == "notes" {
|
||||
return Some(ZedLink::ChannelNotes {
|
||||
channel_id,
|
||||
heading: None,
|
||||
});
|
||||
if let Some(stripped) = link
|
||||
.strip_prefix(ZED_URL_SCHEME)
|
||||
.and_then(|result| result.strip_prefix("://"))
|
||||
{
|
||||
return Some(stripped);
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -371,8 +371,6 @@ pub struct LanguageModel {
|
||||
pub supports_images: bool,
|
||||
pub supports_thinking: bool,
|
||||
pub supports_max_mode: bool,
|
||||
#[serde(default)]
|
||||
pub supports_streaming_tools: bool,
|
||||
// only used by OpenAI and xAI
|
||||
#[serde(default)]
|
||||
pub supports_parallel_tool_calls: bool,
|
||||
|
||||
18
crates/cloud_zeta2_prompt/Cargo.toml
Normal file
18
crates/cloud_zeta2_prompt/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "cloud_zeta2_prompt"
|
||||
version = "0.1.0"
|
||||
publish.workspace = true
|
||||
edition.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/cloud_zeta2_prompt.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
cloud_llm_client.workspace = true
|
||||
indoc.workspace = true
|
||||
serde.workspace = true
|
||||
485
crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs
Normal file
485
crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs
Normal file
@@ -0,0 +1,485 @@
|
||||
use anyhow::Result;
|
||||
use cloud_llm_client::predict_edits_v3::{
|
||||
self, DiffPathFmt, Event, Excerpt, Line, Point, PromptFormat, RelatedFile,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use std::cmp;
|
||||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024;
|
||||
|
||||
pub const CURSOR_MARKER: &str = "<|user_cursor|>";
|
||||
/// NOTE: Differs from zed version of constant - includes a newline
|
||||
pub const EDITABLE_REGION_START_MARKER_WITH_NEWLINE: &str = "<|editable_region_start|>\n";
|
||||
/// NOTE: Differs from zed version of constant - includes a newline
|
||||
pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n";
|
||||
|
||||
const STUDENT_MODEL_INSTRUCTIONS: &str = indoc! {r#"
|
||||
You are a code completion assistant that analyzes edit history to identify and systematically complete incomplete refactorings or patterns across the entire codebase.
|
||||
|
||||
## Edit History
|
||||
|
||||
"#};
|
||||
|
||||
const MINIMAL_PROMPT_REMINDER: &str = indoc! {"
|
||||
---
|
||||
|
||||
Please analyze the edit history and the files, then provide the unified diff for your predicted edits.
|
||||
Do not include the cursor marker in your output.
|
||||
If you're editing multiple files, be sure to reflect filename in the hunk's header.
|
||||
"};
|
||||
|
||||
const XML_TAGS_INSTRUCTIONS: &str = indoc! {r#"
|
||||
# Instructions
|
||||
|
||||
You are an edit prediction agent in a code editor.
|
||||
|
||||
Analyze the history of edits made by the user in order to infer what they are currently trying to accomplish.
|
||||
Then complete the remainder of the current change if it is incomplete, or predict the next edit the user intends to make.
|
||||
Always continue along the user's current trajectory, rather than changing course.
|
||||
|
||||
## Output Format
|
||||
|
||||
You should briefly explain your understanding of the user's overall goal in one sentence, then explain what the next change
|
||||
along the users current trajectory will be in another, and finally specify the next edit using the following XML-like format:
|
||||
|
||||
<edits path="my-project/src/myapp/cli.py">
|
||||
<old_text>
|
||||
OLD TEXT 1 HERE
|
||||
</old_text>
|
||||
<new_text>
|
||||
NEW TEXT 1 HERE
|
||||
</new_text>
|
||||
|
||||
<old_text>
|
||||
OLD TEXT 1 HERE
|
||||
</old_text>
|
||||
<new_text>
|
||||
NEW TEXT 1 HERE
|
||||
</new_text>
|
||||
</edits>
|
||||
|
||||
- Specify the file to edit using the `path` attribute.
|
||||
- Use `<old_text>` and `<new_text>` tags to replace content
|
||||
- `<old_text>` must exactly match existing file content, including indentation
|
||||
- `<old_text>` cannot be empty
|
||||
- Do not escape quotes, newlines, or other characters within tags
|
||||
- Always close all tags properly
|
||||
- Don't include the <|user_cursor|> marker in your output.
|
||||
|
||||
## Edit History
|
||||
|
||||
"#};
|
||||
|
||||
const OLD_TEXT_NEW_TEXT_REMINDER: &str = indoc! {r#"
|
||||
---
|
||||
|
||||
Remember that the edits in the edit history have already been applied.
|
||||
"#};
|
||||
|
||||
pub fn build_prompt(request: &predict_edits_v3::PredictEditsRequest) -> Result<String> {
|
||||
let prompt_data = PromptData {
|
||||
events: request.events.clone(),
|
||||
cursor_point: request.cursor_point,
|
||||
cursor_path: request.excerpt_path.clone(),
|
||||
included_files: request.related_files.clone(),
|
||||
};
|
||||
match request.prompt_format {
|
||||
PromptFormat::MinimalQwen => {
|
||||
return Ok(MinimalQwenPrompt.render(&prompt_data));
|
||||
}
|
||||
PromptFormat::SeedCoder1120 => {
|
||||
return Ok(SeedCoder1120Prompt.render(&prompt_data));
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
let insertions = match request.prompt_format {
|
||||
PromptFormat::Minimal | PromptFormat::OldTextNewText => {
|
||||
vec![(request.cursor_point, CURSOR_MARKER)]
|
||||
}
|
||||
PromptFormat::OnlySnippets => vec![],
|
||||
PromptFormat::MinimalQwen => unreachable!(),
|
||||
PromptFormat::SeedCoder1120 => unreachable!(),
|
||||
};
|
||||
|
||||
let mut prompt = match request.prompt_format {
|
||||
PromptFormat::OldTextNewText => XML_TAGS_INSTRUCTIONS.to_string(),
|
||||
PromptFormat::OnlySnippets => String::new(),
|
||||
PromptFormat::Minimal => STUDENT_MODEL_INSTRUCTIONS.to_string(),
|
||||
PromptFormat::MinimalQwen => unreachable!(),
|
||||
PromptFormat::SeedCoder1120 => unreachable!(),
|
||||
};
|
||||
|
||||
if request.events.is_empty() {
|
||||
prompt.push_str("(No edit history)\n\n");
|
||||
} else {
|
||||
let edit_preamble = if request.prompt_format == PromptFormat::Minimal {
|
||||
"The following are the latest edits made by the user, from earlier to later.\n\n"
|
||||
} else {
|
||||
"Here are the latest edits made by the user, from earlier to later.\n\n"
|
||||
};
|
||||
prompt.push_str(edit_preamble);
|
||||
push_events(&mut prompt, &request.events);
|
||||
}
|
||||
|
||||
let excerpts_preamble = match request.prompt_format {
|
||||
PromptFormat::Minimal => indoc! {"
|
||||
## Part of the file under the cursor
|
||||
|
||||
(The cursor marker <|user_cursor|> indicates the current user cursor position.
|
||||
The file is in current state, edits from edit history has been applied.
|
||||
We only show part of the file around the cursor.
|
||||
You can only edit exactly this part of the file.
|
||||
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.)
|
||||
"},
|
||||
PromptFormat::OldTextNewText => indoc! {"
|
||||
## Code Excerpts
|
||||
|
||||
Here is some excerpts of code that you should take into account to predict the next edit.
|
||||
|
||||
The cursor position is marked by `<|user_cursor|>` as it stands after the last edit in the history.
|
||||
|
||||
In addition other excerpts are included to better understand what the edit will be, including the declaration
|
||||
or references of symbols around the cursor, or other similar code snippets that may need to be updated
|
||||
following patterns that appear in the edit history.
|
||||
|
||||
Consider each of them carefully in relation to the edit history, and that the user may not have navigated
|
||||
to the next place they want to edit yet.
|
||||
|
||||
Lines starting with `…` indicate omitted line ranges. These may appear inside multi-line code constructs.
|
||||
"},
|
||||
PromptFormat::OnlySnippets | PromptFormat::MinimalQwen | PromptFormat::SeedCoder1120 => {
|
||||
indoc! {"
|
||||
## Code Excerpts
|
||||
|
||||
The cursor marker <|user_cursor|> indicates the current user cursor position.
|
||||
The file is in current state, edits from edit history have been applied.
|
||||
"}
|
||||
}
|
||||
};
|
||||
|
||||
prompt.push_str(excerpts_preamble);
|
||||
prompt.push('\n');
|
||||
|
||||
let include_line_numbers = matches!(request.prompt_format, PromptFormat::Minimal);
|
||||
for related_file in &request.related_files {
|
||||
if request.prompt_format == PromptFormat::Minimal {
|
||||
write_codeblock_with_filename(
|
||||
&related_file.path,
|
||||
&related_file.excerpts,
|
||||
if related_file.path == request.excerpt_path {
|
||||
&insertions
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
related_file.max_row,
|
||||
include_line_numbers,
|
||||
&mut prompt,
|
||||
);
|
||||
} else {
|
||||
write_codeblock(
|
||||
&related_file.path,
|
||||
&related_file.excerpts,
|
||||
if related_file.path == request.excerpt_path {
|
||||
&insertions
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
related_file.max_row,
|
||||
include_line_numbers,
|
||||
&mut prompt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match request.prompt_format {
|
||||
PromptFormat::OldTextNewText => {
|
||||
prompt.push_str(OLD_TEXT_NEW_TEXT_REMINDER);
|
||||
}
|
||||
PromptFormat::Minimal => {
|
||||
prompt.push_str(MINIMAL_PROMPT_REMINDER);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(prompt)
|
||||
}
|
||||
|
||||
pub fn generation_params(prompt_format: PromptFormat) -> GenerationParams {
|
||||
match prompt_format {
|
||||
PromptFormat::SeedCoder1120 => SeedCoder1120Prompt::generation_params(),
|
||||
_ => GenerationParams::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_codeblock<'a>(
|
||||
path: &Path,
|
||||
excerpts: impl IntoIterator<Item = &'a Excerpt>,
|
||||
sorted_insertions: &[(Point, &str)],
|
||||
file_line_count: Line,
|
||||
include_line_numbers: bool,
|
||||
output: &'a mut String,
|
||||
) {
|
||||
writeln!(output, "`````{}", DiffPathFmt(path)).unwrap();
|
||||
|
||||
write_excerpts(
|
||||
excerpts,
|
||||
sorted_insertions,
|
||||
file_line_count,
|
||||
include_line_numbers,
|
||||
output,
|
||||
);
|
||||
write!(output, "`````\n\n").unwrap();
|
||||
}
|
||||
|
||||
fn write_codeblock_with_filename<'a>(
|
||||
path: &Path,
|
||||
excerpts: impl IntoIterator<Item = &'a Excerpt>,
|
||||
sorted_insertions: &[(Point, &str)],
|
||||
file_line_count: Line,
|
||||
include_line_numbers: bool,
|
||||
output: &'a mut String,
|
||||
) {
|
||||
writeln!(output, "`````filename={}", DiffPathFmt(path)).unwrap();
|
||||
|
||||
write_excerpts(
|
||||
excerpts,
|
||||
sorted_insertions,
|
||||
file_line_count,
|
||||
include_line_numbers,
|
||||
output,
|
||||
);
|
||||
write!(output, "`````\n\n").unwrap();
|
||||
}
|
||||
|
||||
pub fn write_excerpts<'a>(
|
||||
excerpts: impl IntoIterator<Item = &'a Excerpt>,
|
||||
sorted_insertions: &[(Point, &str)],
|
||||
file_line_count: Line,
|
||||
include_line_numbers: bool,
|
||||
output: &mut String,
|
||||
) {
|
||||
let mut current_row = Line(0);
|
||||
let mut sorted_insertions = sorted_insertions.iter().peekable();
|
||||
|
||||
for excerpt in excerpts {
|
||||
if excerpt.start_line > current_row {
|
||||
writeln!(output, "…").unwrap();
|
||||
}
|
||||
if excerpt.text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
current_row = excerpt.start_line;
|
||||
|
||||
for mut line in excerpt.text.lines() {
|
||||
if include_line_numbers {
|
||||
write!(output, "{}|", current_row.0 + 1).unwrap();
|
||||
}
|
||||
|
||||
while let Some((insertion_location, insertion_marker)) = sorted_insertions.peek() {
|
||||
match current_row.cmp(&insertion_location.line) {
|
||||
cmp::Ordering::Equal => {
|
||||
let (prefix, suffix) = line.split_at(insertion_location.column as usize);
|
||||
output.push_str(prefix);
|
||||
output.push_str(insertion_marker);
|
||||
line = suffix;
|
||||
sorted_insertions.next();
|
||||
}
|
||||
cmp::Ordering::Less => break,
|
||||
cmp::Ordering::Greater => {
|
||||
sorted_insertions.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
current_row.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if current_row < file_line_count {
|
||||
writeln!(output, "…").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_events(output: &mut String, events: &[Arc<predict_edits_v3::Event>]) {
|
||||
if events.is_empty() {
|
||||
return;
|
||||
};
|
||||
|
||||
writeln!(output, "`````diff").unwrap();
|
||||
for event in events {
|
||||
writeln!(output, "{}", event).unwrap();
|
||||
}
|
||||
writeln!(output, "`````\n").unwrap();
|
||||
}
|
||||
|
||||
struct PromptData {
|
||||
events: Vec<Arc<Event>>,
|
||||
cursor_point: Point,
|
||||
cursor_path: Arc<Path>, // TODO: make a common struct with cursor_point
|
||||
included_files: Vec<RelatedFile>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GenerationParams {
|
||||
pub temperature: Option<f32>,
|
||||
pub top_p: Option<f32>,
|
||||
pub stop: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
trait PromptFormatter {
|
||||
fn render(&self, data: &PromptData) -> String;
|
||||
|
||||
fn generation_params() -> GenerationParams {
|
||||
return GenerationParams::default();
|
||||
}
|
||||
}
|
||||
|
||||
struct MinimalQwenPrompt;
|
||||
|
||||
impl PromptFormatter for MinimalQwenPrompt {
|
||||
fn render(&self, data: &PromptData) -> String {
|
||||
let edit_history = self.fmt_edit_history(data);
|
||||
let context = self.fmt_context(data);
|
||||
|
||||
format!(
|
||||
"{instructions}\n\n{edit_history}\n\n{context}",
|
||||
instructions = MinimalQwenPrompt::INSTRUCTIONS,
|
||||
edit_history = edit_history,
|
||||
context = context
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimalQwenPrompt {
|
||||
const INSTRUCTIONS: &str = "You are a code completion assistant that analyzes edit history to identify and systematically complete incomplete refactorings or patterns across the entire codebase.\n";
|
||||
|
||||
fn fmt_edit_history(&self, data: &PromptData) -> String {
|
||||
if data.events.is_empty() {
|
||||
"(No edit history)\n\n".to_string()
|
||||
} else {
|
||||
let mut events_str = String::new();
|
||||
push_events(&mut events_str, &data.events);
|
||||
format!(
|
||||
"The following are the latest edits made by the user, from earlier to later.\n\n{}",
|
||||
events_str
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_context(&self, data: &PromptData) -> String {
|
||||
let mut context = String::new();
|
||||
let include_line_numbers = true;
|
||||
|
||||
for related_file in &data.included_files {
|
||||
writeln!(context, "<|file_sep|>{}", DiffPathFmt(&related_file.path)).unwrap();
|
||||
|
||||
if related_file.path == data.cursor_path {
|
||||
write!(context, "<|fim_prefix|>").unwrap();
|
||||
write_excerpts(
|
||||
&related_file.excerpts,
|
||||
&[(data.cursor_point, "<|fim_suffix|>")],
|
||||
related_file.max_row,
|
||||
include_line_numbers,
|
||||
&mut context,
|
||||
);
|
||||
writeln!(context, "<|fim_middle|>").unwrap();
|
||||
} else {
|
||||
write_excerpts(
|
||||
&related_file.excerpts,
|
||||
&[],
|
||||
related_file.max_row,
|
||||
include_line_numbers,
|
||||
&mut context,
|
||||
);
|
||||
}
|
||||
}
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
struct SeedCoder1120Prompt;
|
||||
|
||||
impl PromptFormatter for SeedCoder1120Prompt {
|
||||
fn render(&self, data: &PromptData) -> String {
|
||||
let edit_history = self.fmt_edit_history(data);
|
||||
let context = self.fmt_context(data);
|
||||
|
||||
format!(
|
||||
"# Edit History:\n{edit_history}\n\n{context}",
|
||||
edit_history = edit_history,
|
||||
context = context
|
||||
)
|
||||
}
|
||||
|
||||
fn generation_params() -> GenerationParams {
|
||||
GenerationParams {
|
||||
temperature: Some(0.2),
|
||||
top_p: Some(0.9),
|
||||
stop: Some(vec!["<[end_of_sentence]>".into()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeedCoder1120Prompt {
|
||||
fn fmt_edit_history(&self, data: &PromptData) -> String {
|
||||
if data.events.is_empty() {
|
||||
"(No edit history)\n\n".to_string()
|
||||
} else {
|
||||
let mut events_str = String::new();
|
||||
push_events(&mut events_str, &data.events);
|
||||
events_str
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_context(&self, data: &PromptData) -> String {
|
||||
let mut context = String::new();
|
||||
let include_line_numbers = true;
|
||||
|
||||
for related_file in &data.included_files {
|
||||
writeln!(context, "# Path: {}\n", DiffPathFmt(&related_file.path)).unwrap();
|
||||
|
||||
if related_file.path == data.cursor_path {
|
||||
let fim_prompt = self.fmt_fim(&related_file, data.cursor_point);
|
||||
context.push_str(&fim_prompt);
|
||||
} else {
|
||||
write_excerpts(
|
||||
&related_file.excerpts,
|
||||
&[],
|
||||
related_file.max_row,
|
||||
include_line_numbers,
|
||||
&mut context,
|
||||
);
|
||||
}
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
fn fmt_fim(&self, file: &RelatedFile, cursor_point: Point) -> String {
|
||||
let mut buf = String::new();
|
||||
const FIM_SUFFIX: &str = "<[fim-suffix]>";
|
||||
const FIM_PREFIX: &str = "<[fim-prefix]>";
|
||||
const FIM_MIDDLE: &str = "<[fim-middle]>";
|
||||
write!(buf, "{}", FIM_PREFIX).unwrap();
|
||||
write_excerpts(
|
||||
&file.excerpts,
|
||||
&[(cursor_point, FIM_SUFFIX)],
|
||||
file.max_row,
|
||||
true,
|
||||
&mut buf,
|
||||
);
|
||||
|
||||
// Swap prefix and suffix parts
|
||||
let index = buf.find(FIM_SUFFIX).unwrap();
|
||||
let prefix = &buf[..index];
|
||||
let suffix = &buf[index..];
|
||||
|
||||
format!("{}{}{}", suffix, prefix, FIM_MIDDLE)
|
||||
}
|
||||
}
|
||||
@@ -54,26 +54,6 @@ async fn check_is_contributor(
|
||||
) -> Result<Json<CheckIsContributorResponse>> {
|
||||
let params = params.into_contributor_selector()?;
|
||||
|
||||
if CopilotSweAgentBot::is_copilot_bot(¶ms) {
|
||||
return Ok(Json(CheckIsContributorResponse {
|
||||
signed_at: Some(
|
||||
CopilotSweAgentBot::created_at()
|
||||
.and_utc()
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
if Dependabot::is_dependabot(¶ms) {
|
||||
return Ok(Json(CheckIsContributorResponse {
|
||||
signed_at: Some(
|
||||
Dependabot::created_at()
|
||||
.and_utc()
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
if RenovateBot::is_renovate_bot(¶ms) {
|
||||
return Ok(Json(CheckIsContributorResponse {
|
||||
signed_at: Some(
|
||||
@@ -103,71 +83,6 @@ async fn check_is_contributor(
|
||||
}))
|
||||
}
|
||||
|
||||
/// The Copilot bot GitHub user (`copilot-swe-agent[bot]`).
|
||||
///
|
||||
/// https://api.github.com/users/copilot-swe-agent[bot]
|
||||
struct CopilotSweAgentBot;
|
||||
|
||||
impl CopilotSweAgentBot {
|
||||
const LOGIN: &'static str = "copilot-swe-agent[bot]";
|
||||
const USER_ID: i32 = 198982749;
|
||||
/// The alias of the GitHub copilot user. Although https://api.github.com/users/copilot
|
||||
/// yields a 404, GitHub still refers to the copilot bot user as @Copilot in some cases.
|
||||
const NAME_ALIAS: &'static str = "copilot";
|
||||
|
||||
/// Returns the `created_at` timestamp for the Dependabot bot user.
|
||||
fn created_at() -> &'static NaiveDateTime {
|
||||
static CREATED_AT: OnceLock<NaiveDateTime> = OnceLock::new();
|
||||
CREATED_AT.get_or_init(|| {
|
||||
chrono::DateTime::parse_from_rfc3339("2025-02-12T20:26:08Z")
|
||||
.expect("failed to parse 'created_at' for 'copilot-swe-agent[bot]'")
|
||||
.naive_utc()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns whether the given contributor selector corresponds to the Copilot bot user.
|
||||
fn is_copilot_bot(contributor: &ContributorSelector) -> bool {
|
||||
match contributor {
|
||||
ContributorSelector::GitHubLogin { github_login } => {
|
||||
github_login == Self::LOGIN || github_login == Self::NAME_ALIAS
|
||||
}
|
||||
ContributorSelector::GitHubUserId { github_user_id } => {
|
||||
github_user_id == &Self::USER_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Dependabot bot GitHub user (`dependabot[bot]`).
|
||||
///
|
||||
/// https://api.github.com/users/dependabot[bot]
|
||||
struct Dependabot;
|
||||
|
||||
impl Dependabot {
|
||||
const LOGIN: &'static str = "dependabot[bot]";
|
||||
const USER_ID: i32 = 49699333;
|
||||
|
||||
/// Returns the `created_at` timestamp for the Dependabot bot user.
|
||||
fn created_at() -> &'static NaiveDateTime {
|
||||
static CREATED_AT: OnceLock<NaiveDateTime> = OnceLock::new();
|
||||
CREATED_AT.get_or_init(|| {
|
||||
chrono::DateTime::parse_from_rfc3339("2019-04-16T22:34:25Z")
|
||||
.expect("failed to parse 'created_at' for 'dependabot[bot]'")
|
||||
.naive_utc()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns whether the given contributor selector corresponds to the Dependabot bot user.
|
||||
fn is_dependabot(contributor: &ContributorSelector) -> bool {
|
||||
match contributor {
|
||||
ContributorSelector::GitHubLogin { github_login } => github_login == Self::LOGIN,
|
||||
ContributorSelector::GitHubUserId { github_user_id } => {
|
||||
github_user_id == &Self::USER_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Renovate bot GitHub user (`renovate[bot]`).
|
||||
///
|
||||
/// https://api.github.com/users/renovate[bot]
|
||||
|
||||
@@ -312,49 +312,6 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: capabilities.clone(),
|
||||
initializer: Some(Box::new(|fake_server| {
|
||||
fake_server.set_request_handler::<lsp::request::Completion, _, _>(
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 14),
|
||||
);
|
||||
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "first_method(…)".into(),
|
||||
detail: Some("fn(&mut self, B) -> C".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
new_text: "first_method($1)".to_string(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 14),
|
||||
lsp::Position::new(0, 14),
|
||||
),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "second_method(…)".into(),
|
||||
detail: Some("fn(&mut self, C) -> D<E>".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
new_text: "second_method()".to_string(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 14),
|
||||
lsp::Position::new(0, 14),
|
||||
),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
},
|
||||
);
|
||||
})),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
),
|
||||
@@ -363,11 +320,6 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
FakeLspAdapter {
|
||||
name: "fake-analyzer",
|
||||
capabilities: capabilities.clone(),
|
||||
initializer: Some(Box::new(|fake_server| {
|
||||
fake_server.set_request_handler::<lsp::request::Completion, _, _>(
|
||||
|_, _| async move { Ok(None) },
|
||||
);
|
||||
})),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
),
|
||||
@@ -421,7 +373,6 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
let fake_language_server = fake_language_servers[0].next().await.unwrap();
|
||||
let second_fake_language_server = fake_language_servers[1].next().await.unwrap();
|
||||
cx_a.background_executor.run_until_parked();
|
||||
cx_b.background_executor.run_until_parked();
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert!(!buffer.completion_triggers().is_empty())
|
||||
@@ -436,9 +387,58 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
});
|
||||
cx_b.focus(&editor_b);
|
||||
|
||||
// Allow the completion request to propagate from guest to host to LSP.
|
||||
cx_b.background_executor.run_until_parked();
|
||||
cx_a.background_executor.run_until_parked();
|
||||
// Receive a completion request as the host's language server.
|
||||
// Return some completions from the host's language server.
|
||||
cx_a.executor().start_waiting();
|
||||
fake_language_server
|
||||
.set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 14),
|
||||
);
|
||||
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "first_method(…)".into(),
|
||||
detail: Some("fn(&mut self, B) -> C".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
new_text: "first_method($1)".to_string(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 14),
|
||||
lsp::Position::new(0, 14),
|
||||
),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "second_method(…)".into(),
|
||||
detail: Some("fn(&mut self, C) -> D<E>".into()),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
new_text: "second_method()".to_string(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 14),
|
||||
lsp::Position::new(0, 14),
|
||||
),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
second_fake_language_server
|
||||
.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move { Ok(None) })
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.executor().finish_waiting();
|
||||
|
||||
// Open the buffer on the host.
|
||||
let buffer_a = project_a
|
||||
@@ -484,7 +484,6 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
|
||||
// The additional edit is applied.
|
||||
cx_a.executor().run_until_parked();
|
||||
cx_b.executor().run_until_parked();
|
||||
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(
|
||||
@@ -642,11 +641,13 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
),
|
||||
})),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
|
||||
..lsp::CompletionItem::default()
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
cx_b.executor().run_until_parked();
|
||||
|
||||
// Await both language server responses
|
||||
first_lsp_completion.next().await.unwrap();
|
||||
second_lsp_completion.next().await.unwrap();
|
||||
|
||||
@@ -32,11 +32,13 @@ impl StdioTransport {
|
||||
cx: &AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?;
|
||||
let builder = ShellBuilder::new(&shell, cfg!(windows)).non_interactive();
|
||||
let mut command =
|
||||
builder.build_command(Some(binary.executable.display().to_string()), &binary.args);
|
||||
let builder = ShellBuilder::new(&shell, cfg!(windows));
|
||||
let (command, args) =
|
||||
builder.build(Some(binary.executable.display().to_string()), &binary.args);
|
||||
|
||||
let mut command = util::command::new_smol_command(command);
|
||||
command
|
||||
.args(args)
|
||||
.envs(binary.env.unwrap_or_default())
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
|
||||
@@ -52,7 +52,6 @@ ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
itertools.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
async-std = { version = "1.12.0", features = ["unstable"] }
|
||||
|
||||
@@ -4,7 +4,7 @@ pub mod copilot_responses;
|
||||
pub mod request;
|
||||
mod sign_in;
|
||||
|
||||
use crate::sign_in::initiate_sign_out;
|
||||
use crate::sign_in::initiate_sign_in_within_workspace;
|
||||
use ::fs::Fs;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -28,10 +28,12 @@ use project::DisableAiSettings;
|
||||
use request::StatusNotification;
|
||||
use semver::Version;
|
||||
use serde_json::json;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::hash_map::Entry,
|
||||
env,
|
||||
ffi::OsString,
|
||||
mem,
|
||||
@@ -40,14 +42,12 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::Dimensions;
|
||||
use util::{ResultExt, fs::remove_matching, rel_path::RelPath};
|
||||
use util::rel_path::RelPath;
|
||||
use util::{ResultExt, fs::remove_matching};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate;
|
||||
pub use crate::sign_in::{
|
||||
ConfigurationMode, ConfigurationView, CopilotCodeVerification, initiate_sign_in,
|
||||
reinstall_and_sign_in,
|
||||
};
|
||||
pub use crate::sign_in::{CopilotCodeVerification, initiate_sign_in, reinstall_and_sign_in};
|
||||
|
||||
actions!(
|
||||
copilot,
|
||||
@@ -98,14 +98,21 @@ pub fn init(
|
||||
.detach();
|
||||
|
||||
cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
|
||||
workspace.register_action(|_, _: &SignIn, window, cx| {
|
||||
initiate_sign_in(window, cx);
|
||||
workspace.register_action(|workspace, _: &SignIn, window, cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
let is_reinstall = false;
|
||||
initiate_sign_in_within_workspace(workspace, copilot, is_reinstall, window, cx);
|
||||
}
|
||||
});
|
||||
workspace.register_action(|_, _: &Reinstall, window, cx| {
|
||||
reinstall_and_sign_in(window, cx);
|
||||
workspace.register_action(|workspace, _: &Reinstall, window, cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
reinstall_and_sign_in_within_workspace(workspace, copilot, window, cx);
|
||||
}
|
||||
});
|
||||
workspace.register_action(|_, _: &SignOut, window, cx| {
|
||||
initiate_sign_out(window, cx);
|
||||
workspace.register_action(|workspace, _: &SignOut, _window, cx| {
|
||||
if let Some(copilot) = Copilot::global(cx) {
|
||||
sign_out_within_workspace(workspace, copilot, cx);
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -368,7 +375,7 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_copilot(
|
||||
fn start_copilot(
|
||||
&mut self,
|
||||
check_edit_prediction_provider: bool,
|
||||
awaiting_sign_in_after_start: bool,
|
||||
@@ -556,14 +563,6 @@ impl Copilot {
|
||||
let server = start_language_server.await;
|
||||
this.update(cx, |this, cx| {
|
||||
cx.notify();
|
||||
|
||||
if env::var("ZED_FORCE_COPILOT_ERROR").is_ok() {
|
||||
this.server = CopilotServer::Error(
|
||||
"Forced error for testing (ZED_FORCE_COPILOT_ERROR)".into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
match server {
|
||||
Ok((server, status)) => {
|
||||
this.server = CopilotServer::Running(RunningCopilotServer {
|
||||
@@ -585,17 +584,7 @@ impl Copilot {
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn is_authenticated(&self) -> bool {
|
||||
return matches!(
|
||||
self.server,
|
||||
CopilotServer::Running(RunningCopilotServer {
|
||||
sign_in_status: SignInStatus::Authorized,
|
||||
..
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
pub fn sign_in(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
pub(crate) fn sign_in(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
if let CopilotServer::Running(server) = &mut self.server {
|
||||
let task = match &server.sign_in_status {
|
||||
SignInStatus::Authorized => Task::ready(Ok(())).shared(),
|
||||
|
||||
@@ -269,7 +269,6 @@ fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b:
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use edit_prediction_types::EditPredictionGranularity;
|
||||
use editor::{
|
||||
Editor, ExcerptRange, MultiBuffer, MultiBufferOffset, SelectionEffects,
|
||||
test::editor_lsp_test_context::EditorLspTestContext,
|
||||
@@ -582,15 +581,13 @@ mod tests {
|
||||
assert!(editor.has_active_edit_prediction());
|
||||
|
||||
// Accepting the first word of the suggestion should only accept the first word and still show the rest.
|
||||
editor.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
|
||||
|
||||
editor.accept_partial_edit_prediction(&Default::default(), window, cx);
|
||||
assert!(editor.has_active_edit_prediction());
|
||||
assert_eq!(editor.text(cx), "one.copilot\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
|
||||
editor.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
|
||||
|
||||
editor.accept_partial_edit_prediction(&Default::default(), window, cx);
|
||||
assert!(!editor.has_active_edit_prediction());
|
||||
assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
|
||||
@@ -626,7 +623,7 @@ mod tests {
|
||||
assert!(editor.has_active_edit_prediction());
|
||||
|
||||
// Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
|
||||
editor.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
|
||||
editor.accept_partial_edit_prediction(&Default::default(), window, cx);
|
||||
assert!(editor.has_active_edit_prediction());
|
||||
assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
|
||||
assert_eq!(
|
||||
@@ -635,7 +632,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// Accepting next word should accept the next word and copilot suggestion should still exist
|
||||
editor.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
|
||||
editor.accept_partial_edit_prediction(&Default::default(), window, cx);
|
||||
assert!(editor.has_active_edit_prediction());
|
||||
assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
|
||||
assert_eq!(
|
||||
@@ -644,7 +641,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
|
||||
editor.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
|
||||
editor.accept_partial_edit_prediction(&Default::default(), window, cx);
|
||||
assert!(!editor.has_active_edit_prediction());
|
||||
assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,159 +1,166 @@
|
||||
use crate::{Copilot, Status, request::PromptUserDeviceFlow};
|
||||
use anyhow::Context as _;
|
||||
use gpui::{
|
||||
App, ClipboardItem, Context, DismissEvent, Element, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, InteractiveElement, IntoElement, MouseDownEvent, ParentElement, Render, Styled,
|
||||
Subscription, Window, WindowBounds, WindowOptions, div, point,
|
||||
Animation, AnimationExt, App, ClipboardItem, Context, DismissEvent, Element, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, MouseDownEvent,
|
||||
ParentElement, Render, Styled, Subscription, Transformation, Window, div, percentage, svg,
|
||||
};
|
||||
use ui::{ButtonLike, CommonAnimationExt, ConfiguredApiCard, Vector, VectorName, prelude::*};
|
||||
use url::Url;
|
||||
use std::time::Duration;
|
||||
use ui::{Button, Label, Vector, VectorName, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{Toast, Workspace, notifications::NotificationId};
|
||||
use workspace::notifications::NotificationId;
|
||||
use workspace::{ModalView, Toast, Workspace};
|
||||
|
||||
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
|
||||
const ERROR_LABEL: &str =
|
||||
"Copilot had issues starting. You can try reinstalling it and signing in again.";
|
||||
|
||||
struct CopilotStatusToast;
|
||||
|
||||
pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
|
||||
let is_reinstall = false;
|
||||
initiate_sign_in_impl(is_reinstall, window, cx)
|
||||
}
|
||||
|
||||
pub fn initiate_sign_out(window: &mut Window, cx: &mut App) {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
copilot_toast(Some("Signing out of Copilot…"), window, cx);
|
||||
|
||||
let sign_out_task = copilot.update(cx, |copilot, cx| copilot.sign_out(cx));
|
||||
window
|
||||
.spawn(cx, async move |cx| match sign_out_task.await {
|
||||
Ok(()) => {
|
||||
cx.update(|window, cx| copilot_toast(Some("Signed out of Copilot"), window, cx))
|
||||
}
|
||||
Err(err) => cx.update(|window, cx| {
|
||||
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_error(&err, cx);
|
||||
})
|
||||
} else {
|
||||
log::error!("{:?}", err);
|
||||
}
|
||||
}),
|
||||
})
|
||||
.detach();
|
||||
let Some(workspace) = window.root::<Workspace>().flatten() else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let is_reinstall = false;
|
||||
initiate_sign_in_within_workspace(workspace, copilot, is_reinstall, window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn reinstall_and_sign_in(window: &mut Window, cx: &mut App) {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return;
|
||||
};
|
||||
let _ = copilot.update(cx, |copilot, cx| copilot.reinstall(cx));
|
||||
let is_reinstall = true;
|
||||
initiate_sign_in_impl(is_reinstall, window, cx);
|
||||
}
|
||||
|
||||
fn open_copilot_code_verification_window(copilot: &Entity<Copilot>, window: &Window, cx: &mut App) {
|
||||
let current_window_center = window.bounds().center();
|
||||
let height = px(450.);
|
||||
let width = px(350.);
|
||||
let window_bounds = WindowBounds::Windowed(gpui::bounds(
|
||||
current_window_center - point(height / 2.0, width / 2.0),
|
||||
gpui::size(height, width),
|
||||
));
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
kind: gpui::WindowKind::PopUp,
|
||||
window_bounds: Some(window_bounds),
|
||||
is_resizable: false,
|
||||
is_movable: true,
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
appears_transparent: true,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|window, cx| cx.new(|cx| CopilotCodeVerification::new(&copilot, window, cx)),
|
||||
)
|
||||
.context("Failed to open Copilot code verification window")
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn copilot_toast(message: Option<&'static str>, window: &Window, cx: &mut App) {
|
||||
const NOTIFICATION_ID: NotificationId = NotificationId::unique::<CopilotStatusToast>();
|
||||
|
||||
let Some(workspace) = window.root::<Workspace>().flatten() else {
|
||||
return;
|
||||
};
|
||||
|
||||
workspace.update(cx, |workspace, cx| match message {
|
||||
Some(message) => workspace.show_toast(Toast::new(NOTIFICATION_ID, message), cx),
|
||||
None => workspace.dismiss_toast(&NOTIFICATION_ID, cx),
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
reinstall_and_sign_in_within_workspace(workspace, copilot, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn initiate_sign_in_impl(is_reinstall: bool, window: &mut Window, cx: &mut App) {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return;
|
||||
};
|
||||
pub fn reinstall_and_sign_in_within_workspace(
|
||||
workspace: &mut Workspace,
|
||||
copilot: Entity<Copilot>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let _ = copilot.update(cx, |copilot, cx| copilot.reinstall(cx));
|
||||
let is_reinstall = true;
|
||||
initiate_sign_in_within_workspace(workspace, copilot, is_reinstall, window, cx);
|
||||
}
|
||||
|
||||
pub fn initiate_sign_in_within_workspace(
|
||||
workspace: &mut Workspace,
|
||||
copilot: Entity<Copilot>,
|
||||
is_reinstall: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
if matches!(copilot.read(cx).status(), Status::Disabled) {
|
||||
copilot.update(cx, |copilot, cx| copilot.start_copilot(false, true, cx));
|
||||
}
|
||||
match copilot.read(cx).status() {
|
||||
Status::Starting { task } => {
|
||||
copilot_toast(
|
||||
Some(if is_reinstall {
|
||||
"Copilot is reinstalling…"
|
||||
} else {
|
||||
"Copilot is starting…"
|
||||
}),
|
||||
window,
|
||||
workspace.show_toast(
|
||||
Toast::new(
|
||||
NotificationId::unique::<CopilotStatusToast>(),
|
||||
if is_reinstall {
|
||||
"Copilot is reinstalling..."
|
||||
} else {
|
||||
"Copilot is starting..."
|
||||
},
|
||||
),
|
||||
cx,
|
||||
);
|
||||
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
task.await;
|
||||
cx.update(|window, cx| {
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return;
|
||||
};
|
||||
match copilot.read(cx).status() {
|
||||
Status::Authorized => {
|
||||
copilot_toast(Some("Copilot has started."), window, cx)
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
task.await;
|
||||
if let Some(copilot) = cx.update(|_window, cx| Copilot::global(cx)).ok().flatten() {
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
match copilot.read(cx).status() {
|
||||
Status::Authorized => workspace.show_toast(
|
||||
Toast::new(
|
||||
NotificationId::unique::<CopilotStatusToast>(),
|
||||
"Copilot has started.",
|
||||
),
|
||||
cx,
|
||||
),
|
||||
_ => {
|
||||
workspace.dismiss_toast(
|
||||
&NotificationId::unique::<CopilotStatusToast>(),
|
||||
cx,
|
||||
);
|
||||
copilot
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.detach_and_log_err(cx);
|
||||
workspace.toggle_modal(window, cx, |_, cx| {
|
||||
CopilotCodeVerification::new(&copilot, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
copilot_toast(None, window, cx);
|
||||
copilot
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.detach_and_log_err(cx);
|
||||
open_copilot_code_verification_window(&copilot, window, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
_ => {
|
||||
copilot
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.detach();
|
||||
open_copilot_code_verification_window(&copilot, window, cx);
|
||||
workspace.toggle_modal(window, cx, |_, cx| {
|
||||
CopilotCodeVerification::new(&copilot, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign_out_within_workspace(
|
||||
workspace: &mut Workspace,
|
||||
copilot: Entity<Copilot>,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.show_toast(
|
||||
Toast::new(
|
||||
NotificationId::unique::<CopilotStatusToast>(),
|
||||
"Signing out of Copilot...",
|
||||
),
|
||||
cx,
|
||||
);
|
||||
let sign_out_task = copilot.update(cx, |copilot, cx| copilot.sign_out(cx));
|
||||
cx.spawn(async move |workspace, cx| match sign_out_task.await {
|
||||
Ok(()) => {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_toast(
|
||||
Toast::new(
|
||||
NotificationId::unique::<CopilotStatusToast>(),
|
||||
"Signed out of Copilot.",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_error(&err, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct CopilotCodeVerification {
|
||||
status: Status,
|
||||
connect_clicked: bool,
|
||||
focus_handle: FocusHandle,
|
||||
copilot: Entity<Copilot>,
|
||||
_subscription: Subscription,
|
||||
sign_up_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Focusable for CopilotCodeVerification {
|
||||
@@ -163,44 +170,29 @@ impl Focusable for CopilotCodeVerification {
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for CopilotCodeVerification {}
|
||||
impl ModalView for CopilotCodeVerification {
|
||||
fn on_before_dismiss(
|
||||
&mut self,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> workspace::DismissDecision {
|
||||
self.copilot.update(cx, |copilot, cx| {
|
||||
if matches!(copilot.status(), Status::SigningIn { .. }) {
|
||||
copilot.sign_out(cx).detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
workspace::DismissDecision::Dismiss(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl CopilotCodeVerification {
|
||||
pub fn new(copilot: &Entity<Copilot>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
window.on_window_should_close(cx, |window, cx| {
|
||||
if let Some(this) = window.root::<CopilotCodeVerification>().flatten() {
|
||||
this.update(cx, |this, cx| {
|
||||
this.before_dismiss(cx);
|
||||
});
|
||||
}
|
||||
true
|
||||
});
|
||||
cx.subscribe_in(
|
||||
&cx.entity(),
|
||||
window,
|
||||
|this, _, _: &DismissEvent, window, cx| {
|
||||
window.remove_window();
|
||||
this.before_dismiss(cx);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
pub fn new(copilot: &Entity<Copilot>, cx: &mut Context<Self>) -> Self {
|
||||
let status = copilot.read(cx).status();
|
||||
// Determine sign-up URL based on verification_uri domain if available
|
||||
let sign_up_url = if let Status::SigningIn {
|
||||
prompt: Some(ref prompt),
|
||||
} = status
|
||||
{
|
||||
// Extract domain from verification_uri to construct sign-up URL
|
||||
Self::get_sign_up_url_from_verification(&prompt.verification_uri)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self {
|
||||
status,
|
||||
connect_clicked: false,
|
||||
focus_handle: cx.focus_handle(),
|
||||
copilot: copilot.clone(),
|
||||
sign_up_url,
|
||||
_subscription: cx.observe(copilot, |this, copilot, cx| {
|
||||
let status = copilot.read(cx).status();
|
||||
match status {
|
||||
@@ -214,74 +206,54 @@ impl CopilotCodeVerification {
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: Status, cx: &mut Context<Self>) {
|
||||
// Update sign-up URL if we have a new verification URI
|
||||
if let Status::SigningIn {
|
||||
prompt: Some(ref prompt),
|
||||
} = status
|
||||
{
|
||||
self.sign_up_url = Self::get_sign_up_url_from_verification(&prompt.verification_uri);
|
||||
}
|
||||
self.status = status;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn get_sign_up_url_from_verification(verification_uri: &str) -> Option<String> {
|
||||
// Extract domain from verification URI using url crate
|
||||
if let Ok(url) = Url::parse(verification_uri)
|
||||
&& let Some(host) = url.host_str()
|
||||
&& !host.contains("github.com")
|
||||
{
|
||||
// For GHE, construct URL from domain
|
||||
Some(format!("https://{}/features/copilot", host))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_device_code(data: &PromptUserDeviceFlow, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let copied = cx
|
||||
.read_from_clipboard()
|
||||
.map(|item| item.text().as_ref() == Some(&data.user_code))
|
||||
.unwrap_or(false);
|
||||
|
||||
ButtonLike::new("copy-button")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||
.size(ButtonSize::Medium)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.child(Label::new(data.user_code.clone()))
|
||||
.child(Label::new(if copied { "Copied!" } else { "Copy" })),
|
||||
)
|
||||
.on_click({
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_1()
|
||||
.border_1()
|
||||
.border_muted(cx)
|
||||
.rounded_sm()
|
||||
.cursor_pointer()
|
||||
.justify_between()
|
||||
.on_mouse_down(gpui::MouseButton::Left, {
|
||||
let user_code = data.user_code.clone();
|
||||
move |_, window, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(user_code.clone()));
|
||||
window.refresh();
|
||||
}
|
||||
})
|
||||
.child(div().flex_1().child(Label::new(data.user_code.clone())))
|
||||
.child(div().flex_none().px_1().child(Label::new(if copied {
|
||||
"Copied!"
|
||||
} else {
|
||||
"Copy"
|
||||
})))
|
||||
}
|
||||
|
||||
fn render_prompting_modal(
|
||||
connect_clicked: bool,
|
||||
data: &PromptUserDeviceFlow,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl Element {
|
||||
let connect_button_label = if connect_clicked {
|
||||
"Waiting for connection…"
|
||||
"Waiting for connection..."
|
||||
} else {
|
||||
"Connect to GitHub"
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.gap_2p5()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.text_center()
|
||||
.child(Headline::new("Use GitHub Copilot in Zed").size(HeadlineSize::Large))
|
||||
.child(Headline::new("Use GitHub Copilot in Zed.").size(HeadlineSize::Large))
|
||||
.child(
|
||||
Label::new("Using Copilot requires an active subscription on GitHub.")
|
||||
.color(Color::Muted),
|
||||
@@ -289,149 +261,105 @@ impl CopilotCodeVerification {
|
||||
.child(Self::render_device_code(data, cx))
|
||||
.child(
|
||||
Label::new("Paste this code into GitHub after clicking the button below.")
|
||||
.color(Color::Muted),
|
||||
.size(ui::LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(
|
||||
Button::new("connect-button", connect_button_label)
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.on_click({
|
||||
let verification_uri = data.verification_uri.clone();
|
||||
cx.listener(move |this, _, _window, cx| {
|
||||
cx.open_url(&verification_uri);
|
||||
this.connect_clicked = true;
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new("copilot-enable-cancel-button", "Cancel")
|
||||
.full_width()
|
||||
.size(ButtonSize::Medium)
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})),
|
||||
),
|
||||
Button::new("connect-button", connect_button_label)
|
||||
.on_click({
|
||||
let verification_uri = data.verification_uri.clone();
|
||||
cx.listener(move |this, _, _window, cx| {
|
||||
cx.open_url(&verification_uri);
|
||||
this.connect_clicked = true;
|
||||
})
|
||||
})
|
||||
.full_width()
|
||||
.style(ButtonStyle::Filled),
|
||||
)
|
||||
.child(
|
||||
Button::new("copilot-enable-cancel-button", "Cancel")
|
||||
.full_width()
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_enabled_modal(cx: &mut Context<Self>) -> impl Element {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.text_center()
|
||||
.justify_center()
|
||||
.child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large))
|
||||
.child(Label::new("You're all set to use GitHub Copilot.").color(Color::Muted))
|
||||
.child(Label::new(
|
||||
"You can update your settings or sign out from the Copilot menu in the status bar.",
|
||||
))
|
||||
.child(
|
||||
Button::new("copilot-enabled-done-button", "Done")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_unauthorized_modal(&self, cx: &mut Context<Self>) -> impl Element {
|
||||
let sign_up_url = self
|
||||
.sign_up_url
|
||||
.as_deref()
|
||||
.unwrap_or(COPILOT_SIGN_UP_URL)
|
||||
.to_owned();
|
||||
let description = "Enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.";
|
||||
|
||||
fn render_unauthorized_modal(cx: &mut Context<Self>) -> impl Element {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.text_center()
|
||||
.justify_center()
|
||||
.child(
|
||||
Headline::new("You must have an active GitHub Copilot subscription.")
|
||||
.size(HeadlineSize::Large),
|
||||
)
|
||||
.child(Label::new(description).color(Color::Warning))
|
||||
.child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large))
|
||||
|
||||
.child(Label::new(
|
||||
"You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.",
|
||||
).color(Color::Warning))
|
||||
.child(
|
||||
Button::new("copilot-subscribe-button", "Subscribe on GitHub")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.on_click(move |_, _, cx| cx.open_url(&sign_up_url)),
|
||||
.on_click(|_, _, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
|
||||
)
|
||||
.child(
|
||||
Button::new("copilot-subscribe-cancel-button", "Cancel")
|
||||
.full_width()
|
||||
.size(ButtonSize::Medium)
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_error_modal(_cx: &mut Context<Self>) -> impl Element {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.text_center()
|
||||
.justify_center()
|
||||
.child(Headline::new("An Error Happened").size(HeadlineSize::Large))
|
||||
.child(Label::new(ERROR_LABEL).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("copilot-subscribe-button", "Reinstall Copilot and Sign In")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.icon(IconName::Download)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(|_, window, cx| reinstall_and_sign_in(window, cx)),
|
||||
)
|
||||
}
|
||||
fn render_loading(window: &mut Window, _: &mut Context<Self>) -> impl Element {
|
||||
let loading_icon = svg()
|
||||
.size_8()
|
||||
.path(IconName::ArrowCircle.path())
|
||||
.text_color(window.text_style().color)
|
||||
.with_animation(
|
||||
"icon_circle_arrow",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|svg, delta| svg.with_transformation(Transformation::rotate(percentage(delta))),
|
||||
);
|
||||
|
||||
fn before_dismiss(
|
||||
&mut self,
|
||||
cx: &mut Context<'_, CopilotCodeVerification>,
|
||||
) -> workspace::DismissDecision {
|
||||
self.copilot.update(cx, |copilot, cx| {
|
||||
if matches!(copilot.status(), Status::SigningIn { .. }) {
|
||||
copilot.sign_out(cx).detach_and_log_err(cx);
|
||||
}
|
||||
});
|
||||
workspace::DismissDecision::Dismiss(true)
|
||||
h_flex().justify_center().child(loading_icon)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for CopilotCodeVerification {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let prompt = match &self.status {
|
||||
Status::SigningIn { prompt: None } => Icon::new(IconName::ArrowCircle)
|
||||
.color(Color::Muted)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
Status::SigningIn { prompt: None } => {
|
||||
Self::render_loading(window, cx).into_any_element()
|
||||
}
|
||||
Status::SigningIn {
|
||||
prompt: Some(prompt),
|
||||
} => Self::render_prompting_modal(self.connect_clicked, prompt, cx).into_any_element(),
|
||||
Status::Unauthorized => {
|
||||
self.connect_clicked = false;
|
||||
self.render_unauthorized_modal(cx).into_any_element()
|
||||
Self::render_unauthorized_modal(cx).into_any_element()
|
||||
}
|
||||
Status::Authorized => {
|
||||
self.connect_clicked = false;
|
||||
Self::render_enabled_modal(cx).into_any_element()
|
||||
}
|
||||
Status::Error(..) => Self::render_error_modal(cx).into_any_element(),
|
||||
_ => div().into_any_element(),
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id("copilot_code_verification")
|
||||
.id("copilot code verification")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.size_full()
|
||||
.px_4()
|
||||
.py_8()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.elevation_3(cx)
|
||||
.w_96()
|
||||
.items_center()
|
||||
.p_4()
|
||||
.gap_2()
|
||||
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
@@ -445,243 +373,3 @@ impl Render for CopilotCodeVerification {
|
||||
.child(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfigurationView {
|
||||
copilot_status: Option<Status>,
|
||||
is_authenticated: fn(cx: &App) -> bool,
|
||||
edit_prediction: bool,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
pub enum ConfigurationMode {
|
||||
Chat,
|
||||
EditPrediction,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
pub fn new(
|
||||
is_authenticated: fn(cx: &App) -> bool,
|
||||
mode: ConfigurationMode,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let copilot = Copilot::global(cx);
|
||||
|
||||
Self {
|
||||
copilot_status: copilot.as_ref().map(|copilot| copilot.read(cx).status()),
|
||||
is_authenticated,
|
||||
edit_prediction: matches!(mode, ConfigurationMode::EditPrediction),
|
||||
_subscription: copilot.as_ref().map(|copilot| {
|
||||
cx.observe(copilot, |this, model, cx| {
|
||||
this.copilot_status = Some(model.read(cx).status());
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
fn is_starting(&self) -> bool {
|
||||
matches!(&self.copilot_status, Some(Status::Starting { .. }))
|
||||
}
|
||||
|
||||
fn is_signing_in(&self) -> bool {
|
||||
matches!(
|
||||
&self.copilot_status,
|
||||
Some(Status::SigningIn { .. })
|
||||
| Some(Status::SignedOut {
|
||||
awaiting_signing_in: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn is_error(&self) -> bool {
|
||||
matches!(&self.copilot_status, Some(Status::Error(_)))
|
||||
}
|
||||
|
||||
fn has_no_status(&self) -> bool {
|
||||
self.copilot_status.is_none()
|
||||
}
|
||||
|
||||
fn loading_message(&self) -> Option<SharedString> {
|
||||
if self.is_starting() {
|
||||
Some("Starting Copilot…".into())
|
||||
} else if self.is_signing_in() {
|
||||
Some("Signing into Copilot…".into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_loading_button(
|
||||
&self,
|
||||
label: impl Into<SharedString>,
|
||||
edit_prediction: bool,
|
||||
) -> impl IntoElement {
|
||||
ButtonLike::new("loading_button")
|
||||
.disabled(true)
|
||||
.style(ButtonStyle::Outlined)
|
||||
.when(edit_prediction, |this| this.size(ButtonSize::Medium))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_center()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.with_rotate_animation(4),
|
||||
)
|
||||
.child(Label::new(label)),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_sign_in_button(&self, edit_prediction: bool) -> impl IntoElement {
|
||||
let label = if edit_prediction {
|
||||
"Sign in to GitHub"
|
||||
} else {
|
||||
"Sign in to use GitHub Copilot"
|
||||
};
|
||||
|
||||
Button::new("sign_in", label)
|
||||
.map(|this| {
|
||||
if edit_prediction {
|
||||
this.size(ButtonSize::Medium)
|
||||
} else {
|
||||
this.full_width()
|
||||
}
|
||||
})
|
||||
.style(ButtonStyle::Outlined)
|
||||
.icon(IconName::Github)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(|_, window, cx| initiate_sign_in(window, cx))
|
||||
}
|
||||
|
||||
fn render_reinstall_button(&self, edit_prediction: bool) -> impl IntoElement {
|
||||
let label = if edit_prediction {
|
||||
"Reinstall and Sign in"
|
||||
} else {
|
||||
"Reinstall Copilot and Sign in"
|
||||
};
|
||||
|
||||
Button::new("reinstall_and_sign_in", label)
|
||||
.map(|this| {
|
||||
if edit_prediction {
|
||||
this.size(ButtonSize::Medium)
|
||||
} else {
|
||||
this.full_width()
|
||||
}
|
||||
})
|
||||
.style(ButtonStyle::Outlined)
|
||||
.icon(IconName::Download)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(|_, window, cx| reinstall_and_sign_in(window, cx))
|
||||
}
|
||||
|
||||
fn render_for_edit_prediction(&self) -> impl IntoElement {
|
||||
let container = |description: SharedString, action: AnyElement| {
|
||||
h_flex()
|
||||
.pt_2p5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.max_w_1_2()
|
||||
.child(Label::new("Authenticate To Use"))
|
||||
.child(
|
||||
Label::new(description)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
.child(action)
|
||||
};
|
||||
|
||||
let start_label = "To use Copilot for edit predictions, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot subscription.".into();
|
||||
let no_status_label = "Copilot requires an active GitHub Copilot subscription. Please ensure Copilot is configured and try again, or use a different edit predictions provider.".into();
|
||||
|
||||
if let Some(msg) = self.loading_message() {
|
||||
container(
|
||||
start_label,
|
||||
self.render_loading_button(msg, true).into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
} else if self.is_error() {
|
||||
container(
|
||||
ERROR_LABEL.into(),
|
||||
self.render_reinstall_button(true).into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
} else if self.has_no_status() {
|
||||
container(
|
||||
no_status_label,
|
||||
self.render_sign_in_button(true).into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
container(
|
||||
start_label,
|
||||
self.render_sign_in_button(true).into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_for_chat(&self) -> impl IntoElement {
|
||||
let start_label = "To use Zed's agent with GitHub Copilot, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot Chat subscription.";
|
||||
let no_status_label = "Copilot Chat requires an active GitHub Copilot subscription. Please ensure Copilot is configured and try again, or use a different LLM provider.";
|
||||
|
||||
if let Some(msg) = self.loading_message() {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(start_label))
|
||||
.child(self.render_loading_button(msg, false))
|
||||
.into_any_element()
|
||||
} else if self.is_error() {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(ERROR_LABEL))
|
||||
.child(self.render_reinstall_button(false))
|
||||
.into_any_element()
|
||||
} else if self.has_no_status() {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(no_status_label))
|
||||
.child(self.render_sign_in_button(false))
|
||||
.into_any_element()
|
||||
} else {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(start_label))
|
||||
.child(self.render_sign_in_button(false))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ConfigurationView {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_authenticated = self.is_authenticated;
|
||||
|
||||
if is_authenticated(cx) {
|
||||
return ConfiguredApiCard::new("Authorized")
|
||||
.button_label("Sign Out")
|
||||
.on_click(|_, window, cx| {
|
||||
initiate_sign_out(window, cx);
|
||||
})
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
if self.edit_prediction {
|
||||
self.render_for_edit_prediction().into_any_element()
|
||||
} else {
|
||||
self.render_for_chat().into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1557,7 +1557,7 @@ impl Panel for DebugPanel {
|
||||
self.sessions_with_children.keys().for_each(|session_item| {
|
||||
session_item.update(cx, |item, cx| {
|
||||
item.running_state()
|
||||
.update(cx, |state, cx| state.invert_axies(cx))
|
||||
.update(cx, |state, _| state.invert_axies())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ pub(crate) fn new_debugger_pane(
|
||||
debug_assert!(_previous_subscription.is_none());
|
||||
running
|
||||
.panes
|
||||
.split(&this_pane, &new_pane, split_direction, cx)?;
|
||||
.split(&this_pane, &new_pane, split_direction)?;
|
||||
anyhow::Ok(new_pane)
|
||||
})
|
||||
})
|
||||
@@ -1462,7 +1462,7 @@ impl RunningState {
|
||||
this.serialize_layout(window, cx);
|
||||
match event {
|
||||
Event::Remove { .. } => {
|
||||
let _did_find_pane = this.panes.remove(source_pane, cx).is_ok();
|
||||
let _did_find_pane = this.panes.remove(source_pane).is_ok();
|
||||
debug_assert!(_did_find_pane);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1889,9 +1889,9 @@ impl RunningState {
|
||||
Member::Axis(group_root)
|
||||
}
|
||||
|
||||
pub(crate) fn invert_axies(&mut self, cx: &mut App) {
|
||||
pub(crate) fn invert_axies(&mut self) {
|
||||
self.dock_axis = self.dock_axis.invert();
|
||||
self.panes.invert_axies(cx);
|
||||
self.panes.invert_axies();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user