Compare commits

..

1 Commits

Author SHA1 Message Date
Mikayla Maki
7c2dffc792 Wire through IPC mechanism for GIT_ASKPASS
co-authored-by: julia@zed.dev
2025-02-28 20:56:11 -08:00
549 changed files with 10897 additions and 22522 deletions

View File

@@ -26,6 +26,3 @@ rustflags = [
"-C", "-C",
"target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows "target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows
] ]
[env]
MACOSX_DEPLOYMENT_TARGET = "10.15.7"

View File

@@ -1,51 +0,0 @@
name: Git Beta
description: There is a bug related to new Git features in Zed
type: "Bug"
labels: [git]
title: "Git Beta: <a short description of the Git bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary>
<!-- Click below this line and paste or drag-and-drop your log-->
```
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

View File

@@ -236,24 +236,12 @@ jobs:
if: always() if: always()
run: rm -rf ./../.cargo run: rm -rf ./../.cargo
windows_clippy: windows_tests:
timeout-minutes: 60 timeout-minutes: 60
name: (Windows) Run Clippy name: (Windows) Run Clippy and tests
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-2 runs-on: hosted-windows-2
steps: steps:
# Temporarily Collect some metadata about the hardware behind our runners.
- name: GHA Runner Info
run: |
Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute?api-version=2023-07-01" |
ConvertTo-Json -Depth 10 |
jq "{ vm_size: .vmSize, location: .location, os_disk_gb: (.storageProfile.osDisk.diskSizeGB | tonumber), rs_disk_gb: (.storageProfile.resourceDisk.size | tonumber / 1024) }"
@{
Cores = (Get-CimInstance Win32_Processor).NumberOfCores
vCPUs = (Get-CimInstance Win32_Processor).NumberOfLogicalProcessors
RamGb = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
cpuid = (Get-CimInstance Win32_Processor).Name.Trim()
} | ConvertTo-Json
# more info here:- https://github.com/rust-lang/cargo/issues/13020 # more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git - name: Enable longer pathnames for git
run: git config --system core.longpaths true run: git config --system core.longpaths true
@@ -287,69 +275,6 @@ jobs:
working-directory: ${{ env.ZED_WORKSPACE }} working-directory: ${{ env.ZED_WORKSPACE }}
run: ./script/clippy.ps1 run: ./script/clippy.ps1
- name: Check dev drive space
working-directory: ${{ env.ZED_WORKSPACE }}
# `setup-dev-driver.ps1` creates a 100GB drive, with CI taking up ~45GB of the drive.
run: ./script/exit-ci-if-dev-drive-is-full.ps1 95
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file
if: always()
run: |
if (Test-Path "${{ env.CARGO_HOME }}/config.toml") {
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
}
# Windows CI takes twice as long as our other platforms and fast github hosted runners are expensive.
# But we still want to do CI, so let's only run tests on main and come back to this when we're
# ready to self host our Windows CI (e.g. during the push for full Windows support)
windows_tests:
timeout-minutes: 60
name: (Windows) Run Tests
if: ${{ github.repository_owner == 'zed-industries' && (github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'windows')) }}
runs-on: hosted-windows-2
steps:
# Temporarily Collect some metadata about the hardware behind our runners.
- name: GHA Runner Info
run: |
Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute?api-version=2023-07-01" |
ConvertTo-Json -Depth 10 |
jq "{ vm_size: .vmSize, location: .location, os_disk_gb: (.storageProfile.osDisk.diskSizeGB | tonumber), rs_disk_gb: (.storageProfile.resourceDisk.size | tonumber / 1024) }"
@{
Cores = (Get-CimInstance Win32_Processor).NumberOfCores
vCPUs = (Get-CimInstance Win32_Processor).NumberOfLogicalProcessors
RamGb = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
cpuid = (Get-CimInstance Win32_Processor).Name.Trim()
} | ConvertTo-Json
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
run: git config --system core.longpaths true
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Create Dev Drive using ReFS
run: ./script/setup-dev-driver.ps1
# actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
- name: Copy Git Repo to Dev Drive
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.ZED_WORKSPACE }}" -Recurse
- name: Cache dependencies
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
workspaces: ${{ env.ZED_WORKSPACE }}
cache-provider: "github"
- name: Configure CI
run: |
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
- name: Run tests - name: Run tests
uses: ./.github/actions/run_tests_windows uses: ./.github/actions/run_tests_windows
with: with:
@@ -367,10 +292,7 @@ jobs:
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug. # Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
- name: Clean CI config file - name: Clean CI config file
if: always() if: always()
run: | run: Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
if (Test-Path "${{ env.CARGO_HOME }}/config.toml") {
Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
}
bundle-mac: bundle-mac:
timeout-minutes: 120 timeout-minutes: 120
@@ -500,13 +422,6 @@ jobs:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz path: target/release/zed-*.tar.gz
- name: Upload Linux remote server to workflow run if main branch or specific label
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.gz
path: target/zed-remote-server-linux-x86_64.gz
- name: Upload app bundle to release - name: Upload app bundle to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with: with:
@@ -555,13 +470,6 @@ jobs:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz path: target/release/zed-*.tar.gz
- name: Upload Linux remote server to workflow run if main branch or specific label
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.gz
path: target/zed-remote-server-linux-aarch64.gz
- name: Upload app bundle to release - name: Upload app bundle to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with: with:

643
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ resolver = "2"
members = [ members = [
"crates/activity_indicator", "crates/activity_indicator",
"crates/anthropic", "crates/anthropic",
"crates/askpass",
"crates/assets", "crates/assets",
"crates/assistant", "crates/assistant",
"crates/assistant2", "crates/assistant2",
@@ -104,7 +103,6 @@ members = [
"crates/project_panel", "crates/project_panel",
"crates/project_symbols", "crates/project_symbols",
"crates/prompt_library", "crates/prompt_library",
"crates/prompt_store",
"crates/proto", "crates/proto",
"crates/recent_projects", "crates/recent_projects",
"crates/refineable", "crates/refineable",
@@ -118,7 +116,6 @@ members = [
"crates/rope", "crates/rope",
"crates/rpc", "crates/rpc",
"crates/schema_generator", "crates/schema_generator",
"crates/scripting_tool",
"crates/search", "crates/search",
"crates/semantic_index", "crates/semantic_index",
"crates/semantic_version", "crates/semantic_version",
@@ -169,9 +166,15 @@ members = [
# Extensions # Extensions
# #
"extensions/csharp",
"extensions/deno",
"extensions/elixir",
"extensions/emmet", "extensions/emmet",
"extensions/erlang",
"extensions/glsl", "extensions/glsl",
"extensions/haskell",
"extensions/html", "extensions/html",
"extensions/lua",
"extensions/perplexity", "extensions/perplexity",
"extensions/proto", "extensions/proto",
"extensions/purescript", "extensions/purescript",
@@ -205,7 +208,6 @@ edition = "2021"
activity_indicator = { path = "crates/activity_indicator" } activity_indicator = { path = "crates/activity_indicator" }
ai = { path = "crates/ai" } ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" } anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" } assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" } assistant = { path = "crates/assistant" }
assistant2 = { path = "crates/assistant2" } assistant2 = { path = "crates/assistant2" }
@@ -306,7 +308,6 @@ project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" } project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" } project_symbols = { path = "crates/project_symbols" }
prompt_library = { path = "crates/prompt_library" } prompt_library = { path = "crates/prompt_library" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" } proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" } recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" } refineable = { path = "crates/refineable" }
@@ -318,7 +319,6 @@ reqwest_client = { path = "crates/reqwest_client" }
rich_text = { path = "crates/rich_text" } rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" } rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" } rpc = { path = "crates/rpc" }
scripting_tool = { path = "crates/scripting_tool" }
search = { path = "crates/search" } search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" } semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" } semantic_version = { path = "crates/semantic_version" }
@@ -370,7 +370,7 @@ zeta = { path = "crates/zeta" }
# #
aho-corasick = "1.1" aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" } alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" }
any_vec = "0.14" any_vec = "0.14"
anyhow = "1.0.86" anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] }
@@ -451,7 +451,6 @@ livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "
], default-features = false } ], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0" markup5ever_rcdom = "0.3.0"
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
nanoid = "0.4" nanoid = "0.4"
nbformat = { version = "0.10.0" } nbformat = { version = "0.10.0" }
nix = "0.29" nix = "0.29"
@@ -537,7 +536,7 @@ tiny_http = "0.8"
toml = "0.8" toml = "0.8"
tokio = { version = "1" } tokio = { version = "1" }
tower-http = "0.4.4" tower-http = "0.4.4"
tree-sitter = { version = "0.25.3", features = ["wasm"] } tree-sitter = { version = "0.25.2", features = ["wasm"] }
tree-sitter-bash = "0.23" tree-sitter-bash = "0.23"
tree-sitter-c = "0.23" tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23" tree-sitter-cpp = "0.23"
@@ -597,14 +596,12 @@ features = [
] ]
[workspace.dependencies.windows] [workspace.dependencies.windows]
version = "0.60" version = "0.58"
features = [ features = [
"Foundation_Collections", "implement",
"Foundation_Numerics", "Foundation_Numerics",
"Storage_Search", "Storage",
"Storage_Streams",
"System_Threading", "System_Threading",
"UI_StartScreen",
"UI_ViewManagement", "UI_ViewManagement",
"Wdk_System_SystemServices", "Wdk_System_SystemServices",
"Win32_Globalization", "Win32_Globalization",
@@ -623,11 +620,9 @@ features = [
"Win32_System_Com_StructuredStorage", "Win32_System_Com_StructuredStorage",
"Win32_System_Console", "Win32_System_Console",
"Win32_System_DataExchange", "Win32_System_DataExchange",
"Win32_System_IO",
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
"Win32_System_Memory", "Win32_System_Memory",
"Win32_System_Ole", "Win32_System_Ole",
"Win32_System_Pipes",
"Win32_System_SystemInformation", "Win32_System_SystemInformation",
"Win32_System_SystemServices", "Win32_System_SystemServices",
"Win32_System_Threading", "Win32_System_Threading",
@@ -753,9 +748,5 @@ new_ret_no_self = { level = "allow" }
should_implement_trait = { level = "allow" } should_implement_trait = { level = "allow" }
let_underscore_future = "allow" let_underscore_future = "allow"
# in Rust it can be very tedious to reduce argument count without
# running afoul of the borrow checker.
too_many_arguments = "allow"
[workspace.metadata.cargo-machete] [workspace.metadata.cargo-machete]
ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"] ignored = ["bindgen", "cbindgen", "prost_build", "serde"]

View File

@@ -1,10 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5871 5.40624C12.8514 5.14195 13 4.78346 13 4.40965C13 4.03583 12.8516 3.67731 12.5873 3.41295C12.323 3.14859 11.9645 3.00005 11.5907 3C11.2169 2.99995 10.8584 3.14841 10.594 3.4127L3.92098 10.0874C3.80488 10.2031 3.71903 10.3456 3.67097 10.5024L3.01047 12.6784C2.99754 12.7217 2.99657 12.7676 3.00764 12.8113C3.01872 12.8551 3.04143 12.895 3.07337 12.9269C3.1053 12.9588 3.14528 12.9815 3.18905 12.9925C3.23282 13.0035 3.27875 13.0024 3.32197 12.9894L5.49849 12.3294C5.65508 12.2818 5.79758 12.1964 5.91349 12.0809L12.5871 5.40624Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 4L12 6" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.38818 3.53598V2.53598" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.56982 12.6995L9.56982 13.6995" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.38818 6.53598H3.38818" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5698 9.69949L12.5698 9.69949" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.38818 4.53598L3.38818 3.53598" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5698 11.6995L12.5698 12.6995" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36197 1.67985C5.3748 1.41534 4.36011 2.00117 4.0956 2.98834L2.17985 10.138C1.91534 11.1252 2.50117 12.1399 3.48833 12.4044L10.638 14.3202C11.6252 14.5847 12.6399 13.9988 12.9044 13.0117L14.8202 5.86197C15.0847 4.8748 14.4988 3.86012 13.5117 3.59561L6.36197 1.67985ZM10.0457 4.58266C9.77896 4.51119 9.50479 4.66948 9.43332 4.93621L8.76235 7.44028C8.69088 7.70701 8.84917 7.98118 9.11591 8.05265L11.62 8.72362C11.8867 8.79509 12.1609 8.6368 12.2324 8.37006L12.9033 5.86599C12.9748 5.59926 12.8165 5.32509 12.5498 5.25362L10.0457 4.58266Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 707 B

View File

@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 13L1.5 5H11.5L6.5 13Z" fill="black"/>
<path d="M14 9H9L11.5 5L14 9Z" fill="black" fill-opacity="0.75"/>
<path d="M9 9L14 9L11.5 13L9 9Z" fill="black" fill-opacity="0.65"/>
<path d="M14 5L15.25 7L12.75 7L14 5Z" fill="black" fill-opacity="0.5"/>
<path d="M14 9L12.75 7H15.25L14 9Z" fill="black" fill-opacity="0.55"/>
</svg>

Before

Width:  |  Height:  |  Size: 432 B

View File

@@ -1,6 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 3.25C4.02614 3.25 4.25 3.02614 4.25 2.75C4.25 2.47386 4.02614 2.25 3.75 2.25C3.47386 2.25 3.25 2.47386 3.25 2.75C3.25 3.02614 3.47386 3.25 3.75 3.25ZM3.75 4.25C4.57843 4.25 5.25 3.57843 5.25 2.75C5.25 1.92157 4.57843 1.25 3.75 1.25C2.92157 1.25 2.25 1.92157 2.25 2.75C2.25 3.57843 2.92157 4.25 3.75 4.25Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.25 3.25C8.52614 3.25 8.75 3.02614 8.75 2.75C8.75 2.47386 8.52614 2.25 8.25 2.25C7.97386 2.25 7.75 2.47386 7.75 2.75C7.75 3.02614 7.97386 3.25 8.25 3.25ZM8.25 4.25C9.07843 4.25 9.75 3.57843 9.75 2.75C9.75 1.92157 9.07843 1.25 8.25 1.25C7.42157 1.25 6.75 1.92157 6.75 2.75C6.75 3.57843 7.42157 4.25 8.25 4.25Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 9.75C4.02614 9.75 4.25 9.52614 4.25 9.25C4.25 8.97386 4.02614 8.75 3.75 8.75C3.47386 8.75 3.25 8.97386 3.25 9.25C3.25 9.52614 3.47386 9.75 3.75 9.75ZM3.75 10.75C4.57843 10.75 5.25 10.0784 5.25 9.25C5.25 8.42157 4.57843 7.75 3.75 7.75C2.92157 7.75 2.25 8.42157 2.25 9.25C2.25 10.0784 2.92157 10.75 3.75 10.75Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 3.75H4.25V5.59609C4.67823 5.35824 5.24991 5.25 6 5.25H7.25017C7.5262 5.25 7.75 5.02625 7.75 4.75V3.75H8.75V4.75C8.75 5.57832 8.07871 6.25 7.25017 6.25H6C5.14559 6.25 4.77639 6.41132 4.59684 6.56615C4.42571 6.71373 4.33877 6.92604 4.25 7.30651V8.25H3.25V3.75Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5"/> <g clip-path="url(#clip0_2131_1193)">
<path d="M7 11H8M8 11H9M8 11V8.1C8 8.04477 7.95523 8 7.9 8H7" stroke="black" stroke-width="1.5" stroke-linecap="round"/> <circle cx="7" cy="7" r="6" stroke="black" stroke-width="1.5"/>
<path d="M8 6.5C8.55228 6.5 9 6.05228 9 5.5C9 4.94772 8.55228 4.5 8 4.5C7.44772 4.5 7 4.94772 7 5.5C7 6.05228 7.44772 6.5 8 6.5Z" fill="black"/> <path d="M6 10H7M8 10H7M7 10V7.1C7 7.04477 6.95523 7 6.9 7H6" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="7" cy="4.5" r="1" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2131_1193">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 479 B

View File

@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.6" d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
<path d="M14 8L10 12M14 12L10 8" stroke="black" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 296 B

View File

@@ -10,8 +10,8 @@
"pagedown": "menu::SelectLast", "pagedown": "menu::SelectLast",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"tab": "menu::SelectNext", "tab": "menu::SelectNext",
"ctrl-p": "menu::SelectPrevious", "ctrl-p": "menu::SelectPrev",
"shift-tab": "menu::SelectPrevious", "shift-tab": "menu::SelectPrev",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"ctrl-escape": "menu::Cancel", "ctrl-escape": "menu::Cancel",
@@ -38,14 +38,14 @@
{ {
"context": "Picker || menu", "context": "Picker || menu",
"bindings": { "bindings": {
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"down": "menu::SelectNext" "down": "menu::SelectNext"
} }
}, },
{ {
"context": "Prompt", "context": "Prompt",
"bindings": { "bindings": {
"left": "menu::SelectPrevious", "left": "menu::SelectPrev",
"right": "menu::SelectNext" "right": "menu::SelectNext"
} }
}, },
@@ -57,7 +57,7 @@
"backspace": "editor::Backspace", "backspace": "editor::Backspace",
"delete": "editor::Delete", "delete": "editor::Delete",
"tab": "editor::Tab", "tab": "editor::Tab",
"shift-tab": "editor::Backtab", "shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine", "ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose", // "ctrl-t": "editor::Transpose",
"ctrl-k ctrl-q": "editor::Rewrap", "ctrl-k ctrl-q": "editor::Rewrap",
@@ -180,7 +180,7 @@
"ctrl-k c": "assistant::CopyCode", "ctrl-k c": "assistant::CopyCode",
"ctrl-shift-e": "project_panel::ToggleFocus", "ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-g": "search::SelectNextMatch", "ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPreviousMatch", "ctrl-shift-g": "search::SelectPrevMatch",
"ctrl-alt-/": "assistant::ToggleModelSelector", "ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-k h": "assistant::DeployHistory", "ctrl-k h": "assistant::DeployHistory",
"ctrl-k l": "assistant::DeployPromptLibrary", "ctrl-k l": "assistant::DeployPromptLibrary",
@@ -203,7 +203,7 @@
"escape": "buffer_search::Dismiss", "escape": "buffer_search::Dismiss",
"tab": "buffer_search::FocusEditor", "tab": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch", "enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPreviousMatch", "shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
"find": "search::FocusSearch", "find": "search::FocusSearch",
"ctrl-f": "search::FocusSearch", "ctrl-f": "search::FocusSearch",
@@ -272,7 +272,7 @@
"alt-8": ["pane::ActivateItem", 7], "alt-8": ["pane::ActivateItem", 7],
"alt-9": ["pane::ActivateItem", 8], "alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem", "alt-0": "pane::ActivateLastItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft", "ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight", "ctrl-shift-pagedown": "pane::SwapItemRight",
@@ -290,8 +290,8 @@
"forward": "pane::GoForward", "forward": "pane::GoForward",
"ctrl-alt-g": "search::SelectNextMatch", "ctrl-alt-g": "search::SelectNextMatch",
"f3": "search::SelectNextMatch", "f3": "search::SelectNextMatch",
"ctrl-alt-shift-g": "search::SelectPreviousMatch", "ctrl-alt-shift-g": "search::SelectPrevMatch",
"shift-f3": "search::SelectPreviousMatch", "shift-f3": "search::SelectPrevMatch",
"shift-find": "project_search::ToggleFocus", "shift-find": "project_search::ToggleFocus",
"ctrl-shift-f": "project_search::ToggleFocus", "ctrl-shift-f": "project_search::ToggleFocus",
"ctrl-alt-shift-h": "search::ToggleReplace", "ctrl-alt-shift-h": "search::ToggleReplace",
@@ -334,7 +334,7 @@
"ctrl-u": "editor::UndoSelection", "ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection", "ctrl-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic", "f8": "editor::GoToDiagnostic",
"shift-f8": "editor::GoToPreviousDiagnostic", "shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit", "alt-f12": "editor::GoToDefinitionSplit",
@@ -373,7 +373,7 @@
"alt-y": "git::StageAndNext", "alt-y": "git::StageAndNext",
"alt-shift-y": "git::UnstageAndNext", "alt-shift-y": "git::UnstageAndNext",
"alt-.": "editor::GoToHunk", "alt-.": "editor::GoToHunk",
"alt-,": "editor::GoToPreviousHunk" "alt-,": "editor::GoToPrevHunk"
} }
}, },
{ {
@@ -475,7 +475,9 @@
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd", "ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart", "ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
// "ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-alt-right": "editor::MoveToNextSubwordEnd", "ctrl-alt-right": "editor::MoveToNextSubwordEnd",
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
"ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart",
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
@@ -534,8 +536,8 @@
{ {
"context": "Editor && (showing_code_actions || showing_completions)", "context": "Editor && (showing_code_actions || showing_completions)",
"bindings": { "bindings": {
"ctrl-p": "editor::ContextMenuPrevious", "ctrl-p": "editor::ContextMenuPrev",
"up": "editor::ContextMenuPrevious", "up": "editor::ContextMenuPrev",
"ctrl-n": "editor::ContextMenuNext", "ctrl-n": "editor::ContextMenuNext",
"down": "editor::ContextMenuNext", "down": "editor::ContextMenuNext",
"pageup": "editor::ContextMenuFirst", "pageup": "editor::ContextMenuFirst",
@@ -563,7 +565,7 @@
"ctrl-alt-enter": "editor::OpenExcerptsSplit", "ctrl-alt-enter": "editor::OpenExcerptsSplit",
"ctrl-shift-e": "pane::RevealInProjectPanel", "ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk", "ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk", "ctrl-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist", "ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints" "ctrl-:": "editor::ToggleInlayHints"
} }
@@ -610,29 +612,12 @@
"ctrl-alt-e": "assistant2::RemoveAllContext" "ctrl-alt-e": "assistant2::RemoveAllContext"
} }
}, },
{
"context": "AssistantPanel2 && prompt_editor",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "assistant2::NewPromptEditor",
"cmd-alt-t": "assistant2::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"bindings": { "bindings": {
"enter": "assistant2::Chat" "enter": "assistant2::Chat"
} }
}, },
{
"context": "EditMessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel",
"enter": "menu::Confirm",
"alt-enter": "editor::Newline"
}
},
{ {
"context": "ContextStrip", "context": "ContextStrip",
"bindings": { "bindings": {
@@ -677,7 +662,7 @@
"alt-ctrl-r": "outline_panel::RevealInFileManager", "alt-ctrl-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::Open", "space": "outline_panel::Open",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts", "alt-enter": "editor::OpenExcerpts",
"ctrl-alt-enter": "editor::OpenExcerptsSplit" "ctrl-alt-enter": "editor::OpenExcerptsSplit"
} }
@@ -715,7 +700,7 @@
"shift-find": "project_panel::NewSearchInDirectory", "shift-find": "project_panel::NewSearchInDirectory",
"ctrl-shift-f": "project_panel::NewSearchInDirectory", "ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"escape": "menu::Cancel" "escape": "menu::Cancel"
} }
}, },
@@ -728,57 +713,30 @@
{ {
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"bindings": { "bindings": {
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"alt-y": "git::StageFile",
"alt-shift-y": "git::UnstageFile",
"ctrl-alt-y": "git::ToggleStaged",
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll",
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus",
"ctrl-enter": "git::Commit", "ctrl-enter": "git::Commit",
"alt-enter": "menu::SecondaryConfirm", "alt-enter": "menu::SecondaryConfirm"
"backspace": "git::RestoreFile"
} }
}, },
{ {
"context": "GitCommit > Editor", "context": "GitCommit > Editor",
"bindings": { "bindings": {
"escape": "menu::Cancel",
"enter": "editor::Newline", "enter": "editor::Newline",
"ctrl-enter": "git::Commit", "ctrl-enter": "git::Commit"
"alt-l": "git::GenerateCommitMessage"
}
},
{
"context": "GitPanel",
"use_key_equivalents": true,
"bindings": {
"ctrl-g ctrl-g": "git::Fetch",
"ctrl-g up": "git::Push",
"ctrl-g down": "git::Pull",
"ctrl-g shift-up": "git::ForcePush",
"ctrl-g d": "git::Diff",
"ctrl-g backspace": "git::RestoreTrackedFiles",
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll"
} }
}, },
{ {
"context": "GitDiff > Editor", "context": "GitDiff > Editor",
"bindings": { "bindings": {
"ctrl-enter": "git::Commit", "ctrl-enter": "git::Commit"
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll"
}
},
{
"context": "AskPass > Editor",
"bindings": {
"enter": "menu::Confirm"
} }
}, },
{ {
@@ -787,10 +745,16 @@
"escape": "git_panel::FocusChanges", "escape": "git_panel::FocusChanges",
"tab": "git_panel::FocusChanges", "tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges", "shift-tab": "git_panel::FocusChanges",
"enter": "editor::Newline",
"ctrl-enter": "git::Commit", "ctrl-enter": "git::Commit",
"alt-up": "git_panel::FocusChanges", "alt-up": "git_panel::FocusChanges"
"alt-l": "git::GenerateCommitMessage" }
},
{
"context": "GitCommit > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
"ctrl-enter": "git::Commit"
} }
}, },
{ {
@@ -815,9 +779,6 @@
{ {
"context": "Picker > Editor", "context": "Picker > Editor",
"bindings": { "bindings": {
"escape": "menu::Cancel",
"up": "menu::SelectPrevious",
"down": "menu::SelectNext",
"tab": "picker::ConfirmCompletion", "tab": "picker::ConfirmCompletion",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }] "alt-enter": ["picker::ConfirmInput", { "secondary": false }]
} }
@@ -831,7 +792,7 @@
{ {
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"bindings": { "bindings": {
"ctrl-shift-p": "file_finder::SelectPrevious", "ctrl-shift-p": "file_finder::SelectPrev",
"ctrl-j": "pane::SplitDown", "ctrl-j": "pane::SplitDown",
"ctrl-k": "pane::SplitUp", "ctrl-k": "pane::SplitUp",
"ctrl-h": "pane::SplitLeft", "ctrl-h": "pane::SplitLeft",
@@ -841,8 +802,8 @@
{ {
"context": "TabSwitcher", "context": "TabSwitcher",
"bindings": { "bindings": {
"ctrl-shift-tab": "menu::SelectPrevious", "ctrl-shift-tab": "menu::SelectPrev",
"ctrl-up": "menu::SelectPrevious", "ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext", "ctrl-down": "menu::SelectNext",
"ctrl-backspace": "tab_switcher::CloseSelectedItem" "ctrl-backspace": "tab_switcher::CloseSelectedItem"
} }

View File

@@ -1,15 +1,4 @@
[ [
// Moved before Standard macOS bindings so that `cmd-w` is not the last binding for
// `workspace::CloseWindow` and displayed/intercepted by macOS
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "prompt_library::NewPrompt",
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
"cmd-w": "workspace::CloseWindow"
}
},
// Standard macOS bindings // Standard macOS bindings
{ {
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -25,14 +14,14 @@
"tab": "menu::SelectNext", "tab": "menu::SelectNext",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"shift-tab": "menu::SelectPrevious", "shift-tab": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrevious", "ctrl-p": "menu::SelectPrev",
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm", "cmd-enter": "menu::SecondaryConfirm",
"cmd-escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel", "ctrl-escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel", "ctrl-c": "menu::Cancel",
"escape": "menu::Cancel", "escape": "menu::Cancel",
"alt-shift-enter": "menu::Restart", "alt-shift-enter": "menu::Restart",
@@ -65,7 +54,7 @@
"ctrl-d": "editor::Delete", "ctrl-d": "editor::Delete",
"delete": "editor::Delete", "delete": "editor::Delete",
"tab": "editor::Tab", "tab": "editor::Tab",
"shift-tab": "editor::Backtab", "shift-tab": "editor::TabPrev",
"ctrl-t": "editor::Transpose", "ctrl-t": "editor::Transpose",
"ctrl-k": "editor::KillRingCut", "ctrl-k": "editor::KillRingCut",
"ctrl-y": "editor::KillRingYank", "ctrl-y": "editor::KillRingYank",
@@ -108,8 +97,8 @@
"cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }], "cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], "ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }], "end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
"cmd-up": "editor::MoveToBeginning", "cmd-up": "editor::MoveToStartOfExcerpt",
"cmd-down": "editor::MoveToEnd", "cmd-down": "editor::MoveToEndOfExcerpt",
"cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left` "cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left`
"cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right` "cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right`
"shift-up": "editor::SelectUp", "shift-up": "editor::SelectUp",
@@ -124,12 +113,11 @@
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect "alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"ctrl-shift-up": "editor::SelectToStartOfParagraph", "ctrl-shift-up": "editor::SelectToStartOfParagraph",
"ctrl-shift-down": "editor::SelectToEndOfParagraph", "ctrl-shift-down": "editor::SelectToEndOfParagraph",
"cmd-shift-up": "editor::SelectToBeginning", "cmd-shift-up": "editor::SelectToStartOfExcerpt",
"cmd-shift-down": "editor::SelectToEnd", "cmd-shift-down": "editor::SelectToEndOfExcerpt",
"cmd-a": "editor::SelectAll", "cmd-a": "editor::SelectAll",
"cmd-l": "editor::SelectLine", "cmd-l": "editor::SelectLine",
"cmd-shift-i": "editor::Format", "cmd-shift-i": "editor::Format",
"alt-shift-o": "editor::OrganizeImports",
"cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
"ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
@@ -172,16 +160,6 @@
"alt-enter": "editor::OpenSelectionsInMultibuffer" "alt-enter": "editor::OpenSelectionsInMultibuffer"
} }
}, },
{
"context": "Editor && multibuffer",
"use_key_equivalents": true,
"bindings": {
"cmd-up": "editor::MoveToStartOfExcerpt",
"cmd-down": "editor::MoveToStartOfNextExcerpt",
"cmd-shift-up": "editor::SelectToStartOfExcerpt",
"cmd-shift-down": "editor::SelectToStartOfNextExcerpt"
}
},
{ {
"context": "Editor && mode == full && edit_prediction", "context": "Editor && mode == full && edit_prediction",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -229,7 +207,7 @@
"cmd-k c": "assistant::CopyCode", "cmd-k c": "assistant::CopyCode",
"cmd-shift-e": "project_panel::ToggleFocus", "cmd-shift-e": "project_panel::ToggleFocus",
"cmd-g": "search::SelectNextMatch", "cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPreviousMatch", "cmd-shift-g": "search::SelectPrevMatch",
"cmd-alt-/": "assistant::ToggleModelSelector", "cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-k h": "assistant::DeployHistory", "cmd-k h": "assistant::DeployHistory",
"cmd-k l": "assistant::DeployPromptLibrary", "cmd-k l": "assistant::DeployPromptLibrary",
@@ -266,14 +244,6 @@
"cmd-alt-e": "assistant2::RemoveAllContext" "cmd-alt-e": "assistant2::RemoveAllContext"
} }
}, },
{
"context": "AssistantPanel2 && prompt_editor",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "assistant2::NewPromptEditor",
"cmd-alt-t": "assistant2::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -281,15 +251,6 @@
"enter": "assistant2::Chat" "enter": "assistant2::Chat"
} }
}, },
{
"context": "EditMessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel",
"enter": "menu::Confirm",
"alt-enter": "editor::Newline"
}
},
{ {
"context": "ContextStrip", "context": "ContextStrip",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -308,6 +269,15 @@
"backspace": "assistant2::RemoveSelectedThread" "backspace": "assistant2::RemoveSelectedThread"
} }
}, },
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "prompt_library::NewPrompt",
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
"cmd-w": "workspace::CloseWindow"
}
},
{ {
"context": "BufferSearchBar", "context": "BufferSearchBar",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -315,7 +285,7 @@
"escape": "buffer_search::Dismiss", "escape": "buffer_search::Dismiss",
"tab": "buffer_search::FocusEditor", "tab": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch", "enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPreviousMatch", "shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
"cmd-f": "search::FocusSearch", "cmd-f": "search::FocusSearch",
"cmd-alt-f": "search::ToggleReplace", "cmd-alt-f": "search::ToggleReplace",
@@ -382,8 +352,8 @@
"context": "Pane", "context": "Pane",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"alt-cmd-left": "pane::ActivatePreviousItem", "alt-cmd-left": "pane::ActivatePrevItem",
"cmd-{": "pane::ActivatePreviousItem", "cmd-{": "pane::ActivatePrevItem",
"alt-cmd-right": "pane::ActivateNextItem", "alt-cmd-right": "pane::ActivateNextItem",
"cmd-}": "pane::ActivateNextItem", "cmd-}": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft", "ctrl-shift-pageup": "pane::SwapItemLeft",
@@ -397,7 +367,7 @@
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }], "cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
"cmd-f": "project_search::ToggleFocus", "cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch", "cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPreviousMatch", "cmd-shift-g": "search::SelectPrevMatch",
"cmd-shift-h": "search::ToggleReplace", "cmd-shift-h": "search::ToggleReplace",
"cmd-alt-l": "search::ToggleSelection", "cmd-alt-l": "search::ToggleSelection",
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
@@ -437,7 +407,7 @@
"cmd-u": "editor::UndoSelection", "cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection", "cmd-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic", "f8": "editor::GoToDiagnostic",
"shift-f8": "editor::GoToPreviousDiagnostic", "shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit", "alt-f12": "editor::GoToDefinitionSplit",
@@ -643,8 +613,8 @@
"context": "Editor && (showing_code_actions || showing_completions)", "context": "Editor && (showing_code_actions || showing_completions)",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"up": "editor::ContextMenuPrevious", "up": "editor::ContextMenuPrev",
"ctrl-p": "editor::ContextMenuPrevious", "ctrl-p": "editor::ContextMenuPrev",
"down": "editor::ContextMenuNext", "down": "editor::ContextMenuNext",
"ctrl-n": "editor::ContextMenuNext", "ctrl-n": "editor::ContextMenuNext",
"pageup": "editor::ContextMenuFirst", "pageup": "editor::ContextMenuFirst",
@@ -670,7 +640,7 @@
"cmd-alt-enter": "editor::OpenExcerptsSplit", "cmd-alt-enter": "editor::OpenExcerptsSplit",
"cmd-shift-e": "pane::RevealInProjectPanel", "cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk", "cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPreviousHunk", "cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist", "ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints" "ctrl-:": "editor::ToggleInlayHints"
} }
@@ -713,7 +683,7 @@
"alt-cmd-r": "outline_panel::RevealInFileManager", "alt-cmd-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::Open", "space": "outline_panel::Open",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts", "alt-enter": "editor::OpenExcerpts",
"cmd-alt-enter": "editor::OpenExcerptsSplit" "cmd-alt-enter": "editor::OpenExcerptsSplit"
} }
@@ -743,7 +713,7 @@
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }], "cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-shift-f": "project_panel::NewSearchInDirectory", "cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"escape": "menu::Cancel" "escape": "menu::Cancel"
} }
}, },
@@ -758,30 +728,26 @@
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"cmd-up": "menu::SelectFirst", "cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast", "cmd-down": "menu::SelectLast",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"cmd-alt-y": "git::ToggleStaged",
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
"cmd-y": "git::StageFile", "cmd-shift-space": "git::StageAll",
"cmd-shift-y": "git::UnstageFile", "ctrl-shift-space": "git::UnstageAll",
"alt-down": "git_panel::FocusEditor", "alt-down": "git_panel::FocusEditor",
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus",
"cmd-enter": "git::Commit", "cmd-enter": "git::Commit"
"backspace": "git::RestoreFile"
} }
}, },
{ {
"context": "GitDiff > Editor", "context": "GitDiff > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-enter": "git::Commit", "cmd-enter": "git::Commit"
"cmd-ctrl-y": "git::StageAll",
"cmd-ctrl-shift-y": "git::UnstageAll"
} }
}, },
{ {
@@ -792,24 +758,7 @@
"cmd-enter": "git::Commit", "cmd-enter": "git::Commit",
"tab": "git_panel::FocusChanges", "tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges", "shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges", "alt-up": "git_panel::FocusChanges"
"shift-escape": "git::ExpandCommitEditor",
"alt-tab": "git::GenerateCommitMessage"
}
},
{
"context": "GitPanel",
"use_key_equivalents": true,
"bindings": {
"ctrl-g ctrl-g": "git::Fetch",
"ctrl-g up": "git::Push",
"ctrl-g down": "git::Pull",
"ctrl-g shift-up": "git::ForcePush",
"ctrl-g d": "git::Diff",
"ctrl-g backspace": "git::RestoreTrackedFiles",
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
"cmd-ctrl-y": "git::StageAll",
"cmd-ctrl-shift-y": "git::UnstageAll"
} }
}, },
{ {
@@ -817,9 +766,7 @@
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"enter": "editor::Newline", "enter": "editor::Newline",
"escape": "menu::Cancel", "cmd-enter": "git::Commit"
"cmd-enter": "git::Commit",
"alt-tab": "git::GenerateCommitMessage"
} }
}, },
{ {
@@ -848,9 +795,6 @@
"context": "Picker > Editor", "context": "Picker > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"escape": "menu::Cancel",
"up": "menu::SelectPrevious",
"down": "menu::SelectNext",
"tab": "picker::ConfirmCompletion", "tab": "picker::ConfirmCompletion",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }], "alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }] "cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }]
@@ -867,7 +811,7 @@
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-shift-p": "file_finder::SelectPrevious", "cmd-shift-p": "file_finder::SelectPrev",
"cmd-j": "pane::SplitDown", "cmd-j": "pane::SplitDown",
"cmd-k": "pane::SplitUp", "cmd-k": "pane::SplitUp",
"cmd-h": "pane::SplitLeft", "cmd-h": "pane::SplitLeft",
@@ -878,8 +822,8 @@
"context": "TabSwitcher", "context": "TabSwitcher",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"ctrl-shift-tab": "menu::SelectPrevious", "ctrl-shift-tab": "menu::SelectPrev",
"ctrl-up": "menu::SelectPrevious", "ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext", "ctrl-down": "menu::SelectNext",
"ctrl-backspace": "tab_switcher::CloseSelectedItem" "ctrl-backspace": "tab_switcher::CloseSelectedItem"
} }

View File

@@ -39,7 +39,7 @@
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected "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::SelectPrevMatch" // find-and-replace:find-previous-selected
} }
}, },
{ {

View File

@@ -122,7 +122,7 @@
"context": "BufferSearchBar > Editor", "context": "BufferSearchBar > Editor",
"bindings": { "bindings": {
"ctrl-s": "search::SelectNextMatch", "ctrl-s": "search::SelectNextMatch",
"ctrl-r": "search::SelectPreviousMatch", "ctrl-r": "search::SelectPrevMatch",
"ctrl-g": "buffer_search::Dismiss" "ctrl-g": "buffer_search::Dismiss"
} }
}, },

View File

@@ -2,7 +2,7 @@
{ {
"bindings": { "bindings": {
"ctrl-alt-s": "zed::OpenSettings", "ctrl-alt-s": "zed::OpenSettings",
"ctrl-{": "pane::ActivatePreviousItem", "ctrl-{": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem" "ctrl-}": "pane::ActivateNextItem"
} }
}, },
@@ -41,9 +41,9 @@
"ctrl-shift-b": "editor::GoToTypeDefinition", "ctrl-shift-b": "editor::GoToTypeDefinition",
"ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit", "ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic", "f2": "editor::GoToDiagnostic",
"shift-f2": "editor::GoToPreviousDiagnostic", "shift-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk", "ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPreviousHunk", "ctrl-alt-shift-up": "editor::GoToPrevHunk",
"ctrl-alt-z": "git::Restore", "ctrl-alt-z": "git::Restore",
"ctrl-home": "editor::MoveToBeginning", "ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd", "ctrl-end": "editor::MoveToEnd",

View File

@@ -1,9 +1,9 @@
[ [
{ {
"bindings": { "bindings": {
"ctrl-{": "pane::ActivatePreviousItem", "ctrl-{": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem", "ctrl-}": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-1": ["workspace::ActivatePane", 0], "ctrl-1": ["workspace::ActivatePane", 0],
"ctrl-2": ["workspace::ActivatePane", 1], "ctrl-2": ["workspace::ActivatePane", 1],
@@ -44,7 +44,7 @@
"shift-f12": "editor::FindAllReferences", "shift-f12": "editor::FindAllReferences",
"ctrl-shift-f12": "editor::FindAllReferences", "ctrl-shift-f12": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk", "ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPreviousHunk", "ctrl-,": "editor::GoToPrevHunk",
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", "ctrl-k ctrl-u": "editor::ConvertToUpperCase",
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", "ctrl-k ctrl-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide", "shift-alt-m": "markdown::OpenPreviewToTheSide",
@@ -62,7 +62,7 @@
"context": "Pane", "context": "Pane",
"bindings": { "bindings": {
"f4": "search::SelectNextMatch", "f4": "search::SelectNextMatch",
"shift-f4": "search::SelectPreviousMatch", "shift-f4": "search::SelectPrevMatch",
"alt-1": ["pane::ActivateItem", 0], "alt-1": ["pane::ActivateItem", 0],
"alt-2": ["pane::ActivateItem", 1], "alt-2": ["pane::ActivateItem", 1],
"alt-3": ["pane::ActivateItem", 2], "alt-3": ["pane::ActivateItem", 2],

View File

@@ -40,7 +40,7 @@
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"cmd-f3": "search::SelectNextMatch", "cmd-f3": "search::SelectNextMatch",
"cmd-shift-f3": "search::SelectPreviousMatch" "cmd-shift-f3": "search::SelectPrevMatch"
} }
}, },
{ {

View File

@@ -122,7 +122,7 @@
"context": "BufferSearchBar > Editor", "context": "BufferSearchBar > Editor",
"bindings": { "bindings": {
"ctrl-s": "search::SelectNextMatch", "ctrl-s": "search::SelectNextMatch",
"ctrl-r": "search::SelectPreviousMatch", "ctrl-r": "search::SelectPrevMatch",
"ctrl-g": "buffer_search::Dismiss" "ctrl-g": "buffer_search::Dismiss"
} }
}, },

View File

@@ -1,7 +1,7 @@
[ [
{ {
"bindings": { "bindings": {
"cmd-{": "pane::ActivatePreviousItem", "cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem" "cmd-}": "pane::ActivateNextItem"
} }
}, },
@@ -39,9 +39,9 @@
"cmd-shift-b": "editor::GoToTypeDefinition", "cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit", "cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic", "f2": "editor::GoToDiagnostic",
"shift-f2": "editor::GoToPreviousDiagnostic", "shift-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk", "ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPreviousHunk", "ctrl-alt-shift-up": "editor::GoToPrevHunk",
"cmd-home": "editor::MoveToBeginning", "cmd-home": "editor::MoveToBeginning",
"cmd-end": "editor::MoveToEnd", "cmd-end": "editor::MoveToEnd",
"cmd-shift-home": "editor::SelectToBeginning", "cmd-shift-home": "editor::SelectToBeginning",
@@ -61,7 +61,7 @@
{ {
"context": "BufferSearchBar > Editor", "context": "BufferSearchBar > Editor",
"bindings": { "bindings": {
"shift-enter": "search::SelectPreviousMatch" "shift-enter": "search::SelectPrevMatch"
} }
}, },
{ {

View File

@@ -1,9 +1,9 @@
[ [
{ {
"bindings": { "bindings": {
"cmd-{": "pane::ActivatePreviousItem", "cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem", "cmd-}": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-1": ["workspace::ActivatePane", 0], "ctrl-1": ["workspace::ActivatePane", 0],
"ctrl-2": ["workspace::ActivatePane", 1], "ctrl-2": ["workspace::ActivatePane", 1],
@@ -45,7 +45,7 @@
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit", "ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
"alt-shift-cmd-down": "editor::FindAllReferences", "alt-shift-cmd-down": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk", "ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPreviousHunk", "ctrl-,": "editor::GoToPrevHunk",
"cmd-k cmd-u": "editor::ConvertToUpperCase", "cmd-k cmd-u": "editor::ConvertToUpperCase",
"cmd-k cmd-l": "editor::ConvertToLowerCase", "cmd-k cmd-l": "editor::ConvertToLowerCase",
"cmd-shift-j": "editor::JoinLines", "cmd-shift-j": "editor::JoinLines",
@@ -64,7 +64,7 @@
"context": "Pane", "context": "Pane",
"bindings": { "bindings": {
"f4": "search::SelectNextMatch", "f4": "search::SelectNextMatch",
"shift-f4": "search::SelectPreviousMatch", "shift-f4": "search::SelectPrevMatch",
"cmd-1": ["pane::ActivateItem", 0], "cmd-1": ["pane::ActivateItem", 0],
"cmd-2": ["pane::ActivateItem", 1], "cmd-2": ["pane::ActivateItem", 1],
"cmd-3": ["pane::ActivateItem", 2], "cmd-3": ["pane::ActivateItem", 2],

View File

@@ -47,7 +47,7 @@
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"ctrl-s": "search::SelectNextMatch", "ctrl-s": "search::SelectNextMatch",
"ctrl-shift-s": "search::SelectPreviousMatch" "ctrl-shift-s": "search::SelectPrevMatch"
} }
}, },
{ {

View File

@@ -13,9 +13,9 @@
"tab": "menu::SelectNext", "tab": "menu::SelectNext",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"shift-tab": "menu::SelectPrevious", "shift-tab": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrevious", "ctrl-p": "menu::SelectPrev",
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm", "cmd-enter": "menu::SecondaryConfirm",

View File

@@ -6,7 +6,7 @@
"a": ["vim::PushObject", { "around": true }], "a": ["vim::PushObject", { "around": true }],
"left": "vim::Left", "left": "vim::Left",
"h": "vim::Left", "h": "vim::Left",
"backspace": "vim::WrappingLeft", "backspace": "vim::Backspace",
"down": "vim::Down", "down": "vim::Down",
"ctrl-j": "vim::Down", "ctrl-j": "vim::Down",
"j": "vim::Down", "j": "vim::Down",
@@ -20,7 +20,7 @@
"k": "vim::Up", "k": "vim::Up",
"right": "vim::Right", "right": "vim::Right",
"l": "vim::Right", "l": "vim::Right",
"space": "vim::WrappingRight", "space": "vim::Space",
"end": "vim::EndOfLine", "end": "vim::EndOfLine",
"$": "vim::EndOfLine", "$": "vim::EndOfLine",
"^": "vim::FirstNonWhitespace", "^": "vim::FirstNonWhitespace",
@@ -62,9 +62,9 @@
"g /": "pane::DeploySearch", "g /": "pane::DeploySearch",
"?": ["vim::Search", { "backwards": true }], "?": ["vim::Search", { "backwards": true }],
"*": "vim::MoveToNext", "*": "vim::MoveToNext",
"#": "vim::MoveToPrevious", "#": "vim::MoveToPrev",
"n": "vim::MoveToNextMatch", "n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPreviousMatch", "shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching", "%": "vim::Matching",
"] }": ["vim::UnmatchedForward", { "char": "}" }], "] }": ["vim::UnmatchedForward", { "char": "}" }],
"[ {": ["vim::UnmatchedBackward", { "char": "{" }], "[ {": ["vim::UnmatchedBackward", { "char": "{" }],
@@ -106,7 +106,7 @@
"g g": "vim::StartOfDocument", "g g": "vim::StartOfDocument",
"g h": "editor::Hover", "g h": "editor::Hover",
"g t": "pane::ActivateNextItem", "g t": "pane::ActivateNextItem",
"g shift-t": "pane::ActivatePreviousItem", "g shift-t": "pane::ActivatePrevItem",
"g d": "editor::GoToDefinition", "g d": "editor::GoToDefinition",
"g shift-d": "editor::GoToDeclaration", "g shift-d": "editor::GoToDeclaration",
"g y": "editor::GoToTypeDefinition", "g y": "editor::GoToTypeDefinition",
@@ -126,7 +126,7 @@
"g shift-a": "editor::FindAllReferences", // zed specific "g shift-a": "editor::FindAllReferences", // zed specific
"g space": "editor::OpenExcerpts", // zed specific "g space": "editor::OpenExcerpts", // zed specific
"g *": ["vim::MoveToNext", { "partial_word": true }], "g *": ["vim::MoveToNext", { "partial_word": true }],
"g #": ["vim::MoveToPrevious", { "partial_word": true }], "g #": ["vim::MoveToPrev", { "partial_word": true }],
"g j": ["vim::Down", { "display_lines": true }], "g j": ["vim::Down", { "display_lines": true }],
"g down": ["vim::Down", { "display_lines": true }], "g down": ["vim::Down", { "display_lines": true }],
"g k": ["vim::Up", { "display_lines": true }], "g k": ["vim::Up", { "display_lines": true }],
@@ -138,7 +138,7 @@
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }], "g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
"g v": "vim::RestoreVisualSelection", "g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic", "g ]": "editor::GoToDiagnostic",
"g [": "editor::GoToPreviousDiagnostic", "g [": "editor::GoToPrevDiagnostic",
"g i": "vim::InsertAtPrevious", "g i": "vim::InsertAtPrevious",
"g ,": "vim::ChangeListNewer", "g ,": "vim::ChangeListNewer",
"g ;": "vim::ChangeListOlder", "g ;": "vim::ChangeListOlder",
@@ -231,15 +231,15 @@
"g w": "vim::PushRewrap", "g w": "vim::PushRewrap",
"g q": "vim::PushRewrap", "g q": "vim::PushRewrap",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore", "insert": "vim::InsertBefore",
// tree-sitter related commands // tree-sitter related commands
"[ x": "vim::SelectLargerSyntaxNode", "[ x": "vim::SelectLargerSyntaxNode",
"] x": "vim::SelectSmallerSyntaxNode", "] x": "vim::SelectSmallerSyntaxNode",
"] d": "editor::GoToDiagnostic", "] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPreviousDiagnostic", "[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk", "] c": "editor::GoToHunk",
"[ c": "editor::GoToPreviousHunk", "[ c": "editor::GoToPrevHunk",
"g c": "vim::PushToggleComments" "g c": "vim::PushToggleComments"
} }
}, },
@@ -247,8 +247,7 @@
"context": "VimControl && VimCount", "context": "VimControl && VimCount",
"bindings": { "bindings": {
"0": ["vim::Number", 0], "0": ["vim::Number", 0],
":": "vim::CountCommand", ":": "vim::CountCommand"
"%": "vim::GoToPercentage"
} }
}, },
{ {
@@ -273,7 +272,7 @@
"shift-s": "vim::SubstituteLine", "shift-s": "vim::SubstituteLine",
"~": "vim::ChangeCase", "~": "vim::ChangeCase",
"*": ["vim::MoveToNext", { "partial_word": true }], "*": ["vim::MoveToNext", { "partial_word": true }],
"#": ["vim::MoveToPrevious", { "partial_word": true }], "#": ["vim::MoveToPrev", { "partial_word": true }],
"ctrl-a": "vim::Increment", "ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement", "ctrl-x": "vim::Decrement",
"g ctrl-a": ["vim::Increment", { "step": true }], "g ctrl-a": ["vim::Increment", { "step": true }],
@@ -449,10 +448,7 @@
"d": "vim::CurrentLine", "d": "vim::CurrentLine",
"s": "vim::PushDeleteSurrounds", "s": "vim::PushDeleteSurrounds",
"o": "editor::ToggleSelectedDiffHunks", // "d o" "o": "editor::ToggleSelectedDiffHunks", // "d o"
"shift-o": "git::ToggleStaged", "p": "git::Restore" // "d p"
"p": "git::Restore", // "d p"
"u": "git::StageAndNext", // "d u"
"shift-u": "git::UnstageAndNext" // "d shift-u"
} }
}, },
{ {
@@ -624,8 +620,8 @@
"ctrl-w =": "vim::ResetPaneSizes", "ctrl-w =": "vim::ResetPaneSizes",
"ctrl-w g t": "pane::ActivateNextItem", "ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem", "ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePreviousItem", "ctrl-w g shift-t": "pane::ActivatePrevItem",
"ctrl-w ctrl-g shift-t": "pane::ActivatePreviousItem", "ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
"ctrl-w w": "workspace::ActivateNextPane", "ctrl-w w": "workspace::ActivateNextPane",
"ctrl-w ctrl-w": "workspace::ActivateNextPane", "ctrl-w ctrl-w": "workspace::ActivateNextPane",
"ctrl-w p": "workspace::ActivatePreviousPane", "ctrl-w p": "workspace::ActivatePreviousPane",
@@ -668,7 +664,7 @@
"escape": "project_panel::ToggleFocus", "escape": "project_panel::ToggleFocus",
"h": "project_panel::CollapseSelectedEntry", "h": "project_panel::CollapseSelectedEntry",
"j": "menu::SelectNext", "j": "menu::SelectNext",
"k": "menu::SelectPrevious", "k": "menu::SelectPrev",
"l": "project_panel::ExpandSelectedEntry", "l": "project_panel::ExpandSelectedEntry",
"o": "project_panel::OpenPermanent", "o": "project_panel::OpenPermanent",
"shift-d": "project_panel::Delete", "shift-d": "project_panel::Delete",
@@ -694,7 +690,7 @@
"context": "OutlinePanel && not_editing", "context": "OutlinePanel && not_editing",
"bindings": { "bindings": {
"j": "menu::SelectNext", "j": "menu::SelectNext",
"k": "menu::SelectPrevious", "k": "menu::SelectPrev",
"shift-g": "menu::SelectLast", "shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst" "g g": "menu::SelectFirst"
} }
@@ -703,7 +699,7 @@
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"k": "menu::SelectPrevious", "k": "menu::SelectPrev",
"j": "menu::SelectNext", "j": "menu::SelectNext",
"g g": "menu::SelectFirst", "g g": "menu::SelectFirst",
"shift-g": "menu::SelectLast", "shift-g": "menu::SelectLast",

View File

@@ -648,19 +648,11 @@
// Show git status colors in the editor tabs. // Show git status colors in the editor tabs.
"git_status": false, "git_status": false,
// Position of the close button on the editor tabs. // Position of the close button on the editor tabs.
// One of: ["right", "left", "hidden"]
"close_position": "right", "close_position": "right",
// Whether to show the file icon for a tab. // Whether to show the file icon for a tab.
"file_icons": false, "file_icons": false,
// Controls the appearance behavior of the tab's close button. // Whether to always show the close button on tabs.
// "always_show_close_button": false,
// 1. Show it just upon hovering the tab. (default)
// "hover"
// 2. Show it persistently.
// "always"
// 3. Never show it, even if hovering it.
// "hidden"
"show_close_button": "hover",
// What to do after closing the current tab. // What to do after closing the current tab.
// //
// 1. Activate the tab that was open previously (default) // 1. Activate the tab that was open previously (default)
@@ -720,8 +712,8 @@
"remove_trailing_whitespace_on_save": true, "remove_trailing_whitespace_on_save": true,
// Whether to start a new line with a comment when a previous line is a comment as well. // Whether to start a new line with a comment when a previous line is a comment as well.
"extend_comment_on_newline": true, "extend_comment_on_newline": true,
// Removes any lines containing only whitespace at the end of the file and // Whether or not to ensure there's a single newline at the end of a buffer
// ensures just one newline at the end. // when saving it.
"ensure_final_newline_on_save": true, "ensure_final_newline_on_save": true,
// Whether or not to perform a buffer format before saving // Whether or not to perform a buffer format before saving
// //
@@ -837,15 +829,7 @@
// //
// The minimum column number to show the inline blame information at // The minimum column number to show the inline blame information at
// "min_column": 0 // "min_column": 0
}, }
// How git hunks are displayed visually in the editor.
// This setting can take two values:
//
// 1. Show unstaged hunks with a transparent background (default):
// "hunk_style": "transparent"
// 2. Show unstaged hunks with a pattern background:
// "hunk_style": "pattern"
"hunk_style": "staged_border"
}, },
// Configuration for how direnv configuration should be loaded. May take 2 values: // Configuration for how direnv configuration should be loaded. May take 2 values:
// 1. Load direnv configuration using `direnv export json` directly. // 1. Load direnv configuration using `direnv export json` directly.
@@ -859,7 +843,15 @@
// Any addition to this list will be merged with the default list. // Any addition to this list will be merged with the default list.
// Globs are matched relative to the worktree root, // Globs are matched relative to the worktree root,
// except when starting with a slash (/) or equivalent in Windows. // except when starting with a slash (/) or equivalent in Windows.
"disabled_globs": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/.dev.vars", "**/secrets.yml"], "disabled_globs": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/.dev.vars",
"**/secrets.yml"
],
// When to show edit predictions previews in buffer. // When to show edit predictions previews in buffer.
// This setting takes two possible values: // This setting takes two possible values:
// 1. Display predictions inline when there are no language server completions available. // 1. Display predictions inline when there are no language server completions available.
@@ -1055,6 +1047,7 @@
// } // }
// //
"file_types": { "file_types": {
"Plain Text": ["txt"],
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"], "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"Shell Script": [".env.*"] "Shell Script": [".env.*"]
}, },
@@ -1174,7 +1167,6 @@
"format_on_save": "off", "format_on_save": "off",
"use_on_type_format": false, "use_on_type_format": false,
"allow_rewrap": "anywhere", "allow_rewrap": "anywhere",
"soft_wrap": "bounded",
"prettier": { "prettier": {
"allowed": true "allowed": true
} }
@@ -1298,11 +1290,6 @@
// "semi": false, // "semi": false,
// "singleQuote": true // "singleQuote": true
}, },
// Settings for auto-closing of JSX tags.
"jsx_tag_auto_close": {
// // Whether to auto-close JSX tags.
// "enabled": true
},
// LSP Specific settings. // LSP Specific settings.
"lsp": { "lsp": {
// Specify the LSP name as a key here. // Specify the LSP name as a key here.

View File

@@ -383,11 +383,6 @@
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
"variable.special": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"variant": { "variant": {
"color": "#83a598ff", "color": "#83a598ff",
"font_style": null, "font_style": null,
@@ -776,11 +771,6 @@
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
"variable.special": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"variant": { "variant": {
"color": "#83a598ff", "color": "#83a598ff",
"font_style": null, "font_style": null,
@@ -1169,11 +1159,6 @@
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
"variable.special": {
"color": "#83a598ff",
"font_style": null,
"font_weight": null
},
"variant": { "variant": {
"color": "#83a598ff", "color": "#83a598ff",
"font_style": null, "font_style": null,
@@ -1562,11 +1547,6 @@
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
"variable.special": {
"color": "#066578ff",
"font_style": null,
"font_weight": null
},
"variant": { "variant": {
"color": "#0b6678ff", "color": "#0b6678ff",
"font_style": null, "font_style": null,
@@ -1955,11 +1935,6 @@
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
"variable.special": {
"color": "#066578ff",
"font_style": null,
"font_weight": null
},
"variant": { "variant": {
"color": "#0b6678ff", "color": "#0b6678ff",
"font_style": null, "font_style": null,
@@ -2348,11 +2323,6 @@
"font_style": null, "font_style": null,
"font_weight": null "font_weight": null
}, },
"variable.special": {
"color": "#066578ff",
"font_style": null,
"font_weight": null
},
"variant": { "variant": {
"color": "#0b6678ff", "color": "#0b6678ff",
"font_style": null, "font_style": null,

View File

@@ -9,10 +9,7 @@ use gpui::{
}; };
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId}; use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
use lsp::LanguageServerName; use lsp::LanguageServerName;
use project::{ use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
ProjectEnvironmentEvent, WorktreeId,
};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration}; use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
@@ -76,22 +73,7 @@ impl ActivityIndicator {
}) })
.detach(); .detach();
cx.subscribe( cx.observe(&project, |_, _, cx| cx.notify()).detach();
&project.read(cx).lsp_store(),
|_, _, event, cx| match event {
LspStoreEvent::LanguageServerUpdate { .. } => cx.notify(),
_ => {}
},
)
.detach();
cx.subscribe(
&project.read(cx).environment().clone(),
|_, _, event, cx| match event {
ProjectEnvironmentEvent::ErrorsUpdated => cx.notify(),
},
)
.detach();
if let Some(auto_updater) = auto_updater.as_ref() { if let Some(auto_updater) = auto_updater.as_ref() {
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
@@ -222,7 +204,7 @@ impl ActivityIndicator {
message: error.0.clone(), message: error.0.clone(),
on_click: Some(Arc::new(move |this, window, cx| { on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| { this.project.update(cx, |project, cx| {
project.remove_environment_error(worktree_id, cx); project.remove_environment_error(cx, worktree_id);
}); });
window.dispatch_action(Box::new(workspace::OpenLog), cx); window.dispatch_action(Box::new(workspace::OpenLog), cx);
})), })),

View File

@@ -1,21 +0,0 @@
[package]
name = "askpass"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/askpass.rs"
[dependencies]
anyhow.workspace = true
futures.workspace = true
gpui.workspace = true
smol.workspace = true
tempfile.workspace = true
util.workspace = true
which.workspace = true

View File

@@ -1,194 +0,0 @@
use std::path::{Path, PathBuf};
use std::time::Duration;
#[cfg(unix)]
use anyhow::Context as _;
use futures::channel::{mpsc, oneshot};
#[cfg(unix)]
use futures::{io::BufReader, AsyncBufReadExt as _};
#[cfg(unix)]
use futures::{select_biased, AsyncWriteExt as _, FutureExt as _};
use futures::{SinkExt, StreamExt};
use gpui::{AsyncApp, BackgroundExecutor, Task};
#[cfg(unix)]
use smol::fs;
#[cfg(unix)]
use smol::{fs::unix::PermissionsExt as _, net::unix::UnixListener};
#[cfg(unix)]
use util::ResultExt as _;
#[derive(PartialEq, Eq)]
pub enum AskPassResult {
CancelledByUser,
Timedout,
}
pub struct AskPassDelegate {
tx: mpsc::UnboundedSender<(String, oneshot::Sender<String>)>,
_task: Task<()>,
}
impl AskPassDelegate {
pub fn new(
cx: &mut AsyncApp,
password_prompt: impl Fn(String, oneshot::Sender<String>, &mut AsyncApp) + Send + Sync + 'static,
) -> Self {
let (tx, mut rx) = mpsc::unbounded::<(String, oneshot::Sender<String>)>();
let task = cx.spawn(|mut cx| async move {
while let Some((prompt, channel)) = rx.next().await {
password_prompt(prompt, channel, &mut cx);
}
});
Self { tx, _task: task }
}
pub async fn ask_password(&mut self, prompt: String) -> anyhow::Result<String> {
let (tx, rx) = oneshot::channel();
self.tx.send((prompt, tx)).await?;
Ok(rx.await?)
}
}
#[cfg(unix)]
pub struct AskPassSession {
script_path: PathBuf,
_askpass_task: Task<()>,
askpass_opened_rx: Option<oneshot::Receiver<()>>,
askpass_kill_master_rx: Option<oneshot::Receiver<()>>,
}
#[cfg(unix)]
impl AskPassSession {
/// This will create a new AskPassSession.
/// You must retain this session until the master process exits.
#[must_use]
pub async fn new(
executor: &BackgroundExecutor,
mut delegate: AskPassDelegate,
) -> anyhow::Result<Self> {
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
let askpass_socket = temp_dir.path().join("askpass.sock");
let askpass_script_path = temp_dir.path().join("askpass.sh");
let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>();
let listener =
UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?;
let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>();
let mut kill_tx = Some(askpass_kill_master_tx);
let askpass_task = executor.spawn(async move {
let mut askpass_opened_tx = Some(askpass_opened_tx);
while let Ok((mut stream, _)) = listener.accept().await {
if let Some(askpass_opened_tx) = askpass_opened_tx.take() {
askpass_opened_tx.send(()).ok();
}
let mut buffer = Vec::new();
let mut reader = BufReader::new(&mut stream);
if reader.read_until(b'\0', &mut buffer).await.is_err() {
buffer.clear();
}
let prompt = String::from_utf8_lossy(&buffer);
if let Some(password) = delegate
.ask_password(prompt.to_string())
.await
.context("failed to get askpass password")
.log_err()
{
stream.write_all(password.as_bytes()).await.log_err();
} else {
if let Some(kill_tx) = kill_tx.take() {
kill_tx.send(()).log_err();
}
// note: we expect the caller to drop this task when it's done.
// We need to keep the stream open until the caller is done to avoid
// spurious errors from ssh.
std::future::pending::<()>().await;
drop(stream);
}
}
drop(temp_dir)
});
anyhow::ensure!(
which::which("nc").is_ok(),
"Cannot find `nc` command (netcat), which is required to connect over SSH."
);
// Create an askpass script that communicates back to this process.
let askpass_script = format!(
"{shebang}\n{print_args} | {nc} -U {askpass_socket} 2> /dev/null \n",
// on macOS `brew install netcat` provides the GNU netcat implementation
// which does not support -U.
nc = if cfg!(target_os = "macos") {
"/usr/bin/nc"
} else {
"nc"
},
askpass_socket = askpass_socket.display(),
print_args = "printf '%s\\0' \"$@\"",
shebang = "#!/bin/sh",
);
fs::write(&askpass_script_path, askpass_script).await?;
fs::set_permissions(&askpass_script_path, std::fs::Permissions::from_mode(0o755)).await?;
Ok(Self {
script_path: askpass_script_path,
_askpass_task: askpass_task,
askpass_kill_master_rx: Some(askpass_kill_master_rx),
askpass_opened_rx: Some(askpass_opened_rx),
})
}
pub fn script_path(&self) -> &Path {
&self.script_path
}
// This will run the askpass task forever, resolving as many authentication requests as needed.
// The caller is responsible for examining the result of their own commands and cancelling this
// future when this is no longer needed. Note that this can only be called once, but due to the
// drop order this takes an &mut, so you can `drop()` it after you're done with the master process.
pub async fn run(&mut self) -> AskPassResult {
let connection_timeout = Duration::from_secs(10);
let askpass_opened_rx = self.askpass_opened_rx.take().expect("Only call run once");
let askpass_kill_master_rx = self
.askpass_kill_master_rx
.take()
.expect("Only call run once");
select_biased! {
_ = askpass_opened_rx.fuse() => {
// Note: this await can only resolve after we are dropped.
askpass_kill_master_rx.await.ok();
return AskPassResult::CancelledByUser
}
_ = futures::FutureExt::fuse(smol::Timer::after(connection_timeout)) => {
return AskPassResult::Timedout
}
}
}
}
#[cfg(not(unix))]
pub struct AskPassSession {
path: PathBuf,
}
#[cfg(not(unix))]
impl AskPassSession {
pub async fn new(_: &BackgroundExecutor, _: AskPassDelegate) -> anyhow::Result<Self> {
Ok(Self {
path: PathBuf::new(),
})
}
pub fn script_path(&self) -> &Path {
&self.path
}
pub async fn run(&mut self) -> AskPassResult {
futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(10))).await;
AskPassResult::Timedout
}
}

View File

@@ -51,7 +51,6 @@ parking_lot.workspace = true
paths.workspace = true paths.workspace = true
project.workspace = true project.workspace = true
prompt_library.workspace = true prompt_library.workspace = true
prompt_store.workspace = true
proto.workspace = true proto.workspace = true
rope.workspace = true rope.workspace = true
schemars.workspace = true schemars.workspace = true

View File

@@ -19,7 +19,7 @@ use gpui::{actions, App, Global, UpdateGlobal};
use language_model::{ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
}; };
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use semantic_index::{CloudEmbeddingProvider, SemanticDb}; use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::Deserialize; use serde::Deserialize;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};

View File

@@ -110,7 +110,7 @@ impl ConfigurationView {
.bg(cx.theme().colors().surface_background) .bg(cx.theme().colors().surface_background)
.border_1() .border_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.rounded_sm() .rounded_md()
.when(configuration_view.is_none(), |this| { .when(configuration_view.is_none(), |this| {
this.child(div().child(Label::new(format!( this.child(div().child(Label::new(format!(
"No configuration view for {}", "No configuration view for {}",

View File

@@ -24,8 +24,7 @@ use language_model::{
AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
}; };
use project::Project; use project::Project;
use prompt_library::{open_prompt_library, PromptLibrary}; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
use prompt_store::PromptBuilder;
use search::{buffer_search::DivRegistrar, BufferSearchBar}; use search::{buffer_search::DivRegistrar, BufferSearchBar};
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use smol::stream::StreamExt; use smol::stream::StreamExt;

View File

@@ -35,11 +35,11 @@ use language_model::{
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest, report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role, LanguageModelRequestMessage, LanguageModelTextStream, Role,
}; };
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, LspAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use rope::Rope; use rope::Rope;
use settings::{update_settings_file, Settings, SettingsStore}; use settings::{update_settings_file, Settings, SettingsStore};
use smol::future::FutureExt; use smol::future::FutureExt;
@@ -386,6 +386,7 @@ impl InlineAssistant {
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn suggest_assist( pub fn suggest_assist(
&mut self, &mut self,
editor: &Entity<Editor>, editor: &Entity<Editor>,
@@ -1588,29 +1589,10 @@ impl Render for PromptEditor {
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)) .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.justify_center() .justify_center()
.gap_2() .gap_2()
.child(LanguageModelSelectorPopoverMenu::new( .child(
self.language_model_selector.clone(), InlineLanguageModelSelector::new(self.language_model_selector.clone())
IconButton::new("context", IconName::SettingsAlt) .render(window, cx),
.shape(IconButtonShape::Square) )
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
gpui::Corner::TopRight,
))
.map(|el| { .map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else { let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
return el; return el;
@@ -1673,6 +1655,7 @@ impl Focusable for PromptEditor {
impl PromptEditor { impl PromptEditor {
const MAX_LINES: u8 = 8; const MAX_LINES: u8 = 8;
#[allow(clippy::too_many_arguments)]
fn new( fn new(
id: InlineAssistId, id: InlineAssistId,
gutter_dimensions: Arc<Mutex<GutterDimensions>>, gutter_dimensions: Arc<Mutex<GutterDimensions>>,
@@ -2331,6 +2314,7 @@ struct InlineAssist {
} }
impl InlineAssist { impl InlineAssist {
#[allow(clippy::too_many_arguments)]
fn new( fn new(
assist_id: InlineAssistId, assist_id: InlineAssistId,
group_id: InlineAssistGroupId, group_id: InlineAssistGroupId,
@@ -3566,10 +3550,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
Task::ready(Ok(vec![CodeAction { Task::ready(Ok(vec![CodeAction {
server_id: language::LanguageServerId(0), server_id: language::LanguageServerId(0),
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end), range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
lsp_action: LspAction::Action(Box::new(lsp::CodeAction { lsp_action: lsp::CodeAction {
title: "Fix with Assistant".into(), title: "Fix with Assistant".into(),
..Default::default() ..Default::default()
})), },
}])) }]))
} else { } else {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))

View File

@@ -19,8 +19,8 @@ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest, report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role, LanguageModelRequestMessage, Role,
}; };
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use std::{ use std::{
cmp, cmp,
@@ -506,7 +506,7 @@ struct PromptEditor {
impl EventEmitter<PromptEditorEvent> for PromptEditor {} impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor { impl Render for PromptEditor {
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 status = &self.codegen.read(cx).status; let status = &self.codegen.read(cx).status;
let buttons = match status { let buttons = match status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
@@ -641,29 +641,10 @@ impl Render for PromptEditor {
.w_12() .w_12()
.justify_center() .justify_center()
.gap_2() .gap_2()
.child(LanguageModelSelectorPopoverMenu::new( .child(
self.language_model_selector.clone(), InlineLanguageModelSelector::new(self.language_model_selector.clone())
IconButton::new("change-model", IconName::SettingsAlt) .render(window, cx),
.shape(IconButtonShape::Square) )
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
gpui::Corner::TopRight,
))
.children( .children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status { if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string()); let error_message = SharedString::from(error.to_string());
@@ -702,6 +683,7 @@ impl Focusable for PromptEditor {
impl PromptEditor { impl PromptEditor {
const MAX_LINES: u8 = 8; const MAX_LINES: u8 = 8;
#[allow(clippy::too_many_arguments)]
fn new( fn new(
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,

View File

@@ -56,10 +56,8 @@ paths.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
prompt_library.workspace = true prompt_library.workspace = true
prompt_store.workspace = true
proto.workspace = true proto.workspace = true
rope.workspace = true rope.workspace = true
scripting_tool.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true
@@ -82,8 +80,8 @@ zed_actions.workspace = true
[dev-dependencies] [dev-dependencies]
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, "features" = ["test-support"] } gpui = { workspace = true, "features" = ["test-support"] }
indoc.workspace = true
language = { workspace = true, "features" = ["test-support"] } language = { workspace = true, "features" = ["test-support"] }
language_model = { workspace = true, "features" = ["test-support"] } language_model = { workspace = true, "features" = ["test-support"] }
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }
rand.workspace = true rand.workspace = true
indoc.workspace = true

View File

@@ -1,20 +1,19 @@
use std::sync::Arc; use std::sync::Arc;
use assistant_tool::ToolWorkingSet;
use collections::HashMap; use collections::HashMap;
use editor::{Editor, MultiBuffer};
use gpui::{ use gpui::{
list, AbsoluteLength, AnyElement, App, ClickEvent, DefiniteLength, EdgesRefinement, Empty, list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, Length,
Entity, Focusable, FontWeight, Length, ListAlignment, ListOffset, ListState, StyleRefinement, ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, TextStyleRefinement,
Subscription, Task, TextStyleRefinement, UnderlineStyle, UnderlineStyle, WeakEntity,
}; };
use language::{Buffer, LanguageRegistry}; use language::LanguageRegistry;
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role}; use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
use markdown::{Markdown, MarkdownStyle}; use markdown::{Markdown, MarkdownStyle};
use scripting_tool::{ScriptingTool, ScriptingToolInput};
use settings::Settings as _; use settings::Settings as _;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, Disclosure, KeyBinding}; use ui::{prelude::*, Disclosure};
use util::ResultExt as _; use workspace::Workspace;
use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent}; use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
@@ -22,30 +21,26 @@ use crate::tool_use::{ToolUse, ToolUseStatus};
use crate::ui::ContextPill; use crate::ui::ContextPill;
pub struct ActiveThread { pub struct ActiveThread {
workspace: WeakEntity<Workspace>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
thread_store: Entity<ThreadStore>, thread_store: Entity<ThreadStore>,
thread: Entity<Thread>, thread: Entity<Thread>,
save_thread_task: Option<Task<()>>,
messages: Vec<MessageId>, messages: Vec<MessageId>,
list_state: ListState, list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>, rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
rendered_scripting_tool_uses:
HashMap<LanguageModelToolUseId, (Entity<Markdown>, Option<String>)>,
editing_message: Option<(MessageId, EditMessageState)>,
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
last_error: Option<ThreadError>, last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
struct EditMessageState {
editor: Entity<Editor>,
}
impl ActiveThread { impl ActiveThread {
pub fn new( pub fn new(
thread: Entity<Thread>, thread: Entity<Thread>,
thread_store: Entity<ThreadStore>, thread_store: Entity<ThreadStore>,
workspace: WeakEntity<Workspace>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@@ -55,38 +50,27 @@ impl ActiveThread {
]; ];
let mut this = Self { let mut this = Self {
workspace,
language_registry, language_registry,
tools,
thread_store, thread_store,
thread: thread.clone(), thread: thread.clone(),
save_thread_task: None,
messages: Vec::new(), messages: Vec::new(),
rendered_messages_by_id: HashMap::default(), rendered_messages_by_id: HashMap::default(),
rendered_scripting_tool_uses: HashMap::default(),
expanded_tool_uses: HashMap::default(), expanded_tool_uses: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), { list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade(); let this = cx.entity().downgrade();
move |ix, window: &mut Window, cx: &mut App| { move |ix, _: &mut Window, cx: &mut App| {
this.update(cx, |this, cx| this.render_message(ix, window, cx)) this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap() .unwrap()
} }
}), }),
editing_message: None,
last_error: None, last_error: None,
_subscriptions: subscriptions, _subscriptions: subscriptions,
}; };
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() { for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
this.push_message(&message.id, message.text.clone(), window, cx); this.push_message(&message.id, message.text.clone(), window, cx);
for tool_use in thread.read(cx).scripting_tool_uses_for_message(message.id) {
this.render_scripting_tool_use_markdown(
tool_use.id.clone(),
tool_use.name.as_ref(),
tool_use.input.clone(),
window,
cx,
);
}
} }
this this
@@ -133,44 +117,6 @@ impl ActiveThread {
self.messages.push(*id); self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1); self.list_state.splice(old_len..old_len, 1);
let markdown = self.render_markdown(text.into(), window, cx);
self.rendered_messages_by_id.insert(*id, markdown);
self.list_state.scroll_to(ListOffset {
item_ix: old_len,
offset_in_item: Pixels(0.0),
});
}
fn edited_message(
&mut self,
id: &MessageId,
text: String,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
return;
};
self.list_state.splice(index..index + 1, 1);
let markdown = self.render_markdown(text.into(), window, cx);
self.rendered_messages_by_id.insert(*id, markdown);
}
fn deleted_message(&mut self, id: &MessageId) {
let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
return;
};
self.messages.remove(index);
self.list_state.splice(index..index + 1, 0);
self.rendered_messages_by_id.remove(id);
}
fn render_markdown(
&self,
text: SharedString,
window: &Window,
cx: &mut Context<Self>,
) -> Entity<Markdown> {
let theme_settings = ThemeSettings::get_global(cx); let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors(); let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx); let ui_font_size = TextSize::Default.rems(cx);
@@ -179,8 +125,6 @@ impl ActiveThread {
text_style.refine(&TextStyleRefinement { text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()), font_family: Some(theme_settings.ui_font.family.clone()),
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
font_features: Some(theme_settings.ui_font.features.clone()),
font_size: Some(ui_font_size.into()), font_size: Some(ui_font_size.into()),
color: Some(cx.theme().colors().text), color: Some(cx.theme().colors().text),
..Default::default() ..Default::default()
@@ -190,8 +134,6 @@ impl ActiveThread {
base_text_style: text_style, base_text_style: text_style,
syntax: cx.theme().syntax().clone(), syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection, selection_background_color: cx.theme().players().local().selection,
code_block_overflow_x_scroll: true,
table_overflow_x_scroll: true,
code_block: StyleRefinement { code_block: StyleRefinement {
margin: EdgesRefinement { margin: EdgesRefinement {
top: Some(Length::Definite(rems(0.).into())), top: Some(Length::Definite(rems(0.).into())),
@@ -205,7 +147,7 @@ impl ActiveThread {
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
}, },
background: Some(colors.terminal_background.into()), background: Some(colors.editor_background.into()),
border_color: Some(colors.border_variant), border_color: Some(colors.border_variant),
border_widths: EdgesRefinement { border_widths: EdgesRefinement {
top: Some(AbsoluteLength::Pixels(Pixels(1.))), top: Some(AbsoluteLength::Pixels(Pixels(1.))),
@@ -215,8 +157,6 @@ impl ActiveThread {
}, },
text: Some(TextStyleRefinement { text: Some(TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()), 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()), font_size: Some(buffer_font_size.into()),
..Default::default() ..Default::default()
}), }),
@@ -224,8 +164,6 @@ impl ActiveThread {
}, },
inline_code: TextStyleRefinement { inline_code: TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()), 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()), font_size: Some(buffer_font_size.into()),
background_color: Some(colors.editor_foreground.opacity(0.1)), background_color: Some(colors.editor_foreground.opacity(0.1)),
..Default::default() ..Default::default()
@@ -242,50 +180,25 @@ impl ActiveThread {
..Default::default() ..Default::default()
}; };
cx.new(|cx| { let markdown = cx.new(|cx| {
Markdown::new( Markdown::new(
text, text.into(),
markdown_style, markdown_style,
Some(self.language_registry.clone()), Some(self.language_registry.clone()),
None, None,
cx, cx,
) )
}) });
} self.rendered_messages_by_id.insert(*id, markdown);
self.list_state.scroll_to(ListOffset {
/// Renders the input of a scripting tool use to Markdown. item_ix: old_len,
/// offset_in_item: Pixels(0.0),
/// Does nothing if the tool use does not correspond to the scripting tool. });
fn render_scripting_tool_use_markdown(
&mut self,
tool_use_id: LanguageModelToolUseId,
tool_name: &str,
tool_input: serde_json::Value,
window: &mut Window,
cx: &mut Context<Self>,
) {
if tool_name != ScriptingTool::NAME {
return;
}
let tool_input_result = serde_json::from_value::<ScriptingToolInput>(tool_input);
let (lua_script, summary) = if let Ok(input) = tool_input_result {
(input.lua_script, input.summary)
} else {
(String::new(), None)
};
let lua_script =
self.render_markdown(format!("```lua\n{lua_script}\n```").into(), window, cx);
self.rendered_scripting_tool_uses
.insert(tool_use_id, (lua_script, summary));
} }
fn handle_thread_event( fn handle_thread_event(
&mut self, &mut self,
_thread: &Entity<Thread>, _: &Entity<Thread>,
event: &ThreadEvent, event: &ThreadEvent,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
@@ -295,7 +208,11 @@ impl ActiveThread {
self.last_error = Some(error.clone()); self.last_error = Some(error.clone());
} }
ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => { ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => {
self.save_thread(cx); self.thread_store
.update(cx, |thread_store, cx| {
thread_store.save_thread(&self.thread, cx)
})
.detach_and_log_err(cx);
} }
ThreadEvent::StreamedAssistantText(message_id, text) => { ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) { if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
@@ -314,50 +231,48 @@ impl ActiveThread {
self.push_message(message_id, message_text, window, cx); self.push_message(message_id, message_text, window, cx);
} }
self.save_thread(cx); self.thread_store
cx.notify(); .update(cx, |thread_store, cx| {
} thread_store.save_thread(&self.thread, cx)
ThreadEvent::MessageEdited(message_id) => { })
if let Some(message_text) = self .detach_and_log_err(cx);
.thread
.read(cx)
.message(*message_id)
.map(|message| message.text.clone())
{
self.edited_message(message_id, message_text, window, cx);
}
self.save_thread(cx);
cx.notify();
}
ThreadEvent::MessageDeleted(message_id) => {
self.deleted_message(message_id);
self.save_thread(cx);
cx.notify(); cx.notify();
} }
ThreadEvent::UsePendingTools => { ThreadEvent::UsePendingTools => {
self.thread.update(cx, |thread, cx| { let pending_tool_uses = self
thread.use_pending_tools(cx); .thread
}); .read(cx)
} .pending_tool_uses()
ThreadEvent::ToolFinished { .into_iter()
pending_tool_use, .. .filter(|tool_use| tool_use.status.is_idle())
} => { .cloned()
if let Some(tool_use) = pending_tool_use { .collect::<Vec<_>>();
self.render_scripting_tool_use_markdown(
tool_use.id.clone(),
tool_use.name.as_ref(),
tool_use.input.clone(),
window,
cx,
);
}
if self.thread.read(cx).all_tools_finished() { for tool_use in pending_tool_uses {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(tool_use.id.clone(), task, cx);
});
}
}
}
ThreadEvent::ToolFinished { .. } => {
let all_tools_finished = self
.thread
.read(cx)
.pending_tool_uses()
.into_iter()
.all(|tool_use| tool_use.status.is_error());
if all_tools_finished {
let model_registry = LanguageModelRegistry::read_global(cx); let model_registry = LanguageModelRegistry::read_global(cx);
if let Some(model) = model_registry.active_model() { if let Some(model) = model_registry.active_model() {
self.thread.update(cx, |thread, cx| { self.thread.update(cx, |thread, cx| {
thread.send_tool_results_to_model(model, cx); // Insert an empty user message to contain the tool results.
thread.insert_user_message("", Vec::new(), cx);
thread.send_to_model(model, RequestKind::Chat, true, cx);
}); });
} }
} }
@@ -365,133 +280,7 @@ impl ActiveThread {
} }
} }
/// Spawns a task to save the active thread. fn render_message(&self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
///
/// Only one task to save the thread will be in flight at a time.
fn save_thread(&mut self, cx: &mut Context<Self>) {
let thread = self.thread.clone();
self.save_thread_task = Some(cx.spawn(|this, mut cx| async move {
let task = this
.update(&mut cx, |this, cx| {
this.thread_store
.update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx))
})
.ok();
if let Some(task) = task {
task.await.log_err();
}
}));
}
fn start_editing_message(
&mut self,
message_id: MessageId,
message_text: String,
window: &mut Window,
cx: &mut Context<Self>,
) {
let buffer = cx.new(|cx| {
MultiBuffer::singleton(cx.new(|cx| Buffer::local(message_text.clone(), cx)), cx)
});
let editor = cx.new(|cx| {
let mut editor = Editor::new(
editor::EditorMode::AutoHeight { max_lines: 8 },
buffer,
None,
false,
window,
cx,
);
editor.focus_handle(cx).focus(window);
editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
editor
});
self.editing_message = Some((
message_id,
EditMessageState {
editor: editor.clone(),
},
));
cx.notify();
}
fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
self.editing_message.take();
cx.notify();
}
fn confirm_editing_message(
&mut self,
_: &menu::Confirm,
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some((message_id, state)) = self.editing_message.take() else {
return;
};
let edited_text = state.editor.read(cx).text(cx);
self.thread.update(cx, |thread, cx| {
thread.edit_message(message_id, Role::User, edited_text, cx);
for message_id in self.messages_after(message_id) {
thread.delete_message(*message_id, cx);
}
});
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
.as_ref()
.map_or(false, |provider| provider.must_accept_terms(cx))
{
cx.notify();
return;
}
let model_registry = LanguageModelRegistry::read_global(cx);
let Some(model) = model_registry.active_model() else {
return;
};
self.thread.update(cx, |thread, cx| {
thread.send_to_model(model, RequestKind::Chat, false, cx)
});
cx.notify();
}
fn last_user_message(&self, cx: &Context<Self>) -> Option<MessageId> {
self.messages
.iter()
.rev()
.find(|message_id| {
self.thread
.read(cx)
.message(**message_id)
.map_or(false, |message| message.role == Role::User)
})
.cloned()
}
fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
self.messages
.iter()
.position(|id| *id == message_id)
.map(|index| &self.messages[index + 1..])
.unwrap_or(&[])
}
fn handle_cancel_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
self.cancel_editing_message(&menu::Cancel, window, cx);
}
fn handle_regenerate_click(
&mut self,
_: &ClickEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.confirm_editing_message(&menu::Confirm, window, cx);
}
fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let message_id = self.messages[ix]; let message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else { let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any(); return Empty.into_any();
@@ -501,44 +290,20 @@ impl ActiveThread {
return Empty.into_any(); return Empty.into_any();
}; };
let thread = self.thread.read(cx); let context = self.thread.read(cx).context_for_message(message_id);
let tool_uses = self.thread.read(cx).tool_uses_for_message(message_id);
let context = thread.context_for_message(message_id); let colors = cx.theme().colors();
let tool_uses = thread.tool_uses_for_message(message_id);
let scripting_tool_uses = thread.scripting_tool_uses_for_message(message_id);
// Don't render user messages that are just there for returning tool results. // Don't render user messages that are just there for returning tool results.
if message.role == Role::User if message.role == Role::User
&& (thread.message_has_tool_results(message_id) && message.text.is_empty()
|| thread.message_has_scripting_tool_results(message_id)) && self.thread.read(cx).message_has_tool_results(message_id)
{ {
return Empty.into_any(); return Empty.into_any();
} }
let allow_editing_message =
message.role == Role::User && self.last_user_message(cx) == Some(message_id);
let edit_message_editor = self
.editing_message
.as_ref()
.filter(|(id, _)| *id == message_id)
.map(|(_, state)| state.editor.clone());
let colors = cx.theme().colors();
let message_content = v_flex() let message_content = v_flex()
.child( .child(div().p_2p5().text_ui(cx).child(markdown.clone()))
if let Some(edit_message_editor) = edit_message_editor.clone() {
div()
.key_context("EditMessageEditor")
.on_action(cx.listener(Self::cancel_editing_message))
.on_action(cx.listener(Self::confirm_editing_message))
.p_2p5()
.child(edit_message_editor)
} else {
div().p_2p5().text_ui(cx).child(markdown.clone())
},
)
.when_some(context, |parent, context| { .when_some(context, |parent, context| {
if !context.is_empty() { if !context.is_empty() {
parent.child( parent.child(
@@ -568,8 +333,7 @@ impl ActiveThread {
.child( .child(
h_flex() h_flex()
.py_1() .py_1()
.pl_2() .px_2()
.pr_1()
.bg(colors.editor_foreground.opacity(0.05)) .bg(colors.editor_foreground.opacity(0.05))
.border_b_1() .border_b_1()
.border_color(colors.border) .border_color(colors.border)
@@ -588,71 +352,6 @@ impl ActiveThread {
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted), .color(Color::Muted),
), ),
)
.when_some(
edit_message_editor.clone(),
|this, edit_message_editor| {
let focus_handle = edit_message_editor.focus_handle(cx);
this.child(
h_flex()
.gap_1()
.child(
Button::new("cancel-edit-message", "Cancel")
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
cx.listener(Self::handle_cancel_click),
),
)
.child(
Button::new(
"confirm-edit-message",
"Regenerate",
)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
cx.listener(Self::handle_regenerate_click),
),
),
)
},
)
.when(
edit_message_editor.is_none() && allow_editing_message,
|this| {
this.child(
Button::new("edit-message", "Edit")
.label_size(LabelSize::Small)
.on_click(cx.listener({
let message_text = message.text.clone();
move |this, _, window, cx| {
this.start_editing_message(
message_id,
message_text.clone(),
window,
cx,
);
}
})),
)
},
), ),
) )
.child(message_content), .child(message_content),
@@ -661,28 +360,22 @@ impl ActiveThread {
.id(("message-container", ix)) .id(("message-container", ix))
.child(message_content) .child(message_content)
.map(|parent| { .map(|parent| {
if tool_uses.is_empty() && scripting_tool_uses.is_empty() { if tool_uses.is_empty() {
return parent; return parent;
} }
parent.child( parent.child(
v_flex() v_flex().children(
.children( tool_uses
tool_uses .into_iter()
.into_iter() .map(|tool_use| self.render_tool_use(tool_use, cx)),
.map(|tool_use| self.render_tool_use(tool_use, cx)), ),
)
.children(
scripting_tool_uses
.into_iter()
.map(|tool_use| self.render_scripting_tool_use(tool_use, cx)),
),
) )
}), }),
Role::System => div().id(("message-container", ix)).py_1().px_2().child( Role::System => div().id(("message-container", ix)).py_1().px_2().child(
v_flex() v_flex()
.bg(colors.editor_background) .bg(colors.editor_background)
.rounded_sm() .rounded_md()
.child(message_content), .child(message_content),
), ),
}; };
@@ -710,13 +403,8 @@ impl ActiveThread {
.pl_1() .pl_1()
.pr_2() .pr_2()
.bg(cx.theme().colors().editor_foreground.opacity(0.02)) .bg(cx.theme().colors().editor_foreground.opacity(0.02))
.map(|element| { .when(is_open, |element| element.border_b_1().rounded_t(px(6.)))
if is_open { .when(!is_open, |element| element.rounded(px(6.)))
element.border_b_1().rounded_t(px(6.))
} else {
element.rounded_md()
}
})
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child( .child(
h_flex() h_flex()
@@ -790,139 +478,6 @@ impl ActiveThread {
}), }),
) )
} }
fn render_scripting_tool_use(
&self,
tool_use: ToolUse,
cx: &mut Context<Self>,
) -> impl IntoElement {
let is_open = self
.expanded_tool_uses
.get(&tool_use.id)
.copied()
.unwrap_or_default();
div().px_2p5().child(
v_flex()
.gap_1()
.rounded_lg()
.border_1()
.border_color(cx.theme().colors().border)
.child(
h_flex()
.justify_between()
.py_0p5()
.pl_1()
.pr_2()
.bg(cx.theme().colors().editor_foreground.opacity(0.02))
.map(|element| {
if is_open {
element.border_b_1().rounded_t(px(6.))
} else {
element.rounded_md()
}
})
.border_color(cx.theme().colors().border)
.child(
h_flex()
.gap_1()
.child(Disclosure::new("tool-use-disclosure", is_open).on_click(
cx.listener({
let tool_use_id = tool_use.id.clone();
move |this, _event, _window, _cx| {
let is_open = this
.expanded_tool_uses
.entry(tool_use_id.clone())
.or_insert(false);
*is_open = !*is_open;
}
}),
))
.child(Label::new(tool_use.name)),
)
.child(
Label::new(match tool_use.status {
ToolUseStatus::Pending => "Pending",
ToolUseStatus::Running => "Running",
ToolUseStatus::Finished(_) => "Finished",
ToolUseStatus::Error(_) => "Error",
})
.size(LabelSize::XSmall)
.buffer_font(cx),
),
)
.map(|parent| {
if !is_open {
return parent;
}
let scripting_tool_result =
self.rendered_scripting_tool_uses.get(&tool_use.id).cloned();
let (lua_script_markdown, summary) = match scripting_tool_result {
Some((markdown, summary)) => (Some(markdown), summary),
None => (None, None),
};
parent.child(
v_flex()
.child(
v_flex()
.gap_0p5()
.py_1()
.px_2p5()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(v_flex().when_some(
summary.clone(),
|parent, summary| {
parent.child(
v_flex()
.gap_0p5()
.child(
Label::new("Summary:")
.weight(FontWeight::SEMIBOLD),
)
.child(Label::new(summary))
.pb_1(),
)
},
))
.child(Label::new("Input:").weight(FontWeight::SEMIBOLD))
.map(|parent| {
if let Some(markdown) = lua_script_markdown {
parent.child(markdown)
} else {
parent.child(Label::new(
"Failed to render script input to Markdown",
))
}
}),
)
.map(|parent| match tool_use.status {
ToolUseStatus::Finished(output) => parent.child(
v_flex()
.gap_0p5()
.py_1()
.px_2p5()
.child(Label::new("Result:"))
.child(Label::new(output)),
),
ToolUseStatus::Error(err) => parent.child(
v_flex()
.gap_0p5()
.py_1()
.px_2p5()
.child(Label::new("Error:"))
.child(Label::new(err)),
),
ToolUseStatus::Pending | ToolUseStatus::Running => parent,
}),
)
}),
)
}
} }
impl Render for ActiveThread { impl Render for ActiveThread {

View File

@@ -27,7 +27,7 @@ use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use fs::Fs; use fs::Fs;
use gpui::{actions, App}; use gpui::{actions, App};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use settings::Settings as _; use settings::Settings as _;
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate}; pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};

View File

@@ -134,7 +134,7 @@ impl AssistantConfiguration {
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.border_1() .border_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.rounded_sm() .rounded_md()
.map(|parent| match configuration_view { .map(|parent| match configuration_view {
Some(configuration_view) => parent.child(configuration_view), Some(configuration_view) => parent.child(configuration_view),
None => parent.child(div().child(Label::new(format!( None => parent.child(div().child(Label::new(format!(

View File

@@ -1,24 +1,19 @@
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use fs::Fs; use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString}; use gpui::{Entity, FocusHandle};
use language_model::LanguageModelRegistry; use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
use language_model_selector::{
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
};
use settings::update_settings_file; use settings::update_settings_file;
use std::sync::Arc; use std::sync::Arc;
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip}; use ui::prelude::*;
pub struct AssistantModelSelector { pub struct AssistantModelSelector {
selector: Entity<LanguageModelSelector>, pub selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
impl AssistantModelSelector { impl AssistantModelSelector {
pub(crate) fn new( pub(crate) fn new(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@@ -38,54 +33,14 @@ impl AssistantModelSelector {
cx, cx,
) )
}), }),
menu_handle,
focus_handle, focus_handle,
} }
} }
pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
self.menu_handle.toggle(window, cx);
}
} }
impl Render for AssistantModelSelector { impl Render for AssistantModelSelector {
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 active_model = LanguageModelRegistry::read_global(cx).active_model(); AssistantLanguageModelSelector::new(self.focus_handle.clone(), self.selector.clone())
let focus_handle = self.focus_handle.clone(); .render(window, cx)
let model_name = match active_model {
Some(model) => model.name().0,
_ => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
)
.with_handle(self.menu_handle.clone())
} }
} }

View File

@@ -15,14 +15,12 @@ use editor::Editor;
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter, prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
FocusHandle, Focusable, FontWeight, KeyContext, Pixels, Subscription, Task, UpdateGlobal, FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry}; use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
use project::Project; use project::Project;
use prompt_library::{open_prompt_library, PromptLibrary}; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
use prompt_store::PromptBuilder;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use time::UtcOffset; use time::UtcOffset;
use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip}; use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip};
@@ -92,6 +90,7 @@ pub struct AssistantPanel {
context_editor: Option<Entity<ContextEditor>>, context_editor: Option<Entity<ContextEditor>>,
configuration: Option<Entity<AssistantConfiguration>>, configuration: Option<Entity<AssistantConfiguration>>,
configuration_subscription: Option<Subscription>, configuration_subscription: Option<Subscription>,
tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset, local_timezone: UtcOffset,
active_view: ActiveView, active_view: ActiveView,
history_store: Entity<HistoryStore>, history_store: Entity<HistoryStore>,
@@ -132,7 +131,7 @@ impl AssistantPanel {
log::info!("[assistant2-debug] finished initializing ContextStore"); log::info!("[assistant2-debug] finished initializing ContextStore");
workspace.update_in(&mut cx, |workspace, window, cx| { workspace.update_in(&mut cx, |workspace, window, cx| {
cx.new(|cx| Self::new(workspace, thread_store, context_store, window, cx)) cx.new(|cx| Self::new(workspace, thread_store, context_store, tools, window, cx))
}) })
}) })
} }
@@ -141,6 +140,7 @@ impl AssistantPanel {
workspace: &Workspace, workspace: &Workspace,
thread_store: Entity<ThreadStore>, thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>, context_store: Entity<assistant_context_editor::ContextStore>,
tools: Arc<ToolWorkingSet>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@@ -166,29 +166,30 @@ impl AssistantPanel {
let history_store = let history_store =
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx)); cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
let thread = cx.new(|cx| {
ActiveThread::new(
thread.clone(),
thread_store.clone(),
language_registry.clone(),
window,
cx,
)
});
Self { Self {
active_view: ActiveView::Thread, active_view: ActiveView::Thread,
workspace, workspace: workspace.clone(),
project: project.clone(), project,
fs: fs.clone(), fs: fs.clone(),
language_registry, language_registry: language_registry.clone(),
thread_store: thread_store.clone(), thread_store: thread_store.clone(),
thread, thread: cx.new(|cx| {
ActiveThread::new(
thread.clone(),
thread_store.clone(),
workspace,
language_registry,
tools.clone(),
window,
cx,
)
}),
message_editor, message_editor,
context_store, context_store,
context_editor: None, context_editor: None,
configuration: None, configuration: None,
configuration_subscription: None, configuration_subscription: None,
tools,
local_timezone: UtcOffset::from_whole_seconds( local_timezone: UtcOffset::from_whole_seconds(
chrono::Local::now().offset().local_minus_utc(), chrono::Local::now().offset().local_minus_utc(),
) )
@@ -243,7 +244,9 @@ impl AssistantPanel {
ActiveThread::new( ActiveThread::new(
thread.clone(), thread.clone(),
self.thread_store.clone(), self.thread_store.clone(),
self.workspace.clone(),
self.language_registry.clone(), self.language_registry.clone(),
self.tools.clone(),
window, window,
cx, cx,
) )
@@ -376,7 +379,9 @@ impl AssistantPanel {
ActiveThread::new( ActiveThread::new(
thread.clone(), thread.clone(),
this.thread_store.clone(), this.thread_store.clone(),
this.workspace.clone(),
this.language_registry.clone(), this.language_registry.clone(),
this.tools.clone(),
window, window,
cx, cx,
) )
@@ -603,7 +608,7 @@ impl AssistantPanel {
.id("title") .id("title")
.overflow_x_scroll() .overflow_x_scroll()
.px(DynamicSpacing::Base08.rems(cx)) .px(DynamicSpacing::Base08.rems(cx))
.child(Label::new(title).truncate()), .child(Label::new(title).text_ellipsis()),
) )
.child( .child(
h_flex() h_flex()
@@ -987,21 +992,12 @@ impl AssistantPanel {
) )
.into_any() .into_any()
} }
fn key_context(&self) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("AssistantPanel2");
if matches!(self.active_view, ActiveView::PromptEditor) {
key_context.add("prompt_editor");
}
key_context
}
} }
impl Render for AssistantPanel { impl Render for AssistantPanel {
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 {
v_flex() v_flex()
.key_context(self.key_context()) .key_context("AssistantPanel2")
.justify_between() .justify_between()
.size_full() .size_full()
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
@@ -1016,7 +1012,12 @@ impl Render for AssistantPanel {
.map(|parent| match self.active_view { .map(|parent| match self.active_view {
ActiveView::Thread => parent ActiveView::Thread => parent
.child(self.render_active_thread_or_empty_state(window, cx)) .child(self.render_active_thread_or_empty_state(window, cx))
.child(h_flex().child(self.message_editor.clone())) .child(
h_flex()
.border_t_1()
.border_color(cx.theme().colors().border)
.child(self.message_editor.clone()),
)
.children(self.render_last_error(cx)), .children(self.render_last_error(cx)),
ActiveView::History => parent.child(self.history.clone()), ActiveView::History => parent.child(self.history.clone()),
ActiveView::PromptEditor => parent.children(self.context_editor.clone()), ActiveView::PromptEditor => parent.children(self.context_editor.clone()),

View File

@@ -14,7 +14,7 @@ use language_model::{
}; };
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use rope::Rope; use rope::Rope;
use smol::future::FutureExt; use smol::future::FutureExt;
use std::{ use std::{

View File

@@ -167,8 +167,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
} }
} }
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> { fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
Some("Enter the URL that you would like to fetch".into()) "Enter the URL that you would like to fetch".into()
} }
fn selected_index(&self) -> usize { fn selected_index(&self) -> usize {

View File

@@ -208,18 +208,12 @@ impl ContextStore {
let mut text_tasks = Vec::new(); let mut text_tasks = Vec::new();
this.update(&mut cx, |_, cx| { this.update(&mut cx, |_, cx| {
for (path, buffer_entity) in files.into_iter().zip(buffers) { for (path, buffer_entity) in files.into_iter().zip(buffers) {
// Skip all binary files and other non-UTF8 files let buffer_entity = buffer_entity?;
if let Ok(buffer_entity) = buffer_entity { let buffer = buffer_entity.read(cx);
let buffer = buffer_entity.read(cx); let (buffer_info, text_task) =
let (buffer_info, text_task) = collect_buffer_info_and_text( collect_buffer_info_and_text(path, buffer_entity, buffer, cx.to_async());
path, buffer_infos.push(buffer_info);
buffer_entity, text_tasks.push(text_task);
buffer,
cx.to_async(),
);
buffer_infos.push(buffer_info);
text_tasks.push(text_task);
}
} }
anyhow::Ok(()) anyhow::Ok(())
})??; })??;

View File

@@ -25,7 +25,7 @@ use crate::{
pub struct ContextStrip { pub struct ContextStrip {
context_store: Entity<ContextStore>, context_store: Entity<ContextStore>,
context_picker: Entity<ContextPicker>, pub context_picker: Entity<ContextPicker>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
suggest_context_kind: SuggestContextKind, suggest_context_kind: SuggestContextKind,
@@ -36,6 +36,7 @@ pub struct ContextStrip {
} }
impl ContextStrip { impl ContextStrip {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
context_store: Entity<ContextStore>, context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,

View File

@@ -27,9 +27,8 @@ use language::{Buffer, Point, Selection, TransactionId};
use language_model::{report_assistant_event, LanguageModelRegistry}; use language_model::{report_assistant_event, LanguageModelRegistry};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::LspAction;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
@@ -480,6 +479,7 @@ impl InlineAssistant {
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn suggest_assist( pub fn suggest_assist(
&mut self, &mut self,
editor: &Entity<Editor>, editor: &Entity<Editor>,
@@ -1450,6 +1450,7 @@ struct InlineAssistScrollLock {
} }
impl EditorInlineAssists { impl EditorInlineAssists {
#[allow(clippy::too_many_arguments)]
fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self { fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(()); let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
Self { Self {
@@ -1561,6 +1562,7 @@ pub struct InlineAssist {
} }
impl InlineAssist { impl InlineAssist {
#[allow(clippy::too_many_arguments)]
fn new( fn new(
assist_id: InlineAssistId, assist_id: InlineAssistId,
group_id: InlineAssistGroupId, group_id: InlineAssistGroupId,
@@ -1725,10 +1727,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
Task::ready(Ok(vec![CodeAction { Task::ready(Ok(vec![CodeAction {
server_id: language::LanguageServerId(0), server_id: language::LanguageServerId(0),
range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end), range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
lsp_action: LspAction::Action(Box::new(lsp::CodeAction { lsp_action: lsp::CodeAction {
title: "Fix with Assistant".into(), title: "Fix with Assistant".into(),
..Default::default() ..Default::default()
})), },
}])) }]))
} else { } else {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))

View File

@@ -20,7 +20,6 @@ use gpui::{
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window, EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
}; };
use language_model::{LanguageModel, LanguageModelRegistry}; use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::ToggleModelSelector;
use parking_lot::Mutex; use parking_lot::Mutex;
use settings::Settings; use settings::Settings;
use std::cmp; use std::cmp;
@@ -103,9 +102,11 @@ impl<T: 'static> Render for PromptEditor<T> {
.items_start() .items_start()
.cursor(CursorStyle::Arrow) .cursor(CursorStyle::Arrow)
.on_action(cx.listener(Self::toggle_context_picker)) .on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| { .on_action(cx.listener(|this, action, window, cx| {
this.model_selector let selector = this.model_selector.read(cx).selector.clone();
.update(cx, |model_selector, cx| model_selector.toggle(window, cx)); selector.update(cx, |selector, cx| {
selector.toggle_model_selector(action, window, cx);
})
})) }))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
@@ -816,6 +817,7 @@ impl InlineAssistId {
} }
impl PromptEditor<BufferCodegen> { impl PromptEditor<BufferCodegen> {
#[allow(clippy::too_many_arguments)]
pub fn new_buffer( pub fn new_buffer(
id: InlineAssistId, id: InlineAssistId,
gutter_dimensions: Arc<Mutex<GutterDimensions>>, gutter_dimensions: Arc<Mutex<GutterDimensions>>,
@@ -856,7 +858,6 @@ impl PromptEditor<BufferCodegen> {
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
@@ -880,13 +881,7 @@ impl PromptEditor<BufferCodegen> {
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
fs,
model_selector_menu_handle,
prompt_editor.focus_handle(cx),
window,
cx,
)
}), }),
edited_since_done: false, edited_since_done: false,
prompt_history, prompt_history,
@@ -975,6 +970,7 @@ impl TerminalInlineAssistId {
} }
impl PromptEditor<TerminalCodegen> { impl PromptEditor<TerminalCodegen> {
#[allow(clippy::too_many_arguments)]
pub fn new_terminal( pub fn new_terminal(
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
@@ -1010,7 +1006,6 @@ impl PromptEditor<TerminalCodegen> {
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
@@ -1034,13 +1029,7 @@ impl PromptEditor<TerminalCodegen> {
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
}), }),
edited_since_done: false, edited_since_done: false,
prompt_history, prompt_history,

View File

@@ -2,22 +2,20 @@ use std::sync::Arc;
use editor::actions::MoveUp; use editor::actions::MoveUp;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle}; use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use file_icons::FileIcons;
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, TextStyle, pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
WeakEntity, TextStyle, WeakEntity,
}; };
use language_model::LanguageModelRegistry; use language_model::LanguageModelRegistry;
use language_model_selector::ToggleModelSelector;
use rope::Point; use rope::Point;
use settings::Settings; use settings::Settings;
use std::time::Duration; use std::time::Duration;
use text::Bias; use text::Bias;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
prelude::*, ButtonLike, Disclosure, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch,
Switch, Tooltip, TintColor, Tooltip,
}; };
use vim_mode_setting::VimModeSetting; use vim_mode_setting::VimModeSetting;
use workspace::Workspace; use workspace::Workspace;
@@ -40,7 +38,6 @@ pub struct MessageEditor {
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
use_tools: bool, use_tools: bool,
edits_expanded: bool,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -56,7 +53,6 @@ impl MessageEditor {
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone())); let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default(); let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new(|cx| { let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx); let mut editor = Editor::auto_height(10, window, cx);
@@ -109,17 +105,9 @@ impl MessageEditor {
context_picker_menu_handle, context_picker_menu_handle,
inline_context_picker, inline_context_picker,
inline_context_picker_menu_handle, inline_context_picker_menu_handle,
model_selector: cx.new(|cx| { model_selector: cx
AssistantModelSelector::new( .new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)),
fs,
model_selector_menu_handle,
editor.focus_handle(cx),
window,
cx,
)
}),
use_tools: false, use_tools: false,
edits_expanded: false,
_subscriptions: subscriptions, _subscriptions: subscriptions,
} }
} }
@@ -301,317 +289,168 @@ impl Render for MessageEditor {
let linux = platform == PlatformStyle::Linux; let linux = platform == PlatformStyle::Linux;
let windows = platform == PlatformStyle::Windows; let windows = platform == PlatformStyle::Windows;
let button_width = if linux || windows || vim_mode_enabled { let button_width = if linux || windows || vim_mode_enabled {
px(82.) px(92.)
} else { } else {
px(64.) px(64.)
}; };
let changed_buffers = self.thread.read(cx).scripting_changed_buffers(cx);
let changed_buffers_count = changed_buffers.len();
v_flex() v_flex()
.key_context("MessageEditor")
.on_action(cx.listener(Self::chat))
.on_action(cx.listener(|this, action, window, cx| {
let selector = this.model_selector.read(cx).selector.clone();
selector.update(cx, |this, cx| {
this.toggle_model_selector(action, window, cx);
})
}))
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::remove_all_context))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::toggle_chat_mode))
.size_full() .size_full()
.when(is_streaming_completion, |parent| { .gap_2()
let focus_handle = self.editor.focus_handle(cx).clone(); .p_2()
parent.child( .bg(bg_color)
h_flex().py_3().w_full().justify_center().child( .child(self.context_strip.clone())
h_flex()
.flex_none()
.pl_2()
.pr_1()
.py_1()
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_lg()
.shadow_md()
.gap_1()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(gpui::Transformation::rotate(
gpui::percentage(delta),
))
},
),
)
.child(
Label::new("Generating…")
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.child(ui::Divider::vertical())
.child(
Button::new("cancel-generation", "Cancel")
.label_size(LabelSize::XSmall)
.key_binding(
KeyBinding::for_action_in(
&editor::actions::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(
&editor::actions::Cancel,
window,
cx,
);
}),
),
),
)
})
.when(changed_buffers_count > 0, |parent| {
parent.child(
v_flex()
.mx_2()
.bg(cx.theme().colors().element_background)
.border_1()
.border_b_0()
.border_color(cx.theme().colors().border)
.rounded_t_md()
.child(
h_flex()
.gap_2()
.p_2()
.child(
Disclosure::new("edits-disclosure", self.edits_expanded)
.on_click(cx.listener(|this, _ev, _window, cx| {
this.edits_expanded = !this.edits_expanded;
cx.notify();
})),
)
.child(
Label::new("Edits")
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.child(Label::new("").size(LabelSize::XSmall).color(Color::Muted))
.child(
Label::new(format!(
"{} {}",
changed_buffers_count,
if changed_buffers_count == 1 {
"file"
} else {
"files"
}
))
.size(LabelSize::XSmall)
.color(Color::Muted),
),
)
.when(self.edits_expanded, |parent| {
parent.child(
v_flex().bg(cx.theme().colors().editor_background).children(
changed_buffers.enumerate().flat_map(|(index, buffer)| {
let file = buffer.read(cx).file()?;
let path = file.path();
let parent_label = path.parent().and_then(|parent| {
let parent_str = parent.to_string_lossy();
if parent_str.is_empty() {
None
} else {
Some(
Label::new(format!(
"{}{}",
parent_str,
std::path::MAIN_SEPARATOR_STR
))
.color(Color::Muted)
.size(LabelSize::Small),
)
}
});
let name_label = path.file_name().map(|name| {
Label::new(name.to_string_lossy().to_string())
.size(LabelSize::Small)
});
let file_icon = FileIcons::get_icon(&path, cx)
.map(Icon::from_path)
.unwrap_or_else(|| Icon::new(IconName::File));
let element = div()
.p_2()
.when(index + 1 < changed_buffers_count, |parent| {
parent
.border_color(cx.theme().colors().border)
.border_b_1()
})
.child(
h_flex()
.gap_2()
.child(file_icon)
.child(
// TODO: handle overflow
h_flex()
.children(parent_label)
.children(name_label),
)
// TODO: show lines changed
.child(Label::new("+").color(Color::Created))
.child(Label::new("-").color(Color::Deleted)),
);
Some(element)
}),
),
)
}),
)
})
.child( .child(
v_flex() v_flex()
.key_context("MessageEditor") .gap_5()
.on_action(cx.listener(Self::chat)) .child({
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| { let settings = ThemeSettings::get_global(cx);
this.model_selector let text_style = TextStyle {
.update(cx, |model_selector, cx| model_selector.toggle(window, cx)); color: cx.theme().colors().text,
})) font_family: settings.ui_font.family.clone(),
.on_action(cx.listener(Self::toggle_context_picker)) font_features: settings.ui_font.features.clone(),
.on_action(cx.listener(Self::remove_all_context)) font_size: font_size.into(),
.on_action(cx.listener(Self::move_up)) font_weight: settings.ui_font.weight,
.on_action(cx.listener(Self::toggle_chat_mode)) line_height: line_height.into(),
.gap_2() ..Default::default()
.p_2() };
.bg(bg_color)
.border_t_1() EditorElement::new(
.border_color(cx.theme().colors().border) &self.editor,
.child(self.context_strip.clone()) EditorStyle {
background: bg_color,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
})
.child( .child(
v_flex() PopoverMenu::new("inline-context-picker")
.gap_5() .menu(move |window, cx| {
.child({ inline_context_picker.update(cx, |this, cx| {
let settings = ThemeSettings::get_global(cx); this.init(window, cx);
let text_style = TextStyle { });
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
EditorElement::new( Some(inline_context_picker.clone())
&self.editor,
EditorStyle {
background: bg_color,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
}) })
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
x: px(0.0),
y: (-ThemeSettings::get_global(cx).ui_font_size(cx) * 2) - px(4.0),
})
.with_handle(self.inline_context_picker_menu_handle.clone()),
)
.child(
h_flex()
.justify_between()
.child( .child(
PopoverMenu::new("inline-context-picker") Switch::new("use-tools", self.use_tools.into())
.menu(move |window, cx| { .label("Tools")
inline_context_picker.update(cx, |this, cx| { .on_click(cx.listener(|this, selection, _window, _cx| {
this.init(window, cx); this.use_tools = match selection {
}); ToggleState::Selected => true,
ToggleState::Unselected
Some(inline_context_picker.clone()) | ToggleState::Indeterminate => false,
}) };
.attach(gpui::Corner::TopLeft) }))
.anchor(gpui::Corner::BottomLeft) .key_binding(KeyBinding::for_action_in(
.offset(gpui::Point { &ChatMode,
x: px(0.0), &focus_handle,
y: (-ThemeSettings::get_global(cx).ui_font_size(cx) * 2) window,
- px(4.0), cx,
}) )),
.with_handle(self.inline_context_picker_menu_handle.clone()),
) )
.child( .child(h_flex().gap_1().child(self.model_selector.clone()).child(
h_flex() if is_streaming_completion {
.justify_between() ButtonLike::new("cancel-generation")
.child( .width(button_width.into())
Switch::new("use-tools", self.use_tools.into()) .style(ButtonStyle::Tinted(TintColor::Accent))
.label("Tools") .child(
.on_click(cx.listener( h_flex()
|this, selection, _window, _cx| { .w_full()
this.use_tools = match selection { .justify_between()
ToggleState::Selected => true,
ToggleState::Unselected
| ToggleState::Indeterminate => false,
};
},
))
.key_binding(KeyBinding::for_action_in(
&ChatMode,
&focus_handle,
window,
cx,
)),
)
.child(
h_flex().gap_1().child(self.model_selector.clone()).child(
ButtonLike::new("submit-message")
.width(button_width.into())
.style(ButtonStyle::Filled)
.disabled(
is_editor_empty
|| !is_model_selected
|| is_streaming_completion,
)
.child( .child(
h_flex() Label::new("Cancel")
.w_full() .size(LabelSize::Small)
.justify_between() .with_animation(
.child( "pulsating-label",
Label::new("Submit") Animation::new(Duration::from_secs(2))
.size(LabelSize::Small) .repeat()
.color(submit_label_color), .with_easing(pulsating_between(
) 0.4, 0.8,
.children( )),
KeyBinding::for_action_in( |label, delta| label.alpha(delta),
&Chat,
&focus_handle,
window,
cx,
)
.map(|binding| {
binding
.when(vim_mode_enabled, |kb| {
kb.size(rems_from_px(12.))
})
.into_any_element()
}),
), ),
) )
.on_click(move |_event, window, cx| { .children(
focus_handle.dispatch_action(&Chat, window, cx); KeyBinding::for_action_in(
}) &editor::actions::Cancel,
.when(is_editor_empty, |button| { &focus_handle,
button.tooltip(Tooltip::text( window,
"Type a message to submit", cx,
)) )
}) .map(|binding| binding.into_any_element()),
.when(is_streaming_completion, |button| { ),
button.tooltip(Tooltip::text( )
"Cancel to submit a new message", .on_click(move |_event, window, cx| {
)) focus_handle.dispatch_action(
}) &editor::actions::Cancel,
.when(!is_model_selected, |button| { window,
button.tooltip(Tooltip::text( cx,
"Select a model to continue", );
)) })
}), } else {
), ButtonLike::new("submit-message")
), .width(button_width.into())
), .style(ButtonStyle::Filled)
.disabled(is_editor_empty || !is_model_selected)
.child(
h_flex()
.w_full()
.justify_between()
.child(
Label::new("Submit")
.size(LabelSize::Small)
.color(submit_label_color),
)
.children(
KeyBinding::for_action_in(
&Chat,
&focus_handle,
window,
cx,
)
.map(|binding| binding.into_any_element()),
),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, window, cx);
})
.when(is_editor_empty, |button| {
button
.tooltip(Tooltip::text("Type a message to submit"))
})
.when(!is_model_selected, |button| {
button.tooltip(Tooltip::text(
"Select a model to continue",
))
})
},
)),
), ),
) )
} }

View File

@@ -16,7 +16,7 @@ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest, report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role, LanguageModelRequestMessage, Role,
}; };
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use std::sync::Arc; use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::TerminalView; use terminal_view::TerminalView;

View File

@@ -5,15 +5,12 @@ use assistant_tool::ToolWorkingSet;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use futures::StreamExt as _; use futures::StreamExt as _;
use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task}; use gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{ use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult, LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId,
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason,
Role, StopReason,
}; };
use project::Project;
use scripting_tool::{ScriptingSession, ScriptingTool};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::{post_inc, TryFutureExt as _}; use util::{post_inc, TryFutureExt as _};
use uuid::Uuid; use uuid::Uuid;
@@ -73,21 +70,12 @@ pub struct Thread {
context_by_message: HashMap<MessageId, Vec<ContextId>>, context_by_message: HashMap<MessageId, Vec<ContextId>>,
completion_count: usize, completion_count: usize,
pending_completions: Vec<PendingCompletion>, pending_completions: Vec<PendingCompletion>,
project: Entity<Project>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
tool_use: ToolUseState, tool_use: ToolUseState,
scripting_session: Entity<ScriptingSession>,
scripting_tool_use: ToolUseState,
} }
impl Thread { impl Thread {
pub fn new( pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut Context<Self>) -> Self {
project: Entity<Project>,
tools: Arc<ToolWorkingSet>,
cx: &mut Context<Self>,
) -> Self {
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
Self { Self {
id: ThreadId::new(), id: ThreadId::new(),
updated_at: Utc::now(), updated_at: Utc::now(),
@@ -99,33 +87,18 @@ impl Thread {
context_by_message: HashMap::default(), context_by_message: HashMap::default(),
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
project,
tools, tools,
tool_use: ToolUseState::new(), tool_use: ToolUseState::default(),
scripting_session,
scripting_tool_use: ToolUseState::new(),
} }
} }
pub fn from_saved( pub fn from_saved(
id: ThreadId, id: ThreadId,
saved: SavedThread, saved: SavedThread,
project: Entity<Project>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
cx: &mut Context<Self>, _cx: &mut Context<Self>,
) -> Self { ) -> Self {
let next_message_id = MessageId( let next_message_id = MessageId(saved.messages.len());
saved
.messages
.last()
.map(|message| message.id.0 + 1)
.unwrap_or(0),
);
let tool_use =
ToolUseState::from_saved_messages(&saved.messages, |name| name != ScriptingTool::NAME);
let scripting_tool_use =
ToolUseState::from_saved_messages(&saved.messages, |name| name == ScriptingTool::NAME);
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
Self { Self {
id, id,
@@ -146,11 +119,8 @@ impl Thread {
context_by_message: HashMap::default(), context_by_message: HashMap::default(),
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
project,
tools, tools,
tool_use, tool_use: ToolUseState::default(),
scripting_session,
scripting_tool_use,
} }
} }
@@ -211,65 +181,29 @@ impl Thread {
) )
} }
/// Returns whether all of the tool uses have finished running. pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
pub fn all_tools_finished(&self) -> bool { self.tool_use.pending_tool_uses()
let mut all_pending_tool_uses = self
.tool_use
.pending_tool_uses()
.into_iter()
.chain(self.scripting_tool_use.pending_tool_uses());
// If the only pending tool uses left are the ones with errors, then that means that we've finished running all
// of the pending tools.
all_pending_tool_uses.all(|tool_use| tool_use.status.is_error())
} }
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> { pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
self.tool_use.tool_uses_for_message(id) self.tool_use.tool_uses_for_message(id)
} }
pub fn scripting_tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
self.scripting_tool_use.tool_uses_for_message(id)
}
pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {
self.tool_use.tool_results_for_message(id)
}
pub fn scripting_tool_results_for_message(
&self,
id: MessageId,
) -> Vec<&LanguageModelToolResult> {
self.scripting_tool_use.tool_results_for_message(id)
}
pub fn scripting_changed_buffers<'a>(
&self,
cx: &'a App,
) -> impl ExactSizeIterator<Item = &'a Entity<language::Buffer>> {
self.scripting_session.read(cx).changed_buffers()
}
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool { pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
self.tool_use.message_has_tool_results(message_id) self.tool_use.message_has_tool_results(message_id)
} }
pub fn message_has_scripting_tool_results(&self, message_id: MessageId) -> bool {
self.scripting_tool_use.message_has_tool_results(message_id)
}
pub fn insert_user_message( pub fn insert_user_message(
&mut self, &mut self,
text: impl Into<String>, text: impl Into<String>,
context: Vec<ContextSnapshot>, context: Vec<ContextSnapshot>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> MessageId { ) {
let message_id = self.insert_message(Role::User, text, cx); let message_id = self.insert_message(Role::User, text, cx);
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>(); let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
self.context self.context
.extend(context.into_iter().map(|context| (context.id, context))); .extend(context.into_iter().map(|context| (context.id, context)));
self.context_by_message.insert(message_id, context_ids); self.context_by_message.insert(message_id, context_ids);
message_id
} }
pub fn insert_message( pub fn insert_message(
@@ -289,34 +223,6 @@ impl Thread {
id id
} }
pub fn edit_message(
&mut self,
id: MessageId,
new_role: Role,
new_text: String,
cx: &mut Context<Self>,
) -> bool {
let Some(message) = self.messages.iter_mut().find(|message| message.id == id) else {
return false;
};
message.role = new_role;
message.text = new_text;
self.touch_updated_at();
cx.emit(ThreadEvent::MessageEdited(id));
true
}
pub fn delete_message(&mut self, id: MessageId, cx: &mut Context<Self>) -> bool {
let Some(index) = self.messages.iter().position(|message| message.id == id) else {
return false;
};
self.messages.remove(index);
self.context_by_message.remove(&id);
self.touch_updated_at();
cx.emit(ThreadEvent::MessageDeleted(id));
true
}
/// Returns the representation of this [`Thread`] in a textual form. /// Returns the representation of this [`Thread`] in a textual form.
/// ///
/// This is the representation we use when attaching a thread as context to another thread. /// This is the representation we use when attaching a thread as context to another thread.
@@ -348,25 +254,16 @@ impl Thread {
let mut request = self.to_completion_request(request_kind, cx); let mut request = self.to_completion_request(request_kind, cx);
if use_tools { if use_tools {
let mut tools = Vec::new(); request.tools = self
tools.push(LanguageModelRequestTool { .tools()
name: ScriptingTool::NAME.into(), .tools(cx)
description: ScriptingTool::DESCRIPTION.into(), .into_iter()
input_schema: ScriptingTool::input_schema(), .map(|tool| LanguageModelRequestTool {
}); name: tool.name(),
description: tool.description(),
tools.extend( input_schema: tool.input_schema(),
self.tools() })
.tools(cx) .collect();
.into_iter()
.map(|tool| LanguageModelRequestTool {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
}),
);
request.tools = tools;
} }
self.stream_completion(request, model, cx); self.stream_completion(request, model, cx);
@@ -396,13 +293,10 @@ impl Thread {
content: Vec::new(), content: Vec::new(),
cache: false, cache: false,
}; };
match request_kind { match request_kind {
RequestKind::Chat => { RequestKind::Chat => {
self.tool_use self.tool_use
.attach_tool_results(message.id, &mut request_message); .attach_tool_results(message.id, &mut request_message);
self.scripting_tool_use
.attach_tool_results(message.id, &mut request_message);
} }
RequestKind::Summarize => { RequestKind::Summarize => {
// We don't care about tool use during summarization. // We don't care about tool use during summarization.
@@ -419,13 +313,11 @@ impl Thread {
RequestKind::Chat => { RequestKind::Chat => {
self.tool_use self.tool_use
.attach_tool_uses(message.id, &mut request_message); .attach_tool_uses(message.id, &mut request_message);
self.scripting_tool_use
.attach_tool_uses(message.id, &mut request_message);
} }
RequestKind::Summarize => { RequestKind::Summarize => {
// We don't care about tool use during summarization. // We don't care about tool use during summarization.
} }
}; }
request.messages.push(request_message); request.messages.push(request_message);
} }
@@ -489,7 +381,7 @@ impl Thread {
// Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it // Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
// will result in duplicating the text of the chunk in the rendered Markdown. // will result in duplicating the text of the chunk in the rendered Markdown.
thread.insert_message(Role::Assistant, chunk, cx); thread.insert_message(Role::Assistant, chunk, cx);
}; }
} }
} }
LanguageModelCompletionEvent::ToolUse(tool_use) => { LanguageModelCompletionEvent::ToolUse(tool_use) => {
@@ -498,15 +390,9 @@ impl Thread {
.iter() .iter()
.rfind(|message| message.role == Role::Assistant) .rfind(|message| message.role == Role::Assistant)
{ {
if tool_use.name.as_ref() == ScriptingTool::NAME { thread
thread .tool_use
.scripting_tool_use .request_tool_use(last_assistant_message.id, tool_use);
.request_tool_use(last_assistant_message.id, tool_use);
} else {
thread
.tool_use
.request_tool_use(last_assistant_message.id, tool_use);
}
} }
} }
} }
@@ -624,63 +510,6 @@ impl Thread {
}); });
} }
pub fn use_pending_tools(&mut self, cx: &mut Context<Self>) {
let pending_tool_uses = self
.tool_use
.pending_tool_uses()
.into_iter()
.filter(|tool_use| tool_use.status.is_idle())
.cloned()
.collect::<Vec<_>>();
for tool_use in pending_tool_uses {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
let task = tool.run(tool_use.input, self.project.clone(), cx);
self.insert_tool_output(tool_use.id.clone(), task, cx);
}
}
let pending_scripting_tool_uses = self
.scripting_tool_use
.pending_tool_uses()
.into_iter()
.filter(|tool_use| tool_use.status.is_idle())
.cloned()
.collect::<Vec<_>>();
for scripting_tool_use in pending_scripting_tool_uses {
let task = match ScriptingTool::deserialize_input(scripting_tool_use.input) {
Err(err) => Task::ready(Err(err.into())),
Ok(input) => {
let (script_id, script_task) =
self.scripting_session.update(cx, move |session, cx| {
session.run_script(input.lua_script, cx)
});
let session = self.scripting_session.clone();
cx.spawn(|_, cx| async move {
script_task.await;
let message = session.read_with(&cx, |session, _cx| {
// Using a id to get the script output seems impractical.
// Why not just include it in the Task result?
// This is because we'll later report the script state as it runs,
session
.get(script_id)
.output_message_for_llm()
.expect("Script shouldn't still be running")
})?;
Ok(message)
})
}
};
self.insert_scripting_tool_output(scripting_tool_use.id.clone(), task, cx);
}
}
pub fn insert_tool_output( pub fn insert_tool_output(
&mut self, &mut self,
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
@@ -693,14 +522,11 @@ impl Thread {
let output = output.await; let output = output.await;
thread thread
.update(&mut cx, |thread, cx| { .update(&mut cx, |thread, cx| {
let pending_tool_use = thread thread
.tool_use .tool_use
.insert_tool_output(tool_use_id.clone(), output); .insert_tool_output(tool_use_id.clone(), output);
cx.emit(ThreadEvent::ToolFinished { cx.emit(ThreadEvent::ToolFinished { tool_use_id });
tool_use_id,
pending_tool_use,
});
}) })
.ok(); .ok();
} }
@@ -710,52 +536,6 @@ impl Thread {
.run_pending_tool(tool_use_id, insert_output_task); .run_pending_tool(tool_use_id, insert_output_task);
} }
pub fn insert_scripting_tool_output(
&mut self,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut Context<Self>,
) {
let insert_output_task = cx.spawn(|thread, mut cx| {
let tool_use_id = tool_use_id.clone();
async move {
let output = output.await;
thread
.update(&mut cx, |thread, cx| {
let pending_tool_use = thread
.scripting_tool_use
.insert_tool_output(tool_use_id.clone(), output);
cx.emit(ThreadEvent::ToolFinished {
tool_use_id,
pending_tool_use,
});
})
.ok();
}
});
self.scripting_tool_use
.run_pending_tool(tool_use_id, insert_output_task);
}
pub fn send_tool_results_to_model(
&mut self,
model: Arc<dyn LanguageModel>,
cx: &mut Context<Self>,
) {
// Insert a user message to contain the tool results.
self.insert_user_message(
// TODO: Sending up a user message without any content results in the model sending back
// responses that also don't have any content. We currently don't handle this case well,
// so for now we provide some text to keep the model on track.
"Here are the tool results.",
Vec::new(),
cx,
);
self.send_to_model(model, RequestKind::Chat, true, cx);
}
/// Cancels the last pending completion, if there are any pending. /// Cancels the last pending completion, if there are any pending.
/// ///
/// Returns whether a completion was canceled. /// Returns whether a completion was canceled.
@@ -781,15 +561,11 @@ pub enum ThreadEvent {
StreamedCompletion, StreamedCompletion,
StreamedAssistantText(MessageId, String), StreamedAssistantText(MessageId, String),
MessageAdded(MessageId), MessageAdded(MessageId),
MessageEdited(MessageId),
MessageDeleted(MessageId),
SummaryChanged, SummaryChanged,
UsePendingTools, UsePendingTools,
ToolFinished { ToolFinished {
#[allow(unused)] #[allow(unused)]
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
/// The pending tool use that corresponds to this tool.
pending_tool_use: Option<PendingToolUse>,
}, },
} }

View File

@@ -33,9 +33,9 @@ impl ThreadHistory {
} }
} }
pub fn select_previous( pub fn select_prev(
&mut self, &mut self,
_: &menu::SelectPrevious, _: &menu::SelectPrev,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@@ -166,7 +166,7 @@ impl Render for ThreadHistory {
.overflow_y_scroll() .overflow_y_scroll()
.size_full() .size_full()
.p_1() .p_1()
.on_action(cx.listener(Self::select_previous)) .on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_first)) .on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_last)) .on_action(cx.listener(Self::select_last))
@@ -260,7 +260,7 @@ impl RenderOnce for PastThread {
.start_slot( .start_slot(
div() div()
.max_w_4_5() .max_w_4_5()
.child(Label::new(summary).size(LabelSize::Small).truncate()), .child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
) )
.end_slot( .end_slot(
h_flex() h_flex()
@@ -356,7 +356,7 @@ impl RenderOnce for PastContext {
.start_slot( .start_slot(
div() div()
.max_w_4_5() .max_w_4_5()
.child(Label::new(summary).size(LabelSize::Small).truncate()), .child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
) )
.end_slot( .end_slot(
h_flex() h_flex()

View File

@@ -12,9 +12,9 @@ use futures::FutureExt as _;
use gpui::{ use gpui::{
prelude::*, App, BackgroundExecutor, Context, Entity, Global, ReadGlobal, SharedString, Task, prelude::*, App, BackgroundExecutor, Context, Entity, Global, ReadGlobal, SharedString, Task,
}; };
use heed::types::{SerdeBincode, SerdeJson}; use heed::types::SerdeBincode;
use heed::Database; use heed::Database;
use language_model::{LanguageModelToolUseId, Role}; use language_model::Role;
use project::Project; use project::Project;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::ResultExt as _; use util::ResultExt as _;
@@ -26,6 +26,7 @@ pub fn init(cx: &mut App) {
} }
pub struct ThreadStore { pub struct ThreadStore {
#[allow(unused)]
project: Entity<Project>, project: Entity<Project>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
context_server_manager: Entity<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
@@ -77,7 +78,7 @@ impl ThreadStore {
} }
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> { pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
cx.new(|cx| Thread::new(self.project.clone(), self.tools.clone(), cx)) cx.new(|cx| Thread::new(self.tools.clone(), cx))
} }
pub fn open_thread( pub fn open_thread(
@@ -95,15 +96,7 @@ impl ThreadStore {
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?; .ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
cx.new(|cx| { cx.new(|cx| Thread::from_saved(id.clone(), thread, this.tools.clone(), cx))
Thread::from_saved(
id.clone(),
thread,
this.project.clone(),
this.tools.clone(),
cx,
)
})
}) })
}) })
} }
@@ -116,35 +109,10 @@ impl ThreadStore {
updated_at: thread.updated_at(), updated_at: thread.updated_at(),
messages: thread messages: thread
.messages() .messages()
.map(|message| { .map(|message| SavedMessage {
let all_tool_uses = thread id: message.id,
.tool_uses_for_message(message.id) role: message.role,
.into_iter() text: message.text.clone(),
.chain(thread.scripting_tool_uses_for_message(message.id))
.map(|tool_use| SavedToolUse {
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
})
.collect();
let all_tool_results = thread
.tool_results_for_message(message.id)
.into_iter()
.chain(thread.scripting_tool_results_for_message(message.id))
.map(|tool_result| SavedToolResult {
tool_use_id: tool_result.tool_use_id.clone(),
is_error: tool_result.is_error,
content: tool_result.content.clone(),
})
.collect();
SavedMessage {
id: message.id,
role: message.role,
text: message.text.clone(),
tool_uses: all_tool_uses,
tool_results: all_tool_results,
}
}) })
.collect(), .collect(),
}; };
@@ -271,29 +239,11 @@ pub struct SavedThread {
pub messages: Vec<SavedMessage>, pub messages: Vec<SavedMessage>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SavedMessage { pub struct SavedMessage {
pub id: MessageId, pub id: MessageId,
pub role: Role, pub role: Role,
pub text: String, pub text: String,
#[serde(default)]
pub tool_uses: Vec<SavedToolUse>,
#[serde(default)]
pub tool_results: Vec<SavedToolResult>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SavedToolUse {
pub id: LanguageModelToolUseId,
pub name: SharedString,
pub input: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SavedToolResult {
pub tool_use_id: LanguageModelToolUseId,
pub is_error: bool,
pub content: Arc<str>,
} }
struct GlobalThreadsDatabase( struct GlobalThreadsDatabase(
@@ -305,7 +255,7 @@ impl Global for GlobalThreadsDatabase {}
pub(crate) struct ThreadsDatabase { pub(crate) struct ThreadsDatabase {
executor: BackgroundExecutor, executor: BackgroundExecutor,
env: heed::Env, env: heed::Env,
threads: Database<SerdeBincode<ThreadId>, SerdeJson<SavedThread>>, threads: Database<SerdeBincode<ThreadId>, SerdeBincode<SavedThread>>,
} }
impl ThreadsDatabase { impl ThreadsDatabase {
@@ -320,7 +270,7 @@ impl ThreadsDatabase {
let database_future = executor let database_future = executor
.spawn({ .spawn({
let executor = executor.clone(); let executor = executor.clone();
let database_path = paths::support_dir().join("threads/threads-db.1.mdb"); let database_path = paths::support_dir().join("threads/threads-db.0.mdb");
async move { ThreadsDatabase::new(database_path, executor) } async move { ThreadsDatabase::new(database_path, executor) }
}) })
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new))) .then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))

View File

@@ -7,11 +7,10 @@ use futures::FutureExt as _;
use gpui::{SharedString, Task}; use gpui::{SharedString, Task};
use language_model::{ use language_model::{
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role, LanguageModelToolUseId, MessageContent,
}; };
use crate::thread::MessageId; use crate::thread::MessageId;
use crate::thread_store::SavedMessage;
#[derive(Debug)] #[derive(Debug)]
pub struct ToolUse { pub struct ToolUse {
@@ -29,6 +28,7 @@ pub enum ToolUseStatus {
Error(SharedString), Error(SharedString),
} }
#[derive(Default)]
pub struct ToolUseState { pub struct ToolUseState {
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>, tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>, tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
@@ -37,87 +37,6 @@ pub struct ToolUseState {
} }
impl ToolUseState { impl ToolUseState {
pub fn new() -> Self {
Self {
tool_uses_by_assistant_message: HashMap::default(),
tool_uses_by_user_message: HashMap::default(),
tool_results: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
}
}
/// Constructs a [`ToolUseState`] from the given list of [`SavedMessage`]s.
///
/// Accepts a function to filter the tools that should be used to populate the state.
pub fn from_saved_messages(
messages: &[SavedMessage],
mut filter_by_tool_name: impl FnMut(&str) -> bool,
) -> Self {
let mut this = Self::new();
let mut tool_names_by_id = HashMap::default();
for message in messages {
match message.role {
Role::Assistant => {
if !message.tool_uses.is_empty() {
let tool_uses = message
.tool_uses
.iter()
.filter(|tool_use| (filter_by_tool_name)(tool_use.name.as_ref()))
.map(|tool_use| LanguageModelToolUse {
id: tool_use.id.clone(),
name: tool_use.name.clone().into(),
input: tool_use.input.clone(),
})
.collect::<Vec<_>>();
tool_names_by_id.extend(
tool_uses
.iter()
.map(|tool_use| (tool_use.id.clone(), tool_use.name.clone())),
);
this.tool_uses_by_assistant_message
.insert(message.id, tool_uses);
}
}
Role::User => {
if !message.tool_results.is_empty() {
let tool_uses_by_user_message = this
.tool_uses_by_user_message
.entry(message.id)
.or_default();
for tool_result in &message.tool_results {
let tool_use_id = tool_result.tool_use_id.clone();
let Some(tool_use) = tool_names_by_id.get(&tool_use_id) else {
log::warn!("no tool name found for tool use: {tool_use_id:?}");
continue;
};
if !(filter_by_tool_name)(tool_use.as_ref()) {
continue;
}
tool_uses_by_user_message.push(tool_use_id.clone());
this.tool_results.insert(
tool_use_id.clone(),
LanguageModelToolResult {
tool_use_id,
is_error: tool_result.is_error,
content: tool_result.content.clone(),
},
);
}
}
}
Role::System => {}
}
}
this
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> { pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect() self.pending_tool_uses_by_id.values().collect()
} }
@@ -165,17 +84,6 @@ impl ToolUseState {
tool_uses tool_uses
} }
pub fn tool_results_for_message(&self, message_id: MessageId) -> Vec<&LanguageModelToolResult> {
let empty = Vec::new();
self.tool_uses_by_user_message
.get(&message_id)
.unwrap_or(&empty)
.iter()
.filter_map(|tool_use_id| self.tool_results.get(&tool_use_id))
.collect()
}
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool { pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
self.tool_uses_by_user_message self.tool_uses_by_user_message
.get(&message_id) .get(&message_id)
@@ -224,7 +132,7 @@ impl ToolUseState {
&mut self, &mut self,
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
output: Result<String>, output: Result<String>,
) -> Option<PendingToolUse> { ) {
match output { match output {
Ok(output) => { Ok(output) => {
self.tool_results.insert( self.tool_results.insert(
@@ -235,7 +143,7 @@ impl ToolUseState {
is_error: false, is_error: false,
}, },
); );
self.pending_tool_uses_by_id.remove(&tool_use_id) self.pending_tool_uses_by_id.remove(&tool_use_id);
} }
Err(err) => { Err(err) => {
self.tool_results.insert( self.tool_results.insert(
@@ -250,8 +158,6 @@ impl ToolUseState {
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) { if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Error(err.to_string().into()); tool_use.status = PendingToolUseStatus::Error(err.to_string().into());
} }
self.pending_tool_uses_by_id.get(&tool_use_id).cloned()
} }
} }
} }
@@ -291,7 +197,6 @@ impl ToolUseState {
pub struct PendingToolUse { pub struct PendingToolUse {
pub id: LanguageModelToolUseId, pub id: LanguageModelToolUseId,
/// The ID of the Assistant message in which the tool use was requested. /// The ID of the Assistant message in which the tool use was requested.
#[allow(unused)]
pub assistant_message_id: MessageId, pub assistant_message_id: MessageId,
pub name: Arc<str>, pub name: Arc<str>,
pub input: serde_json::Value, pub input: serde_json::Value,

View File

@@ -103,7 +103,7 @@ impl RenderOnce for ContextPill {
.pl_1() .pl_1()
.pb(px(1.)) .pb(px(1.))
.border_1() .border_1()
.rounded_sm() .rounded_md()
.gap_1() .gap_1()
.child(self.icon().size(IconSize::XSmall).color(Color::Muted)); .child(self.icon().size(IconSize::XSmall).color(Color::Muted));

View File

@@ -37,7 +37,7 @@ parking_lot.workspace = true
paths.workspace = true paths.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
prompt_store.workspace = true prompt_library.workspace = true
regex.workspace = true regex.workspace = true
rope.workspace = true rope.workspace = true
rpc.workspace = true rpc.workspace = true

View File

@@ -27,7 +27,7 @@ use language_model::{
use open_ai::Model as OpenAiModel; use open_ai::Model as OpenAiModel;
use paths::contexts_dir; use paths::contexts_dir;
use project::Project; use project::Project;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@@ -647,6 +647,7 @@ impl AssistantContext {
) )
} }
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
id: ContextId, id: ContextId,
replica_id: ReplicaId, replica_id: ReplicaId,
@@ -767,6 +768,7 @@ impl AssistantContext {
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn deserialize( pub fn deserialize(
saved_context: SavedContext, saved_context: SavedContext,
path: PathBuf, path: PathBuf,

View File

@@ -20,7 +20,7 @@ use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Rol
use parking_lot::Mutex; use parking_lot::Mutex;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use project::Project; use project::Project;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use rand::prelude::*; use rand::prelude::*;
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
@@ -671,7 +671,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
#[gpui::test] #[gpui::test]
async fn test_workflow_step_parsing(cx: &mut TestAppContext) { async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx.update(prompt_store::init); cx.update(prompt_library::init);
let mut settings_store = cx.update(SettingsStore::test); let mut settings_store = cx.update(SettingsStore::test);
cx.update(|cx| { cx.update(|cx| {
settings_store settings_store

View File

@@ -37,9 +37,7 @@ use language_model::{
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry, LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
Role, Role,
}; };
use language_model_selector::{ use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use picker::Picker; use picker::Picker;
use project::lsp_store::LocalLspAdapterDelegate; use project::lsp_store::LocalLspAdapterDelegate;
@@ -54,7 +52,7 @@ use ui::{
Tooltip, Tooltip,
}; };
use util::{maybe, ResultExt}; use util::{maybe, ResultExt};
use workspace::searchable::{Direction, SearchableItemHandle}; use workspace::searchable::SearchableItemHandle;
use workspace::{ use workspace::{
item::{self, FollowableItem, Item, ItemHandle}, item::{self, FollowableItem, Item, ItemHandle},
notifications::NotificationId, notifications::NotificationId,
@@ -198,7 +196,6 @@ pub struct ContextEditor {
// context editor, we keep a reference here. // context editor, we keep a reference here.
dragged_file_worktrees: Vec<Entity<Worktree>>, dragged_file_worktrees: Vec<Entity<Worktree>>,
language_model_selector: Entity<LanguageModelSelector>, language_model_selector: Entity<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
} }
pub const DEFAULT_TAB_TITLE: &str = "New Chat"; pub const DEFAULT_TAB_TITLE: &str = "New Chat";
@@ -252,6 +249,21 @@ impl ContextEditor {
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed), cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
]; ];
let fs_clone = fs.clone();
let language_model_selector = cx.new(|cx| {
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs_clone.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
});
let sections = context.read(cx).slash_command_output_sections().to_vec(); let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>(); let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands().clone(); let slash_commands = context.read(cx).slash_commands().clone();
@@ -264,7 +276,7 @@ impl ContextEditor {
image_blocks: Default::default(), image_blocks: Default::default(),
scroll_position: None, scroll_position: None,
remote_id: None, remote_id: None,
fs: fs.clone(), fs,
workspace, workspace,
project, project,
pending_slash_command_creases: HashMap::default(), pending_slash_command_creases: HashMap::default(),
@@ -276,20 +288,7 @@ impl ContextEditor {
show_accept_terms: false, show_accept_terms: false,
slash_menu_handle: Default::default(), slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(), dragged_file_worktrees: Vec::new(),
language_model_selector: cx.new(|cx| { language_model_selector,
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
language_model_selector_menu_handle: PopoverMenuHandle::default(),
}; };
this.update_message_headers(cx); this.update_message_headers(cx);
this.update_image_blocks(cx); this.update_image_blocks(cx);
@@ -535,6 +534,7 @@ impl ContextEditor {
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn run_command( pub fn run_command(
&mut self, &mut self,
command_range: Range<language::Anchor>, command_range: Range<language::Anchor>,
@@ -1240,7 +1240,7 @@ impl ContextEditor {
.child("Press") .child("Press")
.child( .child(
h_flex() h_flex()
.rounded_sm() .rounded_md()
.px_1() .px_1()
.mr_0p5() .mr_0p5()
.border_1() .border_1()
@@ -2056,6 +2056,7 @@ impl ContextEditor {
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE)) .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
} }
#[allow(clippy::too_many_arguments)]
fn render_patch_block( fn render_patch_block(
&mut self, &mut self,
range: Range<text::Anchor>, range: Range<text::Anchor>,
@@ -2090,7 +2091,7 @@ impl ContextEditor {
.ml(gutter_width) .ml(gutter_width)
.pb_1() .pb_1()
.w(max_width - gutter_width) .w(max_width - gutter_width)
.rounded_sm() .rounded_md()
.border_1() .border_1()
.border_color(theme.colors().border_variant) .border_color(theme.colors().border_variant)
.overflow_hidden() .overflow_hidden()
@@ -2387,46 +2388,6 @@ impl ContextEditor {
) )
} }
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.editor().focus_handle(cx).clone();
let model_name = match active_model {
Some(model) => model.name().0,
None => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomLeft,
)
.with_handle(self.language_model_selector_menu_handle.clone())
}
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> { fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?; let last_error = self.last_error.as_ref()?;
@@ -2871,7 +2832,7 @@ impl Render for ContextEditor {
None None
}; };
let language_model_selector = self.language_model_selector_menu_handle.clone(); let language_model_selector = self.language_model_selector.clone();
v_flex() v_flex()
.key_context("ContextEditor") .key_context("ContextEditor")
.capture_action(cx.listener(ContextEditor::cancel)) .capture_action(cx.listener(ContextEditor::cancel))
@@ -2884,8 +2845,10 @@ impl Render for ContextEditor {
.on_action(cx.listener(ContextEditor::edit)) .on_action(cx.listener(ContextEditor::edit))
.on_action(cx.listener(ContextEditor::assist)) .on_action(cx.listener(ContextEditor::assist))
.on_action(cx.listener(ContextEditor::split)) .on_action(cx.listener(ContextEditor::split))
.on_action(move |_: &ToggleModelSelector, window, cx| { .on_action(move |action, window, cx| {
language_model_selector.toggle(window, cx); language_model_selector.update(cx, |this, cx| {
this.toggle_model_selector(action, window, cx);
})
}) })
.size_full() .size_full()
.children(self.render_notice(cx)) .children(self.render_notice(cx))
@@ -2924,11 +2887,14 @@ impl Render for ContextEditor {
.gap_1() .gap_1()
.child(self.render_inject_context_menu(cx)) .child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical()) .child(ui::Divider::vertical())
.child( .child(div().pl_0p5().child({
div() let focus_handle = self.editor().focus_handle(cx).clone();
.pl_0p5() AssistantLanguageModelSelector::new(
.child(self.render_language_model_selector(cx)), focus_handle,
), self.language_model_selector.clone(),
)
.render(window, cx)
})),
) )
.child( .child(
h_flex() h_flex()
@@ -3104,13 +3070,12 @@ impl SearchableItem for ContextEditor {
fn active_match_index( fn active_match_index(
&mut self, &mut self,
direction: Direction,
matches: &[Self::Match], matches: &[Self::Match],
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<usize> { ) -> Option<usize> {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.active_match_index(direction, matches, window, cx) editor.active_match_index(matches, window, cx)
}) })
} }
} }
@@ -3420,7 +3385,7 @@ fn invoked_slash_command_fold_placeholder(
.ml_6() .ml_6()
.gap_2() .gap_2()
.bg(cx.theme().colors().surface_background) .bg(cx.theme().colors().surface_background)
.rounded_sm() .rounded_md()
.child(Label::new(format!("/{}", command.name.clone()))) .child(Label::new(format!("/{}", command.name.clone())))
.map(|parent| match &command.status { .map(|parent| match &command.status {
InvokedSlashCommandStatus::Running(_) => { InvokedSlashCommandStatus::Running(_) => {

View File

@@ -16,7 +16,7 @@ use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task,
use language::LanguageRegistry; use language::LanguageRegistry;
use paths::contexts_dir; use paths::contexts_dir;
use project::Project; use project::Project;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use regex::Regex; use regex::Regex;
use rpc::AnyProtoClient; use rpc::AnyProtoClient;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -104,53 +104,49 @@ impl ContextStore {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100); const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await; let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = let this = cx.new(|cx: &mut Context<Self>| {
cx.new(|cx: &mut Context<Self>| { let context_server_factory_registry =
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
ContextServerFactoryRegistry::default_global(cx); let context_server_manager = cx.new(|cx| {
let context_server_manager = cx.new(|cx| { ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
ContextServerManager::new( });
context_server_factory_registry, let mut this = Self {
project.clone(), contexts: Vec::new(),
cx, contexts_metadata: Vec::new(),
) context_server_manager,
}); context_server_slash_command_ids: HashMap::default(),
let mut this = Self { host_contexts: Vec::new(),
contexts: Vec::new(), fs,
contexts_metadata: Vec::new(), languages,
context_server_manager, slash_commands,
context_server_slash_command_ids: HashMap::default(), telemetry,
host_contexts: Vec::new(), _watch_updates: cx.spawn(|this, mut cx| {
fs, async move {
languages, while events.next().await.is_some() {
slash_commands, this.update(&mut cx, |this, cx| this.reload(cx))?
telemetry, .await
_watch_updates: cx.spawn(|this, mut cx| { .log_err();
async move {
while events.next().await.is_some() {
this.update(&mut cx, |this, cx| this.reload(cx))?
.await
.log_err();
}
anyhow::Ok(())
} }
.log_err() anyhow::Ok(())
}), }
client_subscription: None, .log_err()
_project_subscriptions: vec![ }),
cx.subscribe(&project, Self::handle_project_event) client_subscription: None,
], _project_subscriptions: vec![
project_is_shared: false, cx.observe(&project, Self::handle_project_changed),
client: project.read(cx).client(), cx.subscribe(&project, Self::handle_project_event),
project: project.clone(), ],
prompt_builder, project_is_shared: false,
}; client: project.read(cx).client(),
this.handle_project_shared(project.clone(), cx); project: project.clone(),
this.synchronize_contexts(cx); prompt_builder,
this.register_context_server_handlers(cx); };
this.reload(cx).detach_and_log_err(cx); this.handle_project_changed(project.clone(), cx);
this this.synchronize_contexts(cx);
})?; this.register_context_server_handlers(cx);
this.reload(cx).detach_and_log_err(cx);
this
})?;
Ok(this) Ok(this)
}) })
@@ -292,7 +288,7 @@ impl ContextStore {
})? })?
} }
fn handle_project_shared(&mut self, _: Entity<Project>, cx: &mut Context<Self>) { fn handle_project_changed(&mut self, _: Entity<Project>, cx: &mut Context<Self>) {
let is_shared = self.project.read(cx).is_shared(); let is_shared = self.project.read(cx).is_shared();
let was_shared = mem::replace(&mut self.project_is_shared, is_shared); let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
if is_shared == was_shared { if is_shared == was_shared {
@@ -322,14 +318,11 @@ impl ContextStore {
fn handle_project_event( fn handle_project_event(
&mut self, &mut self,
project: Entity<Project>, _: Entity<Project>,
event: &project::Event, event: &project::Event,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
project::Event::RemoteIdChanged(_) => {
self.handle_project_shared(project, cx);
}
project::Event::Reshared => { project::Event::Reshared => {
self.advertise_contexts(cx); self.advertise_contexts(cx);
} }

View File

@@ -140,7 +140,7 @@ impl ResolvedPatch {
buffer.edit( buffer.edit(
edits, edits,
Some(AutoindentMode::Block { Some(AutoindentMode::Block {
original_indent_columns: Vec::new(), original_start_columns: Vec::new(),
}), }),
cx, cx,
); );

View File

@@ -5,9 +5,9 @@ use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWor
use editor::{CompletionProvider, Editor}; use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window}; use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window};
use language::{Anchor, Buffer, ToPoint}; use language::{Anchor, Buffer, LanguageServerId, ToPoint};
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{lsp_store::CompletionDocumentation, CompletionIntent, CompletionSource}; use project::{lsp_store::CompletionDocumentation, CompletionIntent};
use rope::Point; use rope::Point;
use std::{ use std::{
cell::RefCell, cell::RefCell,
@@ -125,8 +125,10 @@ impl SlashCommandCompletionProvider {
)), )),
new_text, new_text,
label: command.label(cx), label: command.label(cx),
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm, confirm,
source: CompletionSource::Custom, resolved: true,
}) })
}) })
.collect() .collect()
@@ -134,6 +136,7 @@ impl SlashCommandCompletionProvider {
}) })
} }
#[allow(clippy::too_many_arguments)]
fn complete_command_argument( fn complete_command_argument(
&self, &self,
command_name: &str, command_name: &str,
@@ -222,8 +225,10 @@ impl SlashCommandCompletionProvider {
label: new_argument.label, label: new_argument.label,
new_text, new_text,
documentation: None, documentation: None,
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm, confirm,
source: CompletionSource::Custom, resolved: true,
} }
}) })
.collect()) .collect())

View File

@@ -207,31 +207,24 @@ impl PickerDelegate for SlashCommandDelegate {
.child( .child(
h_flex() h_flex()
.gap_1p5() .gap_1p5()
.child( .child(Icon::new(info.icon).size(IconSize::XSmall))
Icon::new(info.icon) .child(div().font_buffer(cx).child({
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child({
let mut label = format!("{}", info.name); let mut label = format!("{}", info.name);
if let Some(args) = info.args.as_ref().filter(|_| selected) if let Some(args) = info.args.as_ref().filter(|_| selected)
{ {
label.push_str(&args); label.push_str(&args);
} }
Label::new(label) Label::new(label).single_line().size(LabelSize::Small)
.single_line() }))
.size(LabelSize::Small)
.buffer_font(cx)
})
.children(info.args.clone().filter(|_| !selected).map( .children(info.args.clone().filter(|_| !selected).map(
|args| { |args| {
div() div()
.font_buffer(cx)
.child( .child(
Label::new(args) Label::new(args)
.single_line() .single_line()
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted),
.buffer_font(cx),
) )
.visible_on_hover(format!( .visible_on_hover(format!(
"command-entry-label-{ix}" "command-entry-label-{ix}"
@@ -243,7 +236,7 @@ impl PickerDelegate for SlashCommandDelegate {
Label::new(info.description.clone()) Label::new(info.description.clone())
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted)
.truncate(), .text_ellipsis(),
), ),
), ),
), ),
@@ -301,9 +294,10 @@ where
.gap_1p5() .gap_1p5()
.child(Icon::new(IconName::Plus).size(IconSize::XSmall)) .child(Icon::new(IconName::Plus).size(IconSize::XSmall))
.child( .child(
Label::new("create-your-command") div().font_buffer(cx).child(
.size(LabelSize::Small) Label::new("create-your-command")
.buffer_font(cx), .size(LabelSize::Small),
),
), ),
) )
.child( .child(
@@ -347,7 +341,7 @@ where
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point { .offset(gpui::Point {
x: px(0.0), x: px(0.0),
y: px(-2.0), y: px(-16.0),
}) })
.when_some(handle, |this, handle| this.with_handle(handle)) .when_some(handle, |this, handle| this.with_handle(handle))
} }

View File

@@ -88,6 +88,7 @@ pub trait SlashCommand: 'static + Send + Sync {
fn accepts_arguments(&self) -> bool { fn accepts_arguments(&self) -> bool {
self.requires_argument() self.requires_argument()
} }
#[allow(clippy::too_many_arguments)]
fn run( fn run(
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],

View File

@@ -32,7 +32,7 @@ language.workspace = true
language_model.workspace = true language_model.workspace = true
log.workspace = true log.workspace = true
project.workspace = true project.workspace = true
prompt_store.workspace = true prompt_library.workspace = true
rope.workspace = true rope.workspace = true
schemars.workspace = true schemars.workspace = true
semantic_index.workspace = true semantic_index.workspace = true

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
}; };
use gpui::{Task, WeakEntity}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_store::PromptStore; use prompt_library::PromptStore;
use std::{ use std::{
fmt::Write, fmt::Write,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},

View File

@@ -13,7 +13,7 @@ use feature_flags::FeatureFlag;
use gpui::{App, Task, WeakEntity}; use gpui::{App, Task, WeakEntity};
use language::{Anchor, CodeLabel, LspAdapterDelegate}; use language::{Anchor, CodeLabel, LspAdapterDelegate};
use language_model::{LanguageModelRegistry, LanguageModelTool}; use language_model::{LanguageModelRegistry, LanguageModelTool};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use schemars::JsonSchema; use schemars::JsonSchema;
use semantic_index::SemanticDb; use semantic_index::SemanticDb;
use serde::Deserialize; use serde::Deserialize;

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
}; };
use gpui::{Task, WeakEntity}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_store::PromptStore; use prompt_library::PromptStore;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use ui::prelude::*; use ui::prelude::*;
use workspace::Workspace; use workspace::Workspace;

View File

@@ -17,6 +17,6 @@ collections.workspace = true
derive_more.workspace = true derive_more.workspace = true
gpui.workspace = true gpui.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
project.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
workspace.workspace = true

View File

@@ -4,8 +4,8 @@ mod tool_working_set;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use gpui::{App, Entity, Task}; use gpui::{App, Task, WeakEntity, Window};
use project::Project; use workspace::Workspace;
pub use crate::tool_registry::*; pub use crate::tool_registry::*;
pub use crate::tool_working_set::*; pub use crate::tool_working_set::*;
@@ -31,7 +31,8 @@ pub trait Tool: 'static + Send + Sync {
fn run( fn run(
self: Arc<Self>, self: Arc<Self>,
input: serde_json::Value, input: serde_json::Value,
project: Entity<Project>, workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut App, cx: &mut App,
) -> Task<Result<String>>; ) -> Task<Result<String>>;
} }

View File

@@ -16,7 +16,7 @@ anyhow.workspace = true
assistant_tool.workspace = true assistant_tool.workspace = true
chrono.workspace = true chrono.workspace = true
gpui.workspace = true gpui.workspace = true
project.workspace = true
schemars.workspace = true schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
workspace.workspace = true

View File

@@ -1,19 +1,13 @@
mod list_worktrees_tool;
mod now_tool; mod now_tool;
mod read_file_tool;
use assistant_tool::ToolRegistry; use assistant_tool::ToolRegistry;
use gpui::App; use gpui::App;
use crate::list_worktrees_tool::ListWorktreesTool;
use crate::now_tool::NowTool; use crate::now_tool::NowTool;
use crate::read_file_tool::ReadFileTool;
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
assistant_tool::init(cx); assistant_tool::init(cx);
let registry = ToolRegistry::global(cx); let registry = ToolRegistry::global(cx);
registry.register_tool(NowTool); registry.register_tool(NowTool);
registry.register_tool(ListWorktreesTool);
registry.register_tool(ReadFileTool);
} }

View File

@@ -1,77 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use assistant_tool::Tool;
use gpui::{App, Entity, Task};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ListWorktreesToolInput {}
pub struct ListWorktreesTool;
impl Tool for ListWorktreesTool {
fn name(&self) -> String {
"list-worktrees".into()
}
fn description(&self) -> String {
"Lists all worktrees in the current project. Use this tool when you need to find available worktrees and their IDs.".into()
}
fn input_schema(&self) -> serde_json::Value {
serde_json::json!(
{
"type": "object",
"properties": {},
"required": []
}
)
}
fn run(
self: Arc<Self>,
_input: serde_json::Value,
project: Entity<Project>,
cx: &mut App,
) -> Task<Result<String>> {
cx.spawn(|cx| async move {
cx.update(|cx| {
#[derive(Debug, Serialize)]
struct WorktreeInfo {
id: usize,
root_name: String,
root_dir: Option<String>,
}
let worktrees = project.update(cx, |project, cx| {
project
.visible_worktrees(cx)
.map(|worktree| {
worktree.read_with(cx, |worktree, _cx| WorktreeInfo {
id: worktree.id().to_usize(),
root_dir: worktree
.root_dir()
.map(|root_dir| root_dir.to_string_lossy().to_string()),
root_name: worktree.root_name().to_string(),
})
})
.collect::<Vec<_>>()
});
if worktrees.is_empty() {
return Ok("No worktrees found in the current project.".to_string());
}
let mut result = String::from("Worktrees in the current project:\n\n");
for worktree in worktrees {
result.push_str(&serde_json::to_string(&worktree)?);
}
Ok(result)
})?
})
}
}

View File

@@ -3,8 +3,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use assistant_tool::Tool; use assistant_tool::Tool;
use chrono::{Local, Utc}; use chrono::{Local, Utc};
use gpui::{App, Entity, Task}; use gpui::{App, Task, WeakEntity, Window};
use project::Project;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -42,7 +41,8 @@ impl Tool for NowTool {
fn run( fn run(
self: Arc<Self>, self: Arc<Self>,
input: serde_json::Value, input: serde_json::Value,
_project: Entity<Project>, _workspace: WeakEntity<workspace::Workspace>,
_window: &mut Window,
_cx: &mut App, _cx: &mut App,
) -> Task<Result<String>> { ) -> Task<Result<String>> {
let input: NowToolInput = match serde_json::from_value(input) { let input: NowToolInput = match serde_json::from_value(input) {

View File

@@ -1,62 +0,0 @@
use std::path::Path;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use assistant_tool::Tool;
use gpui::{App, Entity, Task};
use project::{Project, ProjectPath, WorktreeId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ReadFileToolInput {
/// The ID of the worktree in which the file resides.
pub worktree_id: usize,
/// The path to the file to read.
///
/// This path is relative to the worktree root, it must not be an absolute path.
pub path: Arc<Path>,
}
pub struct ReadFileTool;
impl Tool for ReadFileTool {
fn name(&self) -> String {
"read-file".into()
}
fn description(&self) -> String {
"Reads the content of a file specified by a worktree ID and path. Use this tool when you need to access the contents of a file in the project.".into()
}
fn input_schema(&self) -> serde_json::Value {
let schema = schemars::schema_for!(ReadFileToolInput);
serde_json::to_value(&schema).unwrap()
}
fn run(
self: Arc<Self>,
input: serde_json::Value,
project: Entity<Project>,
cx: &mut App,
) -> Task<Result<String>> {
let input = match serde_json::from_value::<ReadFileToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
};
let project_path = ProjectPath {
worktree_id: WorktreeId::from_usize(input.worktree_id),
path: input.path,
};
cx.spawn(|cx| async move {
let buffer = cx
.update(|cx| {
project.update(cx, |project, cx| project.open_buffer(project_path, cx))
})?
.await?;
cx.update(|cx| buffer.read(cx).text())
})
}
}

View File

@@ -141,20 +141,19 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
cx, cx,
move |cx| { move |cx| {
let workspace_handle = cx.entity().downgrade(); let workspace_handle = cx.entity().downgrade();
cx.new(|cx| { cx.new(|_cx| {
MessageNotification::new( MessageNotification::new(format!("Updated to {app_name} {}", version))
format!("Updated to {app_name} {}", version), .primary_message("View Release Notes")
cx, .primary_on_click(move |window, cx| {
) if let Some(workspace) = workspace_handle.upgrade() {
.primary_message("View Release Notes") workspace.update(cx, |workspace, cx| {
.primary_on_click(move |window, cx| { crate::view_release_notes_locally(
if let Some(workspace) = workspace_handle.upgrade() { workspace, window, cx,
workspace.update(cx, |workspace, cx| { );
crate::view_release_notes_locally(workspace, window, cx); })
}) }
} cx.emit(DismissEvent);
cx.emit(DismissEvent); })
})
}) })
}, },
); );

View File

@@ -82,7 +82,7 @@ impl Render for Breadcrumbs {
text_style.color = Color::Muted.color(cx); text_style.color = Color::Muted.color(cx);
StyledText::new(segment.text.replace('\n', "")) StyledText::new(segment.text.replace('\n', ""))
.with_default_highlights(&text_style, segment.highlights.unwrap_or_default()) .with_highlights(&text_style, segment.highlights.unwrap_or_default())
.into_any() .into_any()
}); });
let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || { let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {

View File

@@ -16,7 +16,6 @@ test-support = []
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
clock.workspace = true
futures.workspace = true futures.workspace = true
git2.workspace = true git2.workspace = true
gpui.workspace = true gpui.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -614,19 +614,12 @@ mod windows {
let path = if let Some(path) = path { let path = if let Some(path) = path {
path.to_path_buf().canonicalize()? path.to_path_buf().canonicalize()?
} else { } else {
let cli = std::env::current_exe()?; std::env::current_exe()?
let dir = cli.parent().context("no parent path for cli")?; .parent()
.context("no parent path for cli")?
// ../Zed.exe is the standard, lib/zed is for MSYS2, ./zed.exe is for the target .parent()
// directory in development builds. .context("no parent path for cli folder")?
let possible_locations = ["../Zed.exe", "../lib/zed/zed-editor.exe", "./zed.exe"]; .join("Zed.exe")
possible_locations
.iter()
.find_map(|p| dir.join(p).canonicalize().ok().filter(|path| path != &cli))
.context(format!(
"could not find any of: {}",
possible_locations.join(", ")
))?
}; };
Ok(App(path)) Ok(App(path))

View File

@@ -418,8 +418,6 @@ impl Telemetry {
fn report_event(self: &Arc<Self>, event: Event) { fn report_event(self: &Arc<Self>, event: Event) {
let mut state = self.state.lock(); let mut state = self.state.lock();
// RUST_LOG=telemetry=trace to debug telemetry events
log::trace!(target: "telemetry", "{:?}", event);
if !state.settings.metrics { if !state.settings.metrics {
return; return;

View File

@@ -111,7 +111,7 @@ node_runtime.workspace = true
notifications = { workspace = true, features = ["test-support"] } notifications = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }
prompt_store.workspace = true prompt_library.workspace = true
recent_projects = { workspace = true } recent_projects = { workspace = true }
release_channel.workspace = true release_channel.workspace = true
remote = { workspace = true, features = ["test-support"] } remote = { workspace = true, features = ["test-support"] }

View File

@@ -229,6 +229,7 @@ impl Database {
} }
/// Creates a new channel message. /// Creates a new channel message.
#[allow(clippy::too_many_arguments)]
pub async fn create_channel_message( pub async fn create_channel_message(
&self, &self,
channel_id: ChannelId, channel_id: ChannelId,

View File

@@ -122,6 +122,7 @@ impl Database {
.await .await
} }
#[allow(clippy::too_many_arguments)]
pub async fn get_or_create_user_by_github_account_tx( pub async fn get_or_create_user_by_github_account_tx(
&self, &self,
github_login: &str, github_login: &str,

View File

@@ -289,6 +289,7 @@ impl LlmDatabase {
.await .await
} }
#[allow(clippy::too_many_arguments)]
pub async fn record_usage( pub async fn record_usage(
&self, &self,
user_id: UserId, user_id: UserId,
@@ -553,6 +554,7 @@ impl LlmDatabase {
.await .await
} }
#[allow(clippy::too_many_arguments)]
async fn update_usage_for_measure( async fn update_usage_for_measure(
&self, &self,
user_id: UserId, user_id: UserId,

View File

@@ -33,6 +33,7 @@ pub struct LlmTokenClaims {
const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60); const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60);
impl LlmTokenClaims { impl LlmTokenClaims {
#[allow(clippy::too_many_arguments)]
pub fn create( pub fn create(
user: &user::Model, user: &user::Model,
is_staff: bool, is_staff: bool,

View File

@@ -308,7 +308,7 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>) .add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>) .add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>) .add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)
.add_request_handler(forward_read_only_project_request::<proto::GitGetBranches>) .add_request_handler(forward_read_only_project_request::<proto::GitBranches>)
.add_request_handler(forward_read_only_project_request::<proto::OpenUnstagedDiff>) .add_request_handler(forward_read_only_project_request::<proto::OpenUnstagedDiff>)
.add_request_handler(forward_read_only_project_request::<proto::OpenUncommittedDiff>) .add_request_handler(forward_read_only_project_request::<proto::OpenUncommittedDiff>)
.add_request_handler( .add_request_handler(
@@ -328,7 +328,6 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::PrepareRename>) .add_request_handler(forward_mutating_project_request::<proto::PrepareRename>)
.add_request_handler(forward_mutating_project_request::<proto::PerformRename>) .add_request_handler(forward_mutating_project_request::<proto::PerformRename>)
.add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>) .add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>)
.add_request_handler(forward_mutating_project_request::<proto::ApplyCodeActionKind>)
.add_request_handler(forward_mutating_project_request::<proto::FormatBuffers>) .add_request_handler(forward_mutating_project_request::<proto::FormatBuffers>)
.add_request_handler(forward_mutating_project_request::<proto::CreateProjectEntry>) .add_request_handler(forward_mutating_project_request::<proto::CreateProjectEntry>)
.add_request_handler(forward_mutating_project_request::<proto::RenameProjectEntry>) .add_request_handler(forward_mutating_project_request::<proto::RenameProjectEntry>)
@@ -393,6 +392,9 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::OpenContext>) .add_request_handler(forward_mutating_project_request::<proto::OpenContext>)
.add_request_handler(forward_mutating_project_request::<proto::CreateContext>) .add_request_handler(forward_mutating_project_request::<proto::CreateContext>)
.add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>) .add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
.add_request_handler(forward_mutating_project_request::<proto::Push>)
.add_request_handler(forward_mutating_project_request::<proto::Pull>)
.add_request_handler(forward_mutating_project_request::<proto::Fetch>)
.add_request_handler(forward_mutating_project_request::<proto::Stage>) .add_request_handler(forward_mutating_project_request::<proto::Stage>)
.add_request_handler(forward_mutating_project_request::<proto::Unstage>) .add_request_handler(forward_mutating_project_request::<proto::Unstage>)
.add_request_handler(forward_mutating_project_request::<proto::Commit>) .add_request_handler(forward_mutating_project_request::<proto::Commit>)
@@ -402,10 +404,6 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>) .add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>) .add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>) .add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
.add_request_handler(forward_mutating_project_request::<proto::GitDiff>)
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>) .add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
.add_message_handler(update_context) .add_message_handler(update_context)
.add_request_handler({ .add_request_handler({
@@ -697,6 +695,7 @@ impl Server {
}) })
} }
#[allow(clippy::too_many_arguments)]
pub fn handle_connection( pub fn handle_connection(
self: &Arc<Self>, self: &Arc<Self>,
connection: Connection, connection: Connection,
@@ -1080,6 +1079,7 @@ pub fn routes(server: Arc<Server>) -> Router<(), Body> {
.layer(Extension(server)) .layer(Extension(server))
} }
#[allow(clippy::too_many_arguments)]
pub async fn handle_websocket_request( pub async fn handle_websocket_request(
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>, TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
app_version_header: Option<TypedHeader<AppVersionHeader>>, app_version_header: Option<TypedHeader<AppVersionHeader>>,

View File

@@ -3,6 +3,7 @@ use crate::{
tests::{rust_lang, TestServer}, tests::{rust_lang, TestServer},
}; };
use call::ActiveCall; use call::ActiveCall;
use collections::HashMap;
use editor::{ use editor::{
actions::{ actions::{
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename,
@@ -1982,6 +1983,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
blame_entry("3a3a3a", 2..3), blame_entry("3a3a3a", 2..3),
blame_entry("4c4c4c", 3..4), blame_entry("4c4c4c", 3..4),
], ],
permalinks: HashMap::default(), // This field is deprecrated
messages: [ messages: [
("1b1b1b", "message for idx-0"), ("1b1b1b", "message for idx-0"),
("0d0d0d", "message for idx-1"), ("0d0d0d", "message for idx-1"),
@@ -2025,15 +2027,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.unwrap() .unwrap()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
let buffer_id_b = editor_b.update(cx_b, |editor_b, cx| {
editor_b
.buffer()
.read(cx)
.as_singleton()
.unwrap()
.read(cx)
.remote_id()
});
// client_b now requests git blame for the open buffer // client_b now requests git blame for the open buffer
editor_b.update_in(cx_b, |editor_b, window, cx| { editor_b.update_in(cx_b, |editor_b, window, cx| {
@@ -2052,7 +2045,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
&(0..4) &(0..4)
.map(|row| RowInfo { .map(|row| RowInfo {
buffer_row: Some(row), buffer_row: Some(row),
buffer_id: Some(buffer_id_b),
..Default::default() ..Default::default()
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@@ -2100,7 +2092,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
&(0..4) &(0..4)
.map(|row| RowInfo { .map(|row| RowInfo {
buffer_row: Some(row), buffer_row: Some(row),
buffer_id: Some(buffer_id_b),
..Default::default() ..Default::default()
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@@ -2136,7 +2127,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
&(0..4) &(0..4)
.map(|row| RowInfo { .map(|row| RowInfo {
buffer_row: Some(row), buffer_row: Some(row),
buffer_id: Some(buffer_id_b),
..Default::default() ..Default::default()
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),

View File

@@ -14,7 +14,7 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::{FakeFs, Fs as _, RemoveOptions}; use fs::{FakeFs, Fs as _, RemoveOptions};
use futures::{channel::mpsc, StreamExt as _}; use futures::{channel::mpsc, StreamExt as _};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode}; use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
use gpui::{ use gpui::{
@@ -6741,24 +6741,19 @@ async fn test_remote_git_branches(
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await; let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await;
let project_id = active_call_a let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await .await
.unwrap(); .unwrap();
let project_b = client_b.join_remote_project(project_id, cx_b).await; let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Client A sees that a guest has joined and the repo has been populated let root_path = ProjectPath::root_path(worktree_id);
// Client A sees that a guest has joined.
executor.run_until_parked(); executor.run_until_parked();
let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
let root_path = ProjectPath::root_path(worktree_id);
let branches_b = cx_b let branches_b = cx_b
.update(|cx| repo_b.update(cx, |repository, _| repository.branches())) .update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
.await .await
.unwrap()
.unwrap(); .unwrap();
let new_branch = branches[2]; let new_branch = branches[2];
@@ -6770,10 +6765,13 @@ async fn test_remote_git_branches(
assert_eq!(branches_b, branches_set); assert_eq!(branches_b, branches_set);
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch)) cx_b.update(|cx| {
.await project_b.update(cx, |project, cx| {
.unwrap() project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
.unwrap(); })
})
.await
.unwrap();
executor.run_until_parked(); executor.run_until_parked();
@@ -6790,15 +6788,13 @@ async fn test_remote_git_branches(
assert_eq!(host_branch.name, branches[2]); assert_eq!(host_branch.name, branches[2]);
// Also try creating a new branch // Also try creating a new branch
cx_b.update(|cx| repo_b.read(cx).create_branch("totally-new-branch")) cx_b.update(|cx| {
.await project_b.update(cx, |project, cx| {
.unwrap() project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
.unwrap(); })
})
cx_b.update(|cx| repo_b.read(cx).change_branch("totally-new-branch")) .await
.await .unwrap();
.unwrap()
.unwrap();
executor.run_until_parked(); executor.run_until_parked();

View File

@@ -463,6 +463,7 @@ impl<T: RandomizedTest> TestPlan<T> {
}) })
} }
#[allow(clippy::too_many_arguments)]
async fn apply_server_operation( async fn apply_server_operation(
plan: Arc<Mutex<Self>>, plan: Arc<Mutex<Self>>,
deterministic: BackgroundExecutor, deterministic: BackgroundExecutor,

View File

@@ -276,13 +276,11 @@ async fn test_ssh_collaboration_git_branches(
// has some git repositories // has some git repositories
executor.run_until_parked(); executor.run_until_parked();
let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
let root_path = ProjectPath::root_path(worktree_id); let root_path = ProjectPath::root_path(worktree_id);
let branches_b = cx_b let branches_b = cx_b
.update(|cx| repo_b.read(cx).branches()) .update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx)))
.await .await
.unwrap()
.unwrap(); .unwrap();
let new_branch = branches[2]; let new_branch = branches[2];
@@ -294,10 +292,13 @@ async fn test_ssh_collaboration_git_branches(
assert_eq!(&branches_b, &branches_set); assert_eq!(&branches_b, &branches_set);
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch)) cx_b.update(|cx| {
.await project_b.update(cx, |project, cx| {
.unwrap() project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx)
.unwrap(); })
})
.await
.unwrap();
executor.run_until_parked(); executor.run_until_parked();
@@ -316,15 +317,13 @@ async fn test_ssh_collaboration_git_branches(
assert_eq!(server_branch.name, branches[2]); assert_eq!(server_branch.name, branches[2]);
// Also try creating a new branch // Also try creating a new branch
cx_b.update(|cx| repo_b.read(cx).create_branch("totally-new-branch")) cx_b.update(|cx| {
.await project_b.update(cx, |project, cx| {
.unwrap() project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx)
.unwrap(); })
})
cx_b.update(|cx| repo_b.read(cx).change_branch("totally-new-branch")) .await
.await .unwrap();
.unwrap()
.unwrap();
executor.run_until_parked(); executor.run_until_parked();

View File

@@ -323,7 +323,7 @@ impl ChatPanel {
.my_0p5() .my_0p5()
.px_0p5() .px_0p5()
.gap_x_1() .gap_x_1()
.rounded_sm() .rounded_md()
.child(Icon::new(IconName::ReplyArrowRight).color(Color::Muted)) .child(Icon::new(IconName::ReplyArrowRight).color(Color::Muted))
.when(reply_to_message.is_none(), |el| { .when(reply_to_message.is_none(), |el| {
el.child( el.child(
@@ -358,7 +358,7 @@ impl ChatPanel {
.my_0p5() .my_0p5()
.px_0p5() .px_0p5()
.gap_x_1() .gap_x_1()
.rounded_sm() .rounded_md()
.overflow_hidden() .overflow_hidden()
.hover(|style| style.bg(cx.theme().colors().element_background)) .hover(|style| style.bg(cx.theme().colors().element_background))
.child(Icon::new(IconName::ReplyArrowRight).color(Color::Muted)) .child(Icon::new(IconName::ReplyArrowRight).color(Color::Muted))
@@ -476,7 +476,7 @@ impl ChatPanel {
div() div()
.group("") .group("")
.bg(background) .bg(background)
.rounded_sm() .rounded_md()
.overflow_hidden() .overflow_hidden()
.px_1p5() .px_1p5()
.py_0p5() .py_0p5()
@@ -563,7 +563,7 @@ impl ChatPanel {
.child( .child(
div() div()
.px_1() .px_1()
.rounded_sm() .rounded_md()
.text_ui_xs(cx) .text_ui_xs(cx)
.bg(cx.theme().colors().background) .bg(cx.theme().colors().background)
.child("New messages"), .child("New messages"),
@@ -589,7 +589,7 @@ impl ChatPanel {
div() div()
.w_6() .w_6()
.bg(cx.theme().colors().element_background) .bg(cx.theme().colors().element_background)
.hover(|style| style.bg(cx.theme().colors().element_hover).rounded_sm()) .hover(|style| style.bg(cx.theme().colors().element_hover).rounded_md())
.child(child) .child(child)
} }
@@ -604,7 +604,7 @@ impl ChatPanel {
.absolute() .absolute()
.right_2() .right_2()
.overflow_hidden() .overflow_hidden()
.rounded_sm() .rounded_md()
.border_color(cx.theme().colors().element_selected) .border_color(cx.theme().colors().element_selected)
.border_1() .border_1()
.when(!self.has_open_menu(message_id), |el| { .when(!self.has_open_menu(message_id), |el| {

View File

@@ -10,9 +10,9 @@ use gpui::{
}; };
use language::{ use language::{
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
ToOffset, LanguageServerId, ToOffset,
}; };
use project::{search::SearchQuery, Completion, CompletionSource}; use project::{search::SearchQuery, Completion};
use settings::Settings; use settings::Settings;
use std::{ use std::{
cell::RefCell, cell::RefCell,
@@ -309,9 +309,11 @@ impl MessageEditor {
old_range: range.clone(), old_range: range.clone(),
new_text, new_text,
label, label,
confirm: None,
documentation: None, documentation: None,
source: CompletionSource::Custom, server_id: LanguageServerId(0), // TODO: Make this optional or something?
lsp_completion: Default::default(), // TODO: Make this optional or something?
confirm: None,
resolved: true,
} }
}) })
.collect() .collect()
@@ -529,7 +531,7 @@ impl Render for MessageEditor {
.px_2() .px_2()
.py_1() .py_1()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.rounded_sm() .rounded_md()
.child(EditorElement::new( .child(EditorElement::new(
&self.editor, &self.editor,
EditorStyle { EditorStyle {

View File

@@ -17,7 +17,7 @@ use gpui::{
ListState, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString, ListState, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString,
Styled, Subscription, Task, TextStyle, WeakEntity, Window, Styled, Subscription, Task, TextStyle, WeakEntity, Window,
}; };
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrevious}; use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
use project::{Fs, Project}; use project::{Fs, Project};
use rpc::{ use rpc::{
proto::{self, ChannelVisibility, PeerId}, proto::{self, ChannelVisibility, PeerId},
@@ -869,6 +869,7 @@ impl CollabPanel {
}) })
} }
#[allow(clippy::too_many_arguments)]
fn render_participant_project( fn render_participant_project(
&self, &self,
project_id: u64, project_id: u64,
@@ -1429,7 +1430,7 @@ impl CollabPanel {
cx.notify(); cx.notify();
} }
fn select_previous(&mut self, _: &SelectPrevious, _: &mut Window, cx: &mut Context<Self>) { fn select_prev(&mut self, _: &SelectPrev, _: &mut Window, cx: &mut Context<Self>) {
let ix = self.selection.take().unwrap_or(0); let ix = self.selection.take().unwrap_or(0);
if ix > 0 { if ix > 0 {
self.selection = Some(ix - 1); self.selection = Some(ix - 1);
@@ -2877,7 +2878,7 @@ impl Render for CollabPanel {
.key_context("CollabPanel") .key_context("CollabPanel")
.on_action(cx.listener(CollabPanel::cancel)) .on_action(cx.listener(CollabPanel::cancel))
.on_action(cx.listener(CollabPanel::select_next)) .on_action(cx.listener(CollabPanel::select_next))
.on_action(cx.listener(CollabPanel::select_previous)) .on_action(cx.listener(CollabPanel::select_prev))
.on_action(cx.listener(CollabPanel::confirm)) .on_action(cx.listener(CollabPanel::confirm))
.on_action(cx.listener(CollabPanel::insert_space)) .on_action(cx.listener(CollabPanel::insert_space))
.on_action(cx.listener(CollabPanel::remove_selected_channel)) .on_action(cx.listener(CollabPanel::remove_selected_channel))

View File

@@ -22,7 +22,7 @@ use ui::{
h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip,
}; };
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::notifications::{Notification as WorkspaceNotification, NotificationId}; use workspace::notifications::NotificationId;
use workspace::{ use workspace::{
dock::{DockPosition, Panel, PanelEvent}, dock::{DockPosition, Panel, PanelEvent},
Workspace, Workspace,
@@ -300,7 +300,7 @@ impl NotificationPanel {
.hover(|style| { .hover(|style| {
style style
.bg(cx.theme().colors().element_selected) .bg(cx.theme().colors().element_selected)
.rounded_sm() .rounded_md()
}) })
.child(Label::new(relative_timestamp).color(Color::Muted)) .child(Label::new(relative_timestamp).color(Color::Muted))
.tooltip(move |_, cx| { .tooltip(move |_, cx| {
@@ -570,12 +570,11 @@ impl NotificationPanel {
workspace.dismiss_notification(&id, cx); workspace.dismiss_notification(&id, cx);
workspace.show_notification(id, cx, |cx| { workspace.show_notification(id, cx, |cx| {
let workspace = cx.entity().downgrade(); let workspace = cx.entity().downgrade();
cx.new(|cx| NotificationToast { cx.new(|_| NotificationToast {
notification_id, notification_id,
actor, actor,
text, text,
workspace, workspace,
focus_handle: cx.focus_handle(),
}) })
}) })
}) })
@@ -772,17 +771,8 @@ pub struct NotificationToast {
actor: Option<Arc<User>>, actor: Option<Arc<User>>,
text: String, text: String,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
} }
impl Focusable for NotificationToast {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl WorkspaceNotification for NotificationToast {}
impl NotificationToast { impl NotificationToast {
fn focus_notification_panel(&self, window: &mut Window, cx: &mut Context<Self>) { fn focus_notification_panel(&self, window: &mut Window, cx: &mut Context<Self>) {
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();

View File

@@ -1,4 +1,3 @@
use std::fmt::Display;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -9,7 +8,7 @@ use parking_lot::RwLock;
use theme::ActiveTheme; use theme::ActiveTheme;
pub trait Component { pub trait Component {
fn scope() -> Option<ComponentScope>; fn scope() -> Option<&'static str>;
fn name() -> &'static str { fn name() -> &'static str {
std::any::type_name::<Self>() std::any::type_name::<Self>()
} }
@@ -19,7 +18,7 @@ pub trait Component {
} }
pub trait ComponentPreview: Component { pub trait ComponentPreview: Component {
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement; fn preview(_window: &mut Window, _cx: &App) -> AnyElement;
} }
#[distributed_slice] #[distributed_slice]
@@ -32,8 +31,8 @@ pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
LazyLock::new(|| RwLock::new(ComponentRegistry::new())); LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
pub struct ComponentRegistry { pub struct ComponentRegistry {
components: Vec<(Option<ComponentScope>, &'static str, Option<&'static str>)>, components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>, previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>,
} }
impl ComponentRegistry { impl ComponentRegistry {
@@ -63,10 +62,7 @@ pub fn register_component<T: Component>() {
} }
pub fn register_preview<T: ComponentPreview>() { pub fn register_preview<T: ComponentPreview>() {
let preview_data = ( let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement);
T::name(),
T::preview as fn(&mut Window, &mut App) -> AnyElement,
);
COMPONENT_DATA COMPONENT_DATA
.write() .write()
.previews .previews
@@ -78,23 +74,18 @@ pub struct ComponentId(pub &'static str);
#[derive(Clone)] #[derive(Clone)]
pub struct ComponentMetadata { pub struct ComponentMetadata {
id: ComponentId,
name: SharedString, name: SharedString,
scope: Option<ComponentScope>, scope: Option<SharedString>,
description: Option<SharedString>, description: Option<SharedString>,
preview: Option<fn(&mut Window, &mut App) -> AnyElement>, preview: Option<fn(&mut Window, &App) -> AnyElement>,
} }
impl ComponentMetadata { impl ComponentMetadata {
pub fn id(&self) -> ComponentId {
self.id.clone()
}
pub fn name(&self) -> SharedString { pub fn name(&self) -> SharedString {
self.name.clone() self.name.clone()
} }
pub fn scope(&self) -> Option<ComponentScope> { pub fn scope(&self) -> Option<SharedString> {
self.scope.clone() self.scope.clone()
} }
@@ -102,7 +93,7 @@ impl ComponentMetadata {
self.description.clone() self.description.clone()
} }
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> AnyElement> { pub fn preview(&self) -> Option<fn(&mut Window, &App) -> AnyElement> {
self.preview self.preview
} }
} }
@@ -158,16 +149,14 @@ pub fn components() -> AllComponents {
let data = COMPONENT_DATA.read(); let data = COMPONENT_DATA.read();
let mut all_components = AllComponents::new(); let mut all_components = AllComponents::new();
for (ref scope, name, description) in &data.components { for &(scope, name, description) in &data.components {
let scope = scope.map(Into::into);
let preview = data.previews.get(name).cloned(); let preview = data.previews.get(name).cloned();
let component_name = SharedString::new_static(name);
let id = ComponentId(name);
all_components.insert( all_components.insert(
id.clone(), ComponentId(name),
ComponentMetadata { ComponentMetadata {
id, name: name.into(),
name: component_name, scope,
scope: scope.clone(),
description: description.map(Into::into), description: description.map(Into::into),
preview, preview,
}, },
@@ -177,59 +166,6 @@ pub fn components() -> AllComponents {
all_components all_components
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ComponentScope {
Layout,
Input,
Notification,
Editor,
Collaboration,
VersionControl,
Unknown(SharedString),
}
impl Display for ComponentScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ComponentScope::Layout => write!(f, "Layout"),
ComponentScope::Input => write!(f, "Input"),
ComponentScope::Notification => write!(f, "Notification"),
ComponentScope::Editor => write!(f, "Editor"),
ComponentScope::Collaboration => write!(f, "Collaboration"),
ComponentScope::VersionControl => write!(f, "Version Control"),
ComponentScope::Unknown(name) => write!(f, "Unknown: {}", name),
}
}
}
impl From<&str> for ComponentScope {
fn from(value: &str) -> Self {
match value {
"Layout" => ComponentScope::Layout,
"Input" => ComponentScope::Input,
"Notification" => ComponentScope::Notification,
"Editor" => ComponentScope::Editor,
"Collaboration" => ComponentScope::Collaboration,
"Version Control" | "VersionControl" => ComponentScope::VersionControl,
_ => ComponentScope::Unknown(SharedString::new(value)),
}
}
}
impl From<String> for ComponentScope {
fn from(value: String) -> Self {
match value.as_str() {
"Layout" => ComponentScope::Layout,
"Input" => ComponentScope::Input,
"Notification" => ComponentScope::Notification,
"Editor" => ComponentScope::Editor,
"Collaboration" => ComponentScope::Collaboration,
"Version Control" | "VersionControl" => ComponentScope::VersionControl,
_ => ComponentScope::Unknown(SharedString::new(value)),
}
}
}
/// Which side of the preview to show labels on /// Which side of the preview to show labels on
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExampleLabelSide { pub enum ExampleLabelSide {
@@ -238,8 +174,8 @@ pub enum ExampleLabelSide {
/// Right side /// Right side
Right, Right,
/// Top side /// Top side
#[default]
Top, Top,
#[default]
/// Bottom side /// Bottom side
Bottom, Bottom,
} }
@@ -269,7 +205,6 @@ impl RenderOnce for ComponentExample {
.text_size(px(10.)) .text_size(px(10.))
.text_color(cx.theme().colors().text_muted) .text_color(cx.theme().colors().text_muted)
.when(self.grow, |this| this.flex_1()) .when(self.grow, |this| this.flex_1())
.when(!self.grow, |this| this.flex_none())
.child(self.element) .child(self.element)
.child(self.variant_name) .child(self.variant_name)
.into_any_element() .into_any_element()
@@ -300,7 +235,6 @@ pub struct ComponentExampleGroup {
pub title: Option<SharedString>, pub title: Option<SharedString>,
pub examples: Vec<ComponentExample>, pub examples: Vec<ComponentExample>,
pub grow: bool, pub grow: bool,
pub vertical: bool,
} }
impl RenderOnce for ComponentExampleGroup { impl RenderOnce for ComponentExampleGroup {
@@ -336,7 +270,6 @@ impl RenderOnce for ComponentExampleGroup {
.child( .child(
div() div()
.flex() .flex()
.when(self.vertical, |this| this.flex_col())
.items_start() .items_start()
.w_full() .w_full()
.gap_6() .gap_6()
@@ -354,7 +287,6 @@ impl ComponentExampleGroup {
title: None, title: None,
examples, examples,
grow: false, grow: false,
vertical: false,
} }
} }
@@ -364,7 +296,6 @@ impl ComponentExampleGroup {
title: Some(title.into()), title: Some(title.into()),
examples, examples,
grow: false, grow: false,
vertical: false,
} }
} }
@@ -373,12 +304,6 @@ impl ComponentExampleGroup {
self.grow = true; self.grow = true;
self self
} }
/// Lay the group out vertically.
pub fn vertical(mut self) -> Self {
self.vertical = true;
self
}
} }
/// Create a single example /// Create a single example

View File

@@ -15,12 +15,7 @@ path = "src/component_preview.rs"
default = [] default = []
[dependencies] [dependencies]
client.workspace = true
component.workspace = true component.workspace = true
gpui.workspace = true gpui.workspace = true
languages.workspace = true
project.workspace = true
ui.workspace = true ui.workspace = true
workspace.workspace = true workspace.workspace = true
notifications.workspace = true
collections.workspace = true

View File

@@ -2,51 +2,18 @@
//! //!
//! A view for exploring Zed components. //! A view for exploring Zed components.
use std::iter::Iterator; use component::{components, ComponentMetadata};
use std::sync::Arc; use gpui::{list, prelude::*, uniform_list, App, EventEmitter, FocusHandle, Focusable, Window};
use client::UserStore;
use component::{components, ComponentId, ComponentMetadata};
use gpui::{
list, prelude::*, uniform_list, App, Entity, EventEmitter, FocusHandle, Focusable, Task,
WeakEntity, Window,
};
use collections::HashMap;
use gpui::{ListState, ScrollHandle, UniformListScrollHandle}; use gpui::{ListState, ScrollHandle, UniformListScrollHandle};
use languages::LanguageRegistry; use ui::{prelude::*, ListItem};
use notifications::status_toast::{StatusToast, ToastIcon};
use project::Project;
use ui::{prelude::*, Divider, ListItem, ListSubHeader};
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId}; use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
use workspace::{AppState, ItemId, SerializableItem};
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
let app_state = app_state.clone();
cx.observe_new(move |workspace: &mut Workspace, _, cx| {
let app_state = app_state.clone();
let weak_workspace = cx.entity().downgrade();
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
workspace.register_action( workspace.register_action(
move |workspace, _: &workspace::OpenComponentPreview, window, cx| { |workspace, _: &workspace::OpenComponentPreview, window, cx| {
let app_state = app_state.clone(); let component_preview = cx.new(|cx| ComponentPreview::new(window, cx));
let language_registry = app_state.languages.clone();
let user_store = app_state.user_store.clone();
let component_preview = cx.new(|cx| {
ComponentPreview::new(
weak_workspace.clone(),
language_registry,
user_store,
None,
cx,
)
});
workspace.add_item_to_active_pane( workspace.add_item_to_active_pane(
Box::new(component_preview), Box::new(component_preview),
None, None,
@@ -60,105 +27,43 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
.detach(); .detach();
} }
enum PreviewEntry {
AllComponents,
Separator,
Component(ComponentMetadata),
SectionHeader(SharedString),
}
impl From<ComponentMetadata> for PreviewEntry {
fn from(component: ComponentMetadata) -> Self {
PreviewEntry::Component(component)
}
}
impl From<SharedString> for PreviewEntry {
fn from(section_header: SharedString) -> Self {
PreviewEntry::SectionHeader(section_header)
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
enum PreviewPage {
#[default]
AllComponents,
Component(ComponentId),
}
struct ComponentPreview { struct ComponentPreview {
focus_handle: FocusHandle, focus_handle: FocusHandle,
_view_scroll_handle: ScrollHandle, _view_scroll_handle: ScrollHandle,
nav_scroll_handle: UniformListScrollHandle, nav_scroll_handle: UniformListScrollHandle,
component_map: HashMap<ComponentId, ComponentMetadata>,
active_page: PreviewPage,
components: Vec<ComponentMetadata>, components: Vec<ComponentMetadata>,
component_list: ListState, component_list: ListState,
cursor_index: usize, selected_index: usize,
language_registry: Arc<LanguageRegistry>,
workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>,
} }
impl ComponentPreview { impl ComponentPreview {
pub fn new( pub fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
workspace: WeakEntity<Workspace>, let components = components().all_sorted();
language_registry: Arc<LanguageRegistry>, let initial_length = components.len();
user_store: Entity<UserStore>,
selected_index: impl Into<Option<usize>>,
cx: &mut Context<Self>,
) -> Self {
let sorted_components = components().all_sorted();
let selected_index = selected_index.into().unwrap_or(0);
let component_list = ListState::new( let component_list = ListState::new(initial_length, gpui::ListAlignment::Top, px(500.0), {
sorted_components.len(), let this = cx.entity().downgrade();
gpui::ListAlignment::Top, move |ix, window: &mut Window, cx: &mut App| {
px(1500.0), this.update(cx, |this, cx| {
{ this.render_preview(ix, window, cx).into_any_element()
let this = cx.entity().downgrade(); })
move |ix, window: &mut Window, cx: &mut App| { .unwrap()
this.update(cx, |this, cx| { }
let component = this.get_component(ix); });
this.render_preview(&component, window, cx)
.into_any_element()
})
.unwrap()
}
},
);
let mut component_preview = Self { Self {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
_view_scroll_handle: ScrollHandle::new(), _view_scroll_handle: ScrollHandle::new(),
nav_scroll_handle: UniformListScrollHandle::new(), nav_scroll_handle: UniformListScrollHandle::new(),
language_registry, components,
user_store,
workspace,
active_page: PreviewPage::AllComponents,
component_map: components().0,
components: sorted_components,
component_list, component_list,
cursor_index: selected_index, selected_index: 0,
};
if component_preview.cursor_index > 0 {
component_preview.scroll_to_preview(component_preview.cursor_index, cx);
} }
component_preview.update_component_list(cx);
component_preview
} }
fn scroll_to_preview(&mut self, ix: usize, cx: &mut Context<Self>) { fn scroll_to_preview(&mut self, ix: usize, cx: &mut Context<Self>) {
self.component_list.scroll_to_reveal_item(ix); self.component_list.scroll_to_reveal_item(ix);
self.cursor_index = ix; self.selected_index = ix;
cx.notify();
}
fn set_active_page(&mut self, page: PreviewPage, cx: &mut Context<Self>) {
self.active_page = page;
cx.notify(); cx.notify();
} }
@@ -166,173 +71,32 @@ impl ComponentPreview {
self.components[ix].clone() self.components[ix].clone()
} }
fn scope_ordered_entries(&self) -> Vec<PreviewEntry> {
use std::collections::HashMap;
let mut scope_groups: HashMap<Option<ComponentScope>, Vec<ComponentMetadata>> =
HashMap::default();
for component in &self.components {
scope_groups
.entry(component.scope())
.or_insert_with(Vec::new)
.push(component.clone());
}
for components in scope_groups.values_mut() {
components.sort_by_key(|c| c.name().to_lowercase());
}
let mut entries = Vec::new();
let known_scopes = [
ComponentScope::Layout,
ComponentScope::Input,
ComponentScope::Editor,
ComponentScope::Notification,
ComponentScope::Collaboration,
ComponentScope::VersionControl,
];
// Always show all components first
entries.push(PreviewEntry::AllComponents);
entries.push(PreviewEntry::Separator);
for scope in known_scopes.iter() {
let scope_key = Some(scope.clone());
if let Some(components) = scope_groups.remove(&scope_key) {
if !components.is_empty() {
entries.push(PreviewEntry::SectionHeader(scope.to_string().into()));
for component in components {
entries.push(PreviewEntry::Component(component));
}
}
}
}
for (scope, components) in &scope_groups {
if let Some(ComponentScope::Unknown(_)) = scope {
if !components.is_empty() {
if let Some(scope_value) = scope {
entries.push(PreviewEntry::SectionHeader(scope_value.to_string().into()));
}
for component in components {
entries.push(PreviewEntry::Component(component.clone()));
}
}
}
}
if let Some(components) = scope_groups.get(&None) {
if !components.is_empty() {
entries.push(PreviewEntry::Separator);
entries.push(PreviewEntry::SectionHeader("Uncategorized".into()));
for component in components {
entries.push(PreviewEntry::Component(component.clone()));
}
}
}
entries
}
fn render_sidebar_entry( fn render_sidebar_entry(
&self, &self,
ix: usize, ix: usize,
entry: &PreviewEntry, selected: bool,
cx: &Context<Self>, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
match entry { let component = self.get_component(ix);
PreviewEntry::Component(component_metadata) => {
let id = component_metadata.id();
let selected = self.active_page == PreviewPage::Component(id.clone());
ListItem::new(ix)
.child(Label::new(component_metadata.name().clone()).color(Color::Default))
.selectable(true)
.toggle_state(selected)
.inset(true)
.on_click(cx.listener(move |this, _, _, cx| {
let id = id.clone();
this.set_active_page(PreviewPage::Component(id), cx);
}))
.into_any_element()
}
PreviewEntry::SectionHeader(shared_string) => ListSubHeader::new(shared_string)
.inset(true)
.into_any_element(),
PreviewEntry::AllComponents => {
let selected = self.active_page == PreviewPage::AllComponents;
ListItem::new(ix) ListItem::new(ix)
.child(Label::new("All Components").color(Color::Default)) .child(Label::new(component.name().clone()).color(Color::Default))
.selectable(true) .selectable(true)
.toggle_state(selected) .toggle_state(selected)
.inset(true) .inset(true)
.on_click(cx.listener(move |this, _, _, cx| { .on_click(cx.listener(move |this, _, _, cx| {
this.set_active_page(PreviewPage::AllComponents, cx); this.scroll_to_preview(ix, cx);
})) }))
.into_any_element()
}
PreviewEntry::Separator => ListItem::new(ix)
.child(h_flex().pt_3().child(Divider::horizontal_dashed()))
.into_any_element(),
}
}
fn update_component_list(&mut self, cx: &mut Context<Self>) {
let new_len = self.scope_ordered_entries().len();
let entries = self.scope_ordered_entries();
let weak_entity = cx.entity().downgrade();
let new_list = ListState::new(
new_len,
gpui::ListAlignment::Top,
px(1500.0),
move |ix, window, cx| {
let entry = &entries[ix];
weak_entity
.update(cx, |this, cx| match entry {
PreviewEntry::Component(component) => this
.render_preview(component, window, cx)
.into_any_element(),
PreviewEntry::SectionHeader(shared_string) => this
.render_scope_header(ix, shared_string.clone(), window, cx)
.into_any_element(),
PreviewEntry::AllComponents => div().w_full().h_0().into_any_element(),
PreviewEntry::Separator => div().w_full().h_0().into_any_element(),
})
.unwrap()
},
);
self.component_list = new_list;
}
fn render_scope_header(
&self,
_ix: usize,
title: SharedString,
_window: &Window,
_cx: &App,
) -> impl IntoElement {
h_flex()
.w_full()
.h_10()
.items_center()
.child(Headline::new(title).size(HeadlineSize::XSmall))
.child(Divider::horizontal())
} }
fn render_preview( fn render_preview(
&self, &self,
component: &ComponentMetadata, ix: usize,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let component = self.get_component(ix);
let name = component.name(); let name = component.name();
let scope = component.scope(); let scope = component.scope();
@@ -344,7 +108,7 @@ impl ComponentPreview {
v_flex() v_flex()
.border_1() .border_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.rounded_sm() .rounded_md()
.w_full() .w_full()
.gap_4() .gap_4()
.py_4() .py_4()
@@ -378,71 +142,10 @@ impl ComponentPreview {
) )
.into_any_element() .into_any_element()
} }
fn render_all_components(&self) -> impl IntoElement {
v_flex()
.id("component-list")
.px_8()
.pt_4()
.size_full()
.child(
list(self.component_list.clone())
.flex_grow()
.with_sizing_behavior(gpui::ListSizingBehavior::Auto),
)
}
fn render_component_page(
&mut self,
component_id: &ComponentId,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let component = self.component_map.get(&component_id);
if let Some(component) = component {
v_flex()
.w_full()
.flex_initial()
.min_h_full()
.child(self.render_preview(component, window, cx))
.into_any_element()
} else {
v_flex()
.size_full()
.items_center()
.justify_center()
.child("Component not found")
.into_any_element()
}
}
fn test_status_toast(&self, window: &mut Window, cx: &mut Context<Self>) {
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
let status_toast = StatusToast::new(
"`zed/new-notification-system` created!",
window,
cx,
|this, _, cx| {
this.icon(ToastIcon::new(IconName::GitBranchSmall).color(Color::Muted))
.action(
"Open Pull Request",
cx.listener(|_, _, _, cx| cx.open_url("https://github.com/")),
)
},
);
workspace.toggle_status_toast(window, cx, status_toast)
});
}
}
} }
impl Render for ComponentPreview { impl Render for ComponentPreview {
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 sidebar_entries = self.scope_ordered_entries();
let active_page = self.active_page.clone();
h_flex() h_flex()
.id("component-preview") .id("component-preview")
.key_context("ComponentPreview") .key_context("ComponentPreview")
@@ -452,47 +155,35 @@ impl Render for ComponentPreview {
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.px_2() .px_2()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child(
uniform_list(
cx.entity().clone(),
"component-nav",
self.components.len(),
move |this, range, _window, cx| {
range
.map(|ix| this.render_sidebar_entry(ix, ix == this.selected_index, cx))
.collect()
},
)
.track_scroll(self.nav_scroll_handle.clone())
.pt_4()
.w(px(240.))
.h_full()
.flex_grow(),
)
.child( .child(
v_flex() v_flex()
.h_full() .id("component-list")
.px_8()
.pt_4()
.size_full()
.child( .child(
uniform_list( list(self.component_list.clone())
cx.entity().clone(), .flex_grow()
"component-nav", .with_sizing_behavior(gpui::ListSizingBehavior::Auto),
sidebar_entries.len(),
move |this, range, _window, cx| {
range
.map(|ix| {
this.render_sidebar_entry(ix, &sidebar_entries[ix], cx)
})
.collect()
},
)
.track_scroll(self.nav_scroll_handle.clone())
.pt_4()
.w(px(240.))
.h_full()
.flex_1(),
)
.child(
div().w_full().pb_4().child(
Button::new("toast-test", "Launch Toast")
.on_click(cx.listener({
move |this, _, window, cx| {
this.test_status_toast(window, cx);
cx.notify();
}
}))
.full_width(),
),
), ),
) )
.child(match active_page {
PreviewPage::AllComponents => self.render_all_components().into_any_element(),
PreviewPage::Component(id) => self
.render_component_page(&id, window, cx)
.into_any_element(),
})
} }
} }
@@ -522,86 +213,16 @@ impl Item for ComponentPreview {
fn clone_on_split( fn clone_on_split(
&self, &self,
_workspace_id: Option<WorkspaceId>, _workspace_id: Option<WorkspaceId>,
_window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<gpui::Entity<Self>> ) -> Option<gpui::Entity<Self>>
where where
Self: Sized, Self: Sized,
{ {
let language_registry = self.language_registry.clone(); Some(cx.new(|cx| Self::new(window, cx)))
let user_store = self.user_store.clone();
let weak_workspace = self.workspace.clone();
let selected_index = self.cursor_index;
Some(cx.new(|cx| {
Self::new(
weak_workspace,
language_registry,
user_store,
selected_index,
cx,
)
}))
} }
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) { fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
f(*event) f(*event)
} }
} }
impl SerializableItem for ComponentPreview {
fn serialized_item_kind() -> &'static str {
"ComponentPreview"
}
fn deserialize(
project: Entity<Project>,
workspace: WeakEntity<Workspace>,
_workspace_id: WorkspaceId,
_item_id: ItemId,
window: &mut Window,
cx: &mut App,
) -> Task<gpui::Result<Entity<Self>>> {
let user_store = project.read(cx).user_store().clone();
let language_registry = project.read(cx).languages().clone();
window.spawn(cx, |mut cx| async move {
let user_store = user_store.clone();
let language_registry = language_registry.clone();
let weak_workspace = workspace.clone();
cx.update(|_, cx| {
Ok(cx.new(|cx| {
ComponentPreview::new(weak_workspace, language_registry, user_store, None, cx)
}))
})?
})
}
fn cleanup(
_workspace_id: WorkspaceId,
_alive_items: Vec<ItemId>,
_window: &mut Window,
_cx: &mut App,
) -> Task<gpui::Result<()>> {
Task::ready(Ok(()))
// window.spawn(cx, |_| {
// ...
// })
}
fn serialize(
&mut self,
_workspace: &mut Workspace,
_item_id: ItemId,
_closing: bool,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Task<gpui::Result<()>>> {
// TODO: Serialize the active index so we can re-open to the same place
None
}
fn should_serialize(&self, _event: &Self::Event) -> bool {
false
}
}

View File

@@ -31,3 +31,4 @@ settings.workspace = true
smol.workspace = true smol.workspace = true
url = { workspace = true, features = ["serde"] } url = { workspace = true, features = ["serde"] }
util.workspace = true util.workspace = true
workspace.workspace = true

View File

@@ -1,9 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail};
use assistant_tool::Tool; use assistant_tool::Tool;
use gpui::{App, Entity, Task}; use gpui::{App, Entity, Task, Window};
use project::Project;
use crate::manager::ContextServerManager; use crate::manager::ContextServerManager;
use crate::types; use crate::types;
@@ -50,11 +49,12 @@ impl Tool for ContextServerTool {
} }
fn run( fn run(
self: Arc<Self>, self: std::sync::Arc<Self>,
input: serde_json::Value, input: serde_json::Value,
_project: Entity<Project>, _workspace: gpui::WeakEntity<workspace::Workspace>,
_: &mut Window,
cx: &mut App, cx: &mut App,
) -> Task<Result<String>> { ) -> gpui::Task<gpui::Result<String>> {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) { if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
cx.foreground_executor().spawn({ cx.foreground_executor().spawn({
let tool_name = self.tool.name.clone(); let tool_name = self.tool.name.clone();

Some files were not shown because too many files have changed in this diff Show More