Compare commits

..

1 Commits

Author SHA1 Message Date
Mikayla
76e4d759de WIP: Add 'open single file diff when clicking on status entry' to git panel 2025-01-24 17:09:42 -08:00
961 changed files with 42807 additions and 66036 deletions

View File

@@ -0,0 +1,34 @@
name: Feature Request
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
labels: ["admin read", "triage", "enhancement"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the feature
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
placeholder: |
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `request feature` and this field will be autopopulated -->
validations:
required: true
- type: textarea
attributes:
label: |
If applicable, add mockups / screenshots to help present your vision of the feature
description: Drag images into the text input below
validations:
required: false

View File

@@ -1,34 +1,54 @@
name: Bug Report
description: |
Something is broken in Zed (exclude crashing).
type: "Bug"
Use this template for **non-crash-related** bug reports.
Tip: open this issue template from within Zed with the `file bug report` command palette action.
labels: ["admin read", "triage", "bug"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- 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:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
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"'
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `file bug report` and this field will be autopopulated -->
<!-- If Zed won't launch, include the equivalent with other relevant details (e.g. video card driver version for display bugs, etc) -->
<!-- Zed Version: 0.xxx.x; Channel: Stable, OS: macOS xx.xx, RAM: XXGB, Architecture: x86_64"
validations:
required: true
- type: textarea
attributes:
label: If applicable, add screenshots or screencasts of the incorrect state / behavior
description: Drag images / videos into the text input below
validations:
required: false
- type: textarea
attributes:
label: If applicable, attach your 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

@@ -1,33 +1,26 @@
name: Crash Report
description: Zed is Crashing or Hanging
type: "Crash"
description: |
Use this template for crash reports.
labels: ["admin read", "triage", "bug", "panic / crash"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- 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:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
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"
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
validations:
required: true
- type: textarea

View File

@@ -1,12 +1,18 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
blank_issues_enabled: false
contact_links:
- name: Feature Request
url: https://github.com/zed-industries/zed/discussions/new/choose
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
- name: Zed Discussion Forum
url: https://github.com/zed-industries/zed/discussions
about: A community discussion forum
- name: "Zed Discord: #Support Channel"
url: https://zed.dev/community-links
about: Real-time discussion and user support
- name: Language Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
about: Request a language in the extensions repository
- name: Theme Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme
about: Request a theme in the extensions repository
- name: Top-Ranking Issues
url: https://github.com/zed-industries/zed/issues/5393
about: See an overview of the most popular Zed issues
- name: Platform Support
url: https://github.com/zed-industries/zed/issues/5391
about: A quick note on platform support
- name: Positive Feedback
url: https://github.com/zed-industries/zed/discussions/5397
about: A central location for kind words about Zed

View File

@@ -10,7 +10,7 @@ runs:
cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

View File

@@ -1,26 +0,0 @@
name: "Run tests on Windows"
description: "Runs the tests on Windows"
inputs:
working-directory:
description: "The working directory"
required: true
default: "."
runs:
using: "composite"
steps:
- name: Install Rust
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: cargo install cargo-nextest --locked
- name: Install Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
with:
node-version: "18"
- name: Run tests
shell: pwsh
working-directory: ${{ inputs.working-directory }}
run: cargo nextest run --workspace --no-fail-fast

View File

@@ -14,7 +14,6 @@ concurrency:
jobs:
bump_patch_version:
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
steps:

View File

@@ -135,7 +135,6 @@ jobs:
cargo check -p gpui --features "macos-blade"
cargo check -p workspace
cargo build -p remote_server
cargo check -p gpui --examples
script/check-rust-livekit-macos
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
@@ -182,7 +181,6 @@ jobs:
run: |
cargo build -p zed
cargo check -p workspace
cargo check -p gpui --examples
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
@@ -228,6 +226,7 @@ jobs:
if: always()
run: rm -rf ./../.cargo
# todo(windows): Actually run the tests
windows_tests:
timeout-minutes: 60
name: (Windows) Run Clippy and tests
@@ -237,55 +236,33 @@ jobs:
# 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
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: cargo clippy
working-directory: ${{ env.ZED_WORKSPACE }}
# Windows can't run shell scripts, so we need to use `cargo xtask`.
run: cargo xtask clippy
- name: Run tests
uses: ./.github/actions/run_tests_windows
with:
working-directory: ${{ env.ZED_WORKSPACE }}
- name: Build Zed
working-directory: ${{ env.ZED_WORKSPACE }}
run: cargo build
- 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: Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
run: Remove-Item -Path "./../.cargo" -Recurse -Force
bundle-mac:
timeout-minutes: 120
@@ -306,7 +283,7 @@ jobs:
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Install Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

View File

@@ -19,7 +19,11 @@ jobs:
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
days-before-stale: 120
# We will increase `days-before-stale` to 365 on or after Jan 24th,
# 2024. This date marks one year since migrating issues from
# 'community' to 'zed' repository. The migration added activity to all
# issues, preventing 365 days from working until then.
days-before-stale: 180
days-before-close: 7
any-of-issue-labels: "bug,panic / crash"
operations-per-run: 1000

View File

@@ -9,7 +9,6 @@ permissions:
jobs:
delete_comment:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Check for specific strings in comment

View File

@@ -6,7 +6,6 @@ on:
jobs:
discord_release:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Get release URL

View File

@@ -0,0 +1,25 @@
name: Update All Top Ranking Issues
on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository == 'zed-industries/zed'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true
cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
- name: Install Python 3.13
run: uv python install 3.13
- name: Install dependencies
run: uv sync --project script/update_top_ranking_issues -p 3.13
- name: Run script
run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 5393

View File

@@ -0,0 +1,25 @@
name: Update Weekly Top Ranking Issues
on:
schedule:
- cron: "0 15 * * *"
workflow_dispatch:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository == 'zed-industries/zed'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true
cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
- name: Install Python 3.13
run: uv python install 3.13
- name: Install dependencies
run: uv sync --project script/update_top_ranking_issues -p 3.13
- name: Run script
run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 6952 --query-day-interval 7

View File

@@ -11,7 +11,6 @@ on:
jobs:
danger:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
@@ -22,7 +21,7 @@ jobs:
version: 9
- name: Setup Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "20"
cache: "pnpm"

View File

@@ -63,10 +63,3 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Preserve Wrangler logs
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
if: always()
with:
name: wrangler_logs
path: /home/runner/.config/.wrangler/logs/

View File

@@ -12,7 +12,6 @@ env:
jobs:
style:
name: Check formatting and Clippy lints
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- test

View File

@@ -24,13 +24,11 @@ jobs:
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
pnpm dlx prettier . --check || {
echo "To fix, run from the root of the zed repo:"
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
echo " cd docs && pnpm dlx prettier . --write && cd .."
false
}
env:
PRETTIER_VERSION: 3.5.0
- name: Check for Typos with Typos-CLI
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6

View File

@@ -12,7 +12,6 @@ env:
jobs:
publish:
name: Publish zed-extension CLI
if: github.repository_owner == 'zed-industries'
runs-on:
- ubuntu-latest
steps:

View File

@@ -18,12 +18,11 @@ env:
jobs:
tests:
name: Run randomized tests
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Install Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

View File

@@ -70,7 +70,7 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Install Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

54
.gitignore vendored
View File

@@ -1,35 +1,35 @@
**/*.db
**/cargo-target
**/target
**/venv
*.wasm
*.xcodeproj
.DS_Store
.blob_store
.build
.envrc
.flatpak-builder
.idea
.netrc
.pytest_cache
.swiftpm
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.venv
.vscode
.wrangler
/.direnv
/assets/*licenses.*
/crates/collab/seed.json
/crates/theme/schemas/theme.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/dev.zed.Zed*.json
.envrc
.idea
**/target
**/cargo-target
/zed.xcworkspace
.DS_Store
/plugins/bin
/script/node_modules
/zed.xcworkspace
DerivedData/
/crates/theme/schemas/theme.json
/crates/collab/seed.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/dev.zed.Zed*.json
/assets/*licenses.*
**/venv
.build
*.wasm
Packages
*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.swiftpm
**/*.db
.pytest_cache
.venv
.blob_store
.vscode
.wrangler
.flatpak-builder
# Don't commit any secrets to the repo.
.env.secret.toml

View File

@@ -52,9 +52,3 @@ Zed is made up of several smaller crates - let's go over those you're most likel
- [`rpc`](/crates/rpc) defines messages to be exchanged with collaboration server.
- [`theme`](/crates/theme) defines the theme system and provides a default theme.
- [`ui`](/crates/ui) is a collection of UI components and common patterns used throughout Zed.
- [`cli`](/crates/cli) is the CLI crate which invokes the Zed binary.
- [`zed`](/crates/zed) is where all things come together, and the `main` entry point for Zed.
## Packaging Zed
Check our [notes for packaging Zed](https://zed.dev/docs/development/linux#notes-for-packaging-zed).

953
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
resolver = "2"
members = [
"crates/activity_indicator",
"crates/zed_predict_tos",
"crates/anthropic",
"crates/assets",
"crates/assistant",
@@ -26,15 +27,11 @@ members = [
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
"crates/component",
"crates/component_preview",
"crates/context_server",
"crates/context_server_settings",
"crates/copilot",
"crates/db",
"crates/deepseek",
"crates/diagnostics",
"crates/diff",
"crates/docs_preprocessor",
"crates/editor",
"crates/evals",
@@ -47,17 +44,16 @@ members = [
"crates/feedback",
"crates/file_finder",
"crates/file_icons",
"crates/fireworks",
"crates/fs",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
"crates/git_hosting_providers",
"crates/git_ui",
"crates/go_to_line",
"crates/google_ai",
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/image_viewer",
@@ -83,7 +79,6 @@ members = [
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/migrator",
"crates/multi_buffer",
"crates/node_runtime",
"crates/notifications",
@@ -91,7 +86,6 @@ members = [
"crates/open_ai",
"crates/outline",
"crates/outline_panel",
"crates/panel",
"crates/paths",
"crates/picker",
"crates/prettier",
@@ -111,7 +105,6 @@ members = [
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/schema_generator",
"crates/search",
"crates/semantic_index",
"crates/semantic_version",
@@ -147,8 +140,9 @@ members = [
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/reqwest_client",
"crates/util",
"crates/util_macros",
"crates/vcs_menu",
"crates/vim",
"crates/vim_mode_setting",
"crates/welcome",
@@ -157,6 +151,7 @@ members = [
"crates/zed",
"crates/zed_actions",
"crates/zeta",
"crates/git_ui",
#
# Extensions
@@ -173,6 +168,7 @@ members = [
"extensions/lua",
"extensions/php",
"extensions/perplexity",
"extensions/prisma",
"extensions/proto",
"extensions/purescript",
"extensions/ruff",
@@ -204,6 +200,7 @@ edition = "2021"
activity_indicator = { path = "crates/activity_indicator" }
ai = { path = "crates/ai" }
zed_predict_tos = { path = "crates/zed_predict_tos" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
@@ -228,15 +225,11 @@ collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
component_preview = { path = "crates/component_preview" }
context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" }
diff = { path = "crates/diff" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" }
@@ -245,19 +238,19 @@ feature_flags = { path = "crates/feature_flags" }
feedback = { path = "crates/feedback" }
file_finder = { path = "crates/file_finder" }
file_icons = { path = "crates/file_icons" }
fireworks = { path = "crates/fireworks" }
fs = { path = "crates/fs" }
fsevent = { path = "crates/fsevent" }
fuzzy = { path = "crates/fuzzy" }
git = { path = "crates/git" }
git_hosting_providers = { path = "crates/git_hosting_providers" }
git_ui = { path = "crates/git_ui" }
git_hosting_providers = { path = "crates/git_hosting_providers" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false, features = [
"http_client",
] }
gpui_macros = { path = "crates/gpui_macros" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
image_viewer = { path = "crates/image_viewer" }
@@ -283,7 +276,6 @@ markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
media = { path = "crates/media" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
multi_buffer = { path = "crates/multi_buffer" }
node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
@@ -292,7 +284,6 @@ open_ai = { path = "crates/open_ai" }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
paths = { path = "crates/paths" }
panel = { path = "crates/panel" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -348,7 +339,7 @@ ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
welcome = { path = "crates/welcome" }
@@ -368,7 +359,7 @@ alacritty_terminal = { git = "https://github.com/alacritty/alacritty.git", rev =
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = { version = "0.10", default-features = false, features = ["async-std"] }
ashpd = { version = "0.10", default-features = false, features = ["async-std"]}
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
@@ -382,10 +373,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
naga = { version = "23.1.0", features = ["wgsl-in"] }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -430,12 +420,7 @@ jupyter-websocket-client = { version = "0.9.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
linkme = "0.3.31"
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
"dispatcher",
"services-dispatcher",
"rustls-tls-native-roots",
], default-features = false }
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
@@ -455,13 +440,11 @@ pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git"
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
profiling = "1"
prost = "0.9"
prost-build = "0.9"
prost-types = "0.9"
pulldown-cmark = { version = "0.12.0", default-features = false }
quote = "1.0.9"
rand = "0.8.5"
rayon = "1.8"
regex = "1.5"
@@ -481,7 +464,7 @@ runtimelib = { version = "0.25.0", default-features = false, features = [
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.22" }
rustls = "0.21.12"
rustls-native-certs = "0.8.0"
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
semver = "1.0"
@@ -505,7 +488,6 @@ sqlformat = "0.2"
strsim = "0.11"
strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
sys-locale = "0.3.1"
sysinfo = "0.31.0"
take-until = "0.2.0"
@@ -530,7 +512,6 @@ tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23"
tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-gitcommit = {git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9"}
tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
@@ -541,13 +522,13 @@ tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.24"
tree-sitter-regex = "0.23"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.23"
tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unindent = "0.2.0"
unindent = "0.1.7"
unicode-segmentation = "1.10"
unicode-script = "0.5.7"
url = "2.2"
@@ -564,7 +545,6 @@ wasmtime = { version = "24", default-features = false, features = [
wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zed_llm_client = "0.4"
zstd = "0.11"
metal = "0.31"
@@ -625,7 +605,6 @@ features = [
# TODO livekit https://github.com/RustAudio/cpal/pull/891
[patch.crates-io]
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls"}
[profile.dev]
split-debuginfo = "unpacked"
@@ -679,6 +658,7 @@ telemetry_events = { codegen-units = 1 }
theme_selector = { codegen-units = 1 }
time_format = { codegen-units = 1 }
ui_input = { codegen-units = 1 }
vcs_menu = { codegen-units = 1 }
zed_actions = { codegen-units = 1 }
[profile.release]

View File

@@ -1,4 +1,4 @@
Copyright 2022 - 2025 Zed Industries, Inc.
Copyright 2022 - 2024 Zed Industries, Inc.

View File

@@ -1,4 +1,4 @@
Copyright 2022 - 2025 Zed Industries, Inc.
Copyright 2022 - 2024 Zed Industries, Inc.
Licensed under the Apache License, Version 2.0 (the "License");

View File

@@ -1 +0,0 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="black"></path></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1 +0,0 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="7.25" cy="7.25" r="3" fill="currentColor"></circle></svg>

Before

Width:  |  Height:  |  Size: 165 B

View File

@@ -42,12 +42,6 @@
"elm": "elm",
"erl": "erlang",
"escript": "erlang",
"eslint.config.cjs": "eslint",
"eslint.config.cts": "eslint",
"eslint.config.js": "eslint",
"eslint.config.mjs": "eslint",
"eslint.config.mts": "eslint",
"eslint.config.ts": "eslint",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
@@ -86,8 +80,8 @@
"hpp": "cpp",
"hrl": "erlang",
"hs": "haskell",
"htm": "html",
"html": "html",
"htm": "template",
"html": "template",
"hxx": "cpp",
"ib": "storage",
"ico": "image",
@@ -101,7 +95,7 @@
"jpeg": "image",
"jpg": "image",
"js": "javascript",
"json": "json",
"json": "storage",
"jsonc": "storage",
"jsx": "react",
"jxl": "image",
@@ -150,19 +144,8 @@
"postcss": "css",
"ppt": "document",
"pptx": "document",
"prettier.config.cjs": "prettier",
"prettier.config.js": "prettier",
"prettier.config.mjs": "prettier",
"prettierignore": "prettier",
"prettierrc": "prettier",
"prettierrc.cjs": "prettier",
"prettierrc.js": "prettier",
"prettierrc.json": "prettier",
"prettierrc.json5": "prettier",
"prettierrc.mjs": "prettier",
"prettierrc.toml": "prettier",
"prettierrc.yaml": "prettier",
"prettierrc.yml": "prettier",
"prisma": "prisma",
"profile": "terminal",
"ps1": "terminal",

View File

@@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
<path d="M8 9V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="8" cy="9" r="1" fill="black"/>
<rect x="3.75" y="5.75" width="8.5" height="7.5" rx="1.25" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 452 B

View File

@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 8.9V11C5.34478 11 4.65522 11 3 11V10.4L7 5.6V5H3V7.1" stroke="black" stroke-width="1.5"/>
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
<path 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"/>
</svg>

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -1,19 +0,0 @@
<svg width="440" height="128" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="tilePattern" width="22" height="22" patternUnits="userSpaceOnUse">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
<path 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"/>
</svg>
</pattern>
<linearGradient id="fade" y2="1" x2="0">
<stop offset="0" stop-color="white" stop-opacity=".24"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<mask id="fadeMask" maskContentUnits="objectBoundingBox">
<rect width="1" height="1" fill="url(#fade)"/>
</mask>
</defs>
<rect width="100%" height="100%" fill="url(#tilePattern)" mask="url(#fadeMask)"/>
</svg>

Before

Width:  |  Height:  |  Size: 971 B

View File

@@ -1,6 +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" fill-rule="evenodd" clip-rule="evenodd" d="M6.75 9.31247L8.25 10.5576V11.75H1.75V10.0803L4.49751 7.44273L5.65909 8.40693L3.73923 10.25H6.75V9.31247ZM8.25 5.85739V4.25H6.31358L8.25 5.85739ZM1.75 5.16209V7.1H3.25V6.4072L1.75 5.16209Z" fill="black"/>
<path opacity="0.6" fill-rule="evenodd" clip-rule="evenodd" d="M10.9624 9.40853L11.9014 8L10.6241 6.08397L9.37598 6.91603L10.0986 8L9.80184 8.44518L10.9624 9.40853Z" fill="black"/>
<path opacity="0.6" fill-rule="evenodd" clip-rule="evenodd" d="M12.8936 11.0116L14.9014 8L12.6241 4.58397L11.376 5.41603L13.0986 8L11.7331 10.0483L12.8936 11.0116Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1225 13.809C14.0341 13.9146 13.877 13.9289 13.7711 13.8409L1.19311 3.40021C1.08659 3.31178 1.07221 3.15362 1.16104 3.04743L1.87752 2.19101C1.96588 2.0854 2.123 2.07112 2.22895 2.15906L14.8069 12.5998C14.9134 12.6882 14.9278 12.8464 14.839 12.9526L14.1225 13.809Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -32,7 +32,7 @@
"ctrl-q": "zed::Quit",
"f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "zeta::RateCompletions",
"ctrl-shift-i": "edit_prediction::ToggleMenu"
"ctrl-shift-i": "inline_completion::ToggleMenu"
}
},
{
@@ -122,8 +122,7 @@
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu",
"ctrl-shift-e": "editor::ToggleEditPrediction"
"shift-f10": "editor::OpenContextMenu"
}
},
{
@@ -137,26 +136,25 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
}
},
{
"context": "Editor && mode == full && edit_prediction",
"context": "Editor && mode == full && inline_completion",
"bindings": {
"alt-]": "editor::NextEditPrediction",
"alt-[": "editor::PreviousEditPrediction",
"alt-right": "editor::AcceptPartialEditPrediction"
"alt-]": "editor::NextInlineCompletion",
"alt-[": "editor::PreviousInlineCompletion",
"alt-right": "editor::AcceptPartialInlineCompletion"
}
},
{
"context": "Editor && !edit_prediction",
"context": "Editor && !inline_completion",
"bindings": {
"alt-\\": "editor::ShowEditPrediction"
"alt-\\": "editor::ShowInlineCompletion"
}
},
{
@@ -204,8 +202,8 @@
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"find": "search::FocusSearch",
"ctrl-f": "search::FocusSearch",
"find": "search::FocusSearch",
"ctrl-h": "search::ToggleReplace",
"ctrl-l": "search::ToggleSelection"
}
@@ -275,8 +273,8 @@
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }],
"ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"ctrl-f4": "pane::CloseActiveItem",
"ctrl-w": "pane::CloseActiveItem",
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
@@ -291,15 +289,15 @@
"f3": "search::SelectNextMatch",
"ctrl-alt-shift-g": "search::SelectPrevMatch",
"shift-f3": "search::SelectPrevMatch",
"shift-find": "project_search::ToggleFocus",
"ctrl-shift-f": "project_search::ToggleFocus",
"shift-find": "project_search::ToggleFocus",
"ctrl-alt-shift-h": "search::ToggleReplace",
"ctrl-alt-shift-l": "search::ToggleSelection",
"alt-enter": "search::SelectAllMatches",
"alt-c": "search::ToggleCaseSensitive",
"alt-w": "search::ToggleWholeWord",
"alt-find": "project_search::ToggleFilters",
"alt-ctrl-f": "project_search::ToggleFilters",
"alt-find": "project_search::ToggleFilters",
"ctrl-alt-shift-r": "search::ToggleRegex",
"ctrl-alt-shift-x": "search::ToggleRegex",
"alt-r": "search::ToggleRegex",
@@ -349,15 +347,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
"ctrl-k ctrl-1": ["editor::FoldAtLevel", { "level": 1 }],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", { "level": 2 }],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", { "level": 3 }],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", { "level": 4 }],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", { "level": 5 }],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", { "level": 6 }],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", { "level": 7 }],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", { "level": 8 }],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", { "level": 9 }],
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -427,20 +425,19 @@
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-shift-g": "git_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"alt-save": "workspace::SaveAll",
"ctrl-alt-s": "workspace::SaveAll",
"ctrl-k m": "language_selector::Toggle",
"escape": "workspace::Unfollow",
"ctrl-k ctrl-left": "workspace::ActivatePaneLeft",
"ctrl-k ctrl-right": "workspace::ActivatePaneRight",
"ctrl-k ctrl-up": "workspace::ActivatePaneUp",
"ctrl-k ctrl-down": "workspace::ActivatePaneDown",
"ctrl-k shift-left": "workspace::SwapPaneLeft",
"ctrl-k shift-right": "workspace::SwapPaneRight",
"ctrl-k shift-up": "workspace::SwapPaneUp",
"ctrl-k shift-down": "workspace::SwapPaneDown",
"ctrl-k ctrl-left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-k ctrl-right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-k ctrl-up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-k ctrl-down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-shift-x": "zed::Extensions",
"ctrl-shift-r": "task::Rerun",
"ctrl-alt-r": "task::Rerun",
@@ -454,8 +451,8 @@
{
"context": "ApplicationMenu",
"bindings": {
"left": "app_menu::ActivateMenuLeft",
"right": "app_menu::ActivateMenuRight"
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
}
},
// Bindings from Sublime Text
@@ -497,22 +494,17 @@
},
{
"context": "Editor && showing_completions",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::ConfirmCompletion",
"tab": "editor::ComposeCompletion"
}
},
{
"context": "Editor && edit_prediction",
"context": "Editor && inline_completion && !showing_completions",
"use_key_equivalents": true,
"bindings": {
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
"alt-enter": "editor::AcceptEditPrediction"
}
},
{
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"bindings": {
"tab": "editor::AcceptEditPrediction"
"tab": "editor::AcceptInlineCompletion"
}
},
{
@@ -536,7 +528,8 @@
{
"bindings": {
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
"ctrl-alt-i": "zed::DebugElements"
"ctrl-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints"
}
},
{
@@ -550,12 +543,11 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"ctrl-alt-enter": "editor::OpenExcerptsSplit",
"ctrl-k enter": "editor::OpenExcerptsSplit",
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
"ctrl-enter": "assistant::InlineAssist"
}
},
{
@@ -601,12 +593,14 @@
},
{
"context": "MessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "assistant2::Chat"
}
},
{
"context": "ContextStrip",
"use_key_equivalents": true,
"bindings": {
"up": "assistant2::FocusUp",
"right": "assistant2::FocusRight",
@@ -651,7 +645,7 @@
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"ctrl-alt-enter": "editor::OpenExcerptsSplit"
"ctrl-k enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -684,8 +678,8 @@
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"shift-find": "project_panel::NewSearchInDirectory",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-find": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"escape": "menu::Cancel"
@@ -697,36 +691,6 @@
"space": "project_panel::Open"
}
},
{
"context": "GitPanel && !CommitEditor",
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"bindings": {
"up": "menu::SelectPrev",
"down": "menu::SelectNext",
"enter": "menu::Confirm",
"space": "git::ToggleStaged",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus"
}
},
{
"context": "GitPanel > Editor",
"bindings": {
"escape": "git_panel::FocusChanges",
"ctrl-enter": "git::Commit",
"tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -804,8 +768,6 @@
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
// Overrides for conflicting keybindings
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
"ctrl-shift-a": "editor::SelectAll",
@@ -829,11 +791,5 @@
"shift-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode"
}
},
{
"context": "ZedPredictModal",
"bindings": {
"escape": "menu::Cancel"
}
}
]

View File

@@ -39,8 +39,8 @@
"cmd-m": "zed::Minimize",
"fn-f": "zed::ToggleFullScreen",
"ctrl-cmd-f": "zed::ToggleFullScreen",
"ctrl-cmd-z": "zeta::RateCompletions",
"ctrl-cmd-i": "edit_prediction::ToggleMenu"
"ctrl-shift-z": "zeta::RateCompletions",
"ctrl-shift-i": "inline_completion::ToggleMenu"
}
},
{
@@ -132,8 +132,7 @@
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp",
"ctrl-f12": "editor::GoToDeclaration",
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
"ctrl-cmd-e": "editor::ToggleEditPrediction"
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
}
},
{
@@ -146,29 +145,28 @@
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-k z": "editor::ToggleSoftWrap",
"cmd-f": "buffer_search::Deploy",
"cmd-alt-f": "buffer_search::DeployReplace",
"cmd-alt-f": ["buffer_search::Deploy", { "replace_enabled": true }],
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
"cmd-alt-e": "editor::SelectEnclosingSymbol"
}
},
{
"context": "Editor && mode == full && edit_prediction",
"context": "Editor && mode == full && inline_completion",
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::NextEditPrediction",
"alt-shift-tab": "editor::PreviousEditPrediction",
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
"alt-tab": "editor::NextInlineCompletion",
"alt-shift-tab": "editor::PreviousInlineCompletion",
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
}
},
{
"context": "Editor && !edit_prediction",
"context": "Editor && !inline_completion",
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::ShowEditPrediction"
"alt-tab": "editor::ShowInlineCompletion"
}
},
{
@@ -350,7 +348,7 @@
"cmd-}": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"cmd-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
@@ -414,15 +412,15 @@
"cmd-k cmd-l": "editor::ToggleFold",
"cmd-k cmd-[": "editor::FoldRecursive",
"cmd-k cmd-]": "editor::UnfoldRecursive",
"cmd-k cmd-1": ["editor::FoldAtLevel", 1],
"cmd-k cmd-2": ["editor::FoldAtLevel", 2],
"cmd-k cmd-3": ["editor::FoldAtLevel", 3],
"cmd-k cmd-4": ["editor::FoldAtLevel", 4],
"cmd-k cmd-5": ["editor::FoldAtLevel", 5],
"cmd-k cmd-6": ["editor::FoldAtLevel", 6],
"cmd-k cmd-7": ["editor::FoldAtLevel", 7],
"cmd-k cmd-8": ["editor::FoldAtLevel", 8],
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
"cmd-k cmd-1": ["editor::FoldAtLevel", { "level": 1 }],
"cmd-k cmd-2": ["editor::FoldAtLevel", { "level": 2 }],
"cmd-k cmd-3": ["editor::FoldAtLevel", { "level": 3 }],
"cmd-k cmd-4": ["editor::FoldAtLevel", { "level": 4 }],
"cmd-k cmd-5": ["editor::FoldAtLevel", { "level": 5 }],
"cmd-k cmd-6": ["editor::FoldAtLevel", { "level": 6 }],
"cmd-k cmd-7": ["editor::FoldAtLevel", { "level": 7 }],
"cmd-k cmd-8": ["editor::FoldAtLevel", { "level": 8 }],
"cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
@@ -510,14 +508,14 @@
"cmd-alt-s": "workspace::SaveAll",
"cmd-k m": "language_selector::Toggle",
"escape": "workspace::Unfollow",
"cmd-k cmd-left": "workspace::ActivatePaneLeft",
"cmd-k cmd-right": "workspace::ActivatePaneRight",
"cmd-k cmd-up": "workspace::ActivatePaneUp",
"cmd-k cmd-down": "workspace::ActivatePaneDown",
"cmd-k shift-left": "workspace::SwapPaneLeft",
"cmd-k shift-right": "workspace::SwapPaneRight",
"cmd-k shift-up": "workspace::SwapPaneUp",
"cmd-k shift-down": "workspace::SwapPaneDown",
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"cmd-shift-x": "zed::Extensions"
}
},
@@ -581,17 +579,10 @@
}
},
{
"context": "Editor && edit_prediction",
"bindings": {
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
"alt-tab": "editor::AcceptEditPrediction"
}
},
{
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"context": "Editor && inline_completion && !showing_completions",
"use_key_equivalents": true,
"bindings": {
"tab": "editor::AcceptEditPrediction"
"tab": "editor::AcceptInlineCompletion"
}
},
{
@@ -620,7 +611,8 @@
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
"cmd-alt-i": "zed::DebugElements"
"cmd-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints"
}
},
{
@@ -629,12 +621,11 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"cmd-alt-enter": "editor::OpenExcerptsSplit",
"cmd-k enter": "editor::OpenExcerptsSplit",
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
"ctrl-enter": "assistant::InlineAssist"
}
},
{
@@ -677,7 +668,7 @@
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"cmd-alt-enter": "editor::OpenExcerptsSplit"
"cmd-k enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -716,6 +707,13 @@
"space": "project_panel::Open"
}
},
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
@@ -728,21 +726,17 @@
"space": "git::ToggleStaged",
"cmd-shift-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll",
"alt-down": "git_panel::FocusEditor",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus"
"alt-down": "git_panel::FocusEditor"
}
},
{
"context": "GitPanel > Editor",
"context": "GitPanel && CommitEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
"cmd-enter": "git::Commit",
"tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges"
"alt-up": "git_panel::FocusChanges",
"escape": "git_panel::FocusChanges",
"cmd-enter": "git::CommitChanges",
"cmd-alt-enter": "git::CommitAllChanges"
}
},
{
@@ -839,8 +833,6 @@
// Terminal.app compatibility
"alt-left": ["terminal::SendText", "\u001bb"],
"alt-right": ["terminal::SendText", "\u001bf"],
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
// There are conflicting bindings for these keys in the global context.
// these bindings override them, remove at your own risk:
"up": ["terminal::SendKeystroke", "up"],
@@ -888,7 +880,7 @@
}
},
{
"context": "ZedPredictModal",
"context": "ZedPredictTos",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel"

View File

@@ -13,7 +13,7 @@
"cmd-b": "editor::GoToDefinition",
"cmd-j": "editor::ScrollCursorCenter",
"cmd-enter": "editor::NewlineBelow",
"cmd-alt-enter": "editor::NewlineAbove",
"cmd-alt-enter": "editor::NewLineAbove",
"cmd-shift-l": "editor::SelectLine",
"cmd-shift-t": "outline::Toggle",
"alt-backspace": "editor::DeleteToPreviousWordStart",
@@ -70,7 +70,7 @@
"bindings": {
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-d": "project_panel::Duplicate",
"cmd-n": "project_panel::NewDirectory",
"cmd-n": "project_panel::NewFolder",
"return": "project_panel::Rename",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",

View File

@@ -2,8 +2,8 @@
{
"context": "VimControl && !menu",
"bindings": {
"i": ["vim::PushObject", { "around": false }],
"a": ["vim::PushObject", { "around": true }],
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"left": "vim::Left",
"h": "vim::Left",
"backspace": "vim::Backspace",
@@ -54,10 +54,10 @@
// "b": "vim::PreviousSubwordStart",
// "e": "vim::NextSubwordEnd",
// "g e": "vim::PreviousSubwordEnd",
"shift-w": ["vim::NextWordStart", { "ignore_punctuation": true }],
"shift-e": ["vim::NextWordEnd", { "ignore_punctuation": true }],
"shift-b": ["vim::PreviousWordStart", { "ignore_punctuation": true }],
"g shift-e": ["vim::PreviousWordEnd", { "ignore_punctuation": true }],
"shift-w": ["vim::NextWordStart", { "ignorePunctuation": true }],
"shift-e": ["vim::NextWordEnd", { "ignorePunctuation": true }],
"shift-b": ["vim::PreviousWordStart", { "ignorePunctuation": true }],
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
"/": "vim::Search",
"g /": "pane::DeploySearch",
"?": ["vim::Search", { "backwards": true }],
@@ -70,20 +70,20 @@
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
"] )": ["vim::UnmatchedForward", { "char": ")" }],
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"f": ["vim::PushFindForward", { "before": false }],
"t": ["vim::PushFindForward", { "before": true }],
"shift-f": ["vim::PushFindBackward", { "after": false }],
"shift-t": ["vim::PushFindBackward", { "after": true }],
"m": "vim::PushMark",
"'": ["vim::PushJump", { "line": true }],
"`": ["vim::PushJump", { "line": false }],
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
"shift-t": ["vim::PushOperator", { "FindBackward": { "after": true } }],
"m": ["vim::PushOperator", "Mark"],
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
"ctrl-i": "pane::GoForward",
"ctrl-]": "editor::GoToDefinition",
"escape": "vim::SwitchToNormalMode",
"ctrl-[": "vim::SwitchToNormalMode",
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"v": "vim::ToggleVisual",
"shift-v": "vim::ToggleVisualLine",
"ctrl-g": "vim::ShowLocation",
@@ -102,7 +102,6 @@
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
// "g" commands
"g r": "vim::PushReplaceWithRegister",
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g t": "pane::ActivateNextItem",
@@ -125,17 +124,17 @@
"g .": "editor::ToggleCodeActions", // zed specific
"g shift-a": "editor::FindAllReferences", // zed specific
"g space": "editor::OpenExcerpts", // zed specific
"g *": ["vim::MoveToNext", { "partial_word": true }],
"g #": ["vim::MoveToPrev", { "partial_word": true }],
"g j": ["vim::Down", { "display_lines": true }],
"g down": ["vim::Down", { "display_lines": true }],
"g k": ["vim::Up", { "display_lines": true }],
"g up": ["vim::Up", { "display_lines": true }],
"g $": ["vim::EndOfLine", { "display_lines": true }],
"g end": ["vim::EndOfLine", { "display_lines": true }],
"g 0": ["vim::StartOfLine", { "display_lines": true }],
"g home": ["vim::StartOfLine", { "display_lines": true }],
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
"g *": ["vim::MoveToNext", { "partialWord": true }],
"g #": ["vim::MoveToPrev", { "partialWord": true }],
"g j": ["vim::Down", { "displayLines": true }],
"g down": ["vim::Down", { "displayLines": true }],
"g k": ["vim::Up", { "displayLines": true }],
"g up": ["vim::Up", { "displayLines": true }],
"g $": ["vim::EndOfLine", { "displayLines": true }],
"g end": ["vim::EndOfLine", { "displayLines": true }],
"g 0": ["vim::StartOfLine", { "displayLines": true }],
"g home": ["vim::StartOfLine", { "displayLines": true }],
"g ^": ["vim::FirstNonWhitespace", { "displayLines": true }],
"g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic",
"g [": "editor::GoToPrevDiagnostic",
@@ -147,7 +146,7 @@
"shift-l": "vim::WindowBottom",
"q": "vim::ToggleRecord",
"shift-q": "vim::ReplayLastRecording",
"@": "vim::PushReplayRegister",
"@": ["vim::PushOperator", "ReplayRegister"],
// z commands
"z enter": ["workspace::SendKeystrokes", "z t ^"],
"z -": ["workspace::SendKeystrokes", "z b ^"],
@@ -166,8 +165,8 @@
"z f": "editor::FoldSelectedRanges",
"z shift-m": "editor::FoldAll",
"z shift-r": "editor::UnfoldAll",
"shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
// Count support
"1": ["vim::Number", 1],
"2": ["vim::Number", 2],
@@ -194,13 +193,13 @@
"escape": "editor::Cancel",
":": "command_palette::Toggle",
".": "vim::Repeat",
"c": "vim::PushChange",
"c": ["vim::PushOperator", "Change"],
"shift-c": "vim::ChangeToEndOfLine",
"d": "vim::PushDelete",
"d": ["vim::PushOperator", "Delete"],
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"g shift-j": "vim::JoinLinesNoWhitespace",
"y": "vim::PushYank",
"y": ["vim::PushOperator", "Yank"],
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
@@ -217,19 +216,19 @@
"shift-p": ["vim::Paste", { "before": true }],
"u": "vim::Undo",
"ctrl-r": "vim::Redo",
"r": "vim::PushReplace",
"r": ["vim::PushOperator", "Replace"],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
">": "vim::PushIndent",
"<": "vim::PushOutdent",
"=": "vim::PushAutoIndent",
"!": "vim::PushShellCommand",
"g u": "vim::PushLowercase",
"g shift-u": "vim::PushUppercase",
"g ~": "vim::PushOppositeCase",
"\"": "vim::PushRegister",
"g w": "vim::PushRewrap",
"g q": "vim::PushRewrap",
">": ["vim::PushOperator", "Indent"],
"<": ["vim::PushOperator", "Outdent"],
"=": ["vim::PushOperator", "AutoIndent"],
"!": ["vim::PushOperator", "ShellCommand"],
"g u": ["vim::PushOperator", "Lowercase"],
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
"\"": ["vim::PushOperator", "Register"],
"g w": ["vim::PushOperator", "Rewrap"],
"g q": ["vim::PushOperator", "Rewrap"],
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore",
@@ -240,7 +239,7 @@
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",
"[ c": "editor::GoToPrevHunk",
"g c": "vim::PushToggleComments"
"g c": ["vim::PushOperator", "ToggleComments"]
}
},
{
@@ -265,14 +264,14 @@
"y": "vim::VisualYank",
"shift-y": "vim::VisualYankLine",
"p": "vim::Paste",
"shift-p": ["vim::Paste", { "preserve_clipboard": true }],
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
"c": "vim::Substitute",
"s": "vim::Substitute",
"shift-r": "vim::SubstituteLine",
"shift-s": "vim::SubstituteLine",
"~": "vim::ChangeCase",
"*": ["vim::MoveToNext", { "partial_word": true }],
"#": ["vim::MoveToPrev", { "partial_word": true }],
"*": ["vim::MoveToNext", { "partialWord": true }],
"#": ["vim::MoveToPrev", { "partialWord": true }],
"ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement",
"g ctrl-a": ["vim::Increment", { "step": true }],
@@ -283,19 +282,19 @@
"g shift-a": "vim::VisualInsertEndOfLine",
"shift-j": "vim::JoinLines",
"g shift-j": "vim::JoinLinesNoWhitespace",
"r": "vim::PushReplace",
"ctrl-c": "vim::SwitchToNormalMode",
"ctrl-[": "vim::SwitchToNormalMode",
"escape": "vim::SwitchToNormalMode",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"!": "vim::ShellCommand",
"i": ["vim::PushObject", { "around": false }],
"a": ["vim::PushObject", { "around": true }],
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",
"g q": "vim::Rewrap",
"\"": "vim::PushRegister",
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
@@ -310,19 +309,19 @@
"ctrl-x": null,
"ctrl-x ctrl-o": "editor::ShowCompletions",
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
"ctrl-x ctrl-c": "editor::ShowEditPrediction", // zed specific
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
"ctrl-x ctrl-z": "editor::Cancel",
"ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-k": ["vim::PushDigraph", {}],
"ctrl-v": ["vim::PushLiteral", {}],
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
"ctrl-q": ["vim::PushLiteral", {}],
"ctrl-shift-q": ["vim::PushLiteral", {}],
"ctrl-r": "vim::PushRegister",
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-r": ["vim::PushOperator", "Register"],
"insert": "vim::ToggleReplace",
"ctrl-o": "vim::TemporaryNormal"
}
@@ -357,11 +356,11 @@
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"escape": "vim::NormalBefore",
"ctrl-k": ["vim::PushDigraph", {}],
"ctrl-v": ["vim::PushLiteral", {}],
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
"ctrl-q": ["vim::PushLiteral", {}],
"ctrl-shift-q": ["vim::PushLiteral", {}],
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"backspace": "vim::UndoReplace",
"tab": "vim::Tab",
"enter": "vim::Enter",
@@ -376,15 +375,9 @@
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators",
"escape": "vim::ClearOperators",
"ctrl-k": ["vim::PushDigraph", {}],
"ctrl-v": ["vim::PushLiteral", {}],
"ctrl-q": ["vim::PushLiteral", {}]
}
},
{
"context": "Editor && vim_mode == waiting && (vim_operator == ys || vim_operator == cs)",
"bindings": {
"escape": "vim::SwitchToNormalMode"
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
}
},
{
@@ -400,10 +393,10 @@
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignore_punctuation": true }],
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
// Subword TextObject
// "w": "vim::Subword",
// "shift-w": ["vim::Subword", { "ignore_punctuation": true }],
// "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
@@ -415,7 +408,6 @@
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
// "b": "vim::AnyBrackets",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
@@ -426,10 +418,9 @@
">": "vim::AngleBrackets",
"a": "vim::Argument",
"i": "vim::IndentObj",
"shift-i": ["vim::IndentObj", { "include_below": true }],
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
"f": "vim::Method",
"c": "vim::Class",
"e": "vim::EntireFile"
"c": "vim::Class"
}
},
{
@@ -437,14 +428,14 @@
"bindings": {
"c": "vim::CurrentLine",
"d": "editor::Rename", // zed specific
"s": ["vim::PushChangeSurrounds", {}]
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
}
},
{
"context": "vim_operator == d",
"bindings": {
"d": "vim::CurrentLine",
"s": "vim::PushDeleteSurrounds",
"s": ["vim::PushOperator", "DeleteSurrounds"],
"o": "editor::ToggleSelectedDiffHunks", // "d o"
"p": "editor::RevertSelectedHunks" // "d p"
}
@@ -483,7 +474,7 @@
"context": "vim_operator == y",
"bindings": {
"y": "vim::CurrentLine",
"s": ["vim::PushAddSurrounds", {}]
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
}
},
{
@@ -573,34 +564,34 @@
}
},
{
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
"ctrl-w left": "workspace::ActivatePaneLeft",
"ctrl-w right": "workspace::ActivatePaneRight",
"ctrl-w up": "workspace::ActivatePaneUp",
"ctrl-w down": "workspace::ActivatePaneDown",
"ctrl-w ctrl-h": "workspace::ActivatePaneLeft",
"ctrl-w ctrl-l": "workspace::ActivatePaneRight",
"ctrl-w ctrl-k": "workspace::ActivatePaneUp",
"ctrl-w ctrl-j": "workspace::ActivatePaneDown",
"ctrl-w h": "workspace::ActivatePaneLeft",
"ctrl-w l": "workspace::ActivatePaneRight",
"ctrl-w k": "workspace::ActivatePaneUp",
"ctrl-w j": "workspace::ActivatePaneDown",
"ctrl-w shift-left": "workspace::SwapPaneLeft",
"ctrl-w shift-right": "workspace::SwapPaneRight",
"ctrl-w shift-up": "workspace::SwapPaneUp",
"ctrl-w shift-down": "workspace::SwapPaneDown",
"ctrl-w shift-h": "workspace::SwapPaneLeft",
"ctrl-w shift-l": "workspace::SwapPaneRight",
"ctrl-w shift-k": "workspace::SwapPaneUp",
"ctrl-w shift-j": "workspace::SwapPaneDown",
"ctrl-w >": "vim::ResizePaneRight",
"ctrl-w <": "vim::ResizePaneLeft",
"ctrl-w -": "vim::ResizePaneDown",
"ctrl-w +": "vim::ResizePaneUp",
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w >": ["vim::ResizePane", "Widen"],
"ctrl-w <": ["vim::ResizePane", "Narrow"],
"ctrl-w -": ["vim::ResizePane", "Shorten"],
"ctrl-w +": ["vim::ResizePane", "Lengthen"],
"ctrl-w _": "vim::MaximizePane",
"ctrl-w =": "vim::ResetPaneSizes",
"ctrl-w g t": "pane::ActivateNextItem",
@@ -618,12 +609,10 @@
"ctrl-w shift-s": "pane::SplitHorizontal",
"ctrl-w ctrl-s": "pane::SplitHorizontal",
"ctrl-w s": "pane::SplitHorizontal",
"ctrl-w ctrl-c": "pane::CloseActiveItem",
"ctrl-w c": "pane::CloseActiveItem",
"ctrl-w ctrl-q": "pane::CloseActiveItem",
"ctrl-w q": "pane::CloseActiveItem",
"ctrl-w ctrl-a": "pane::CloseAllItems",
"ctrl-w a": "pane::CloseAllItems",
"ctrl-w ctrl-c": "pane::CloseAllItems",
"ctrl-w c": "pane::CloseAllItems",
"ctrl-w ctrl-q": "pane::CloseAllItems",
"ctrl-w q": "pane::CloseAllItems",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
@@ -631,7 +620,7 @@
}
},
{
"context": "GitPanel || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
@@ -679,20 +668,5 @@
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"k": "menu::SelectPrev",
"j": "menu::SelectNext",
"g g": "menu::SelectFirst",
"shift-g": "menu::SelectLast",
"g f": "menu::Confirm",
"i": "git_panel::FocusEditor",
"x": "git::ToggleStaged",
"shift-x": "git::StageAll",
"shift-u": "git::UnstageAll"
}
}
]

View File

@@ -10,9 +10,8 @@
"light": "One Light",
"dark": "One Dark"
},
"icon_theme": "Zed (Default)",
// The name of a base set of key bindings to use.
// This setting can take six values, each named after another
// This setting can take four values, each named after another
// text editor:
//
// 1. "VSCode"
@@ -24,8 +23,8 @@
"base_keymap": "VSCode",
// Features that can be globally enabled or disabled
"features": {
// Which edit prediction provider to use.
"edit_prediction_provider": "copilot"
// Which inline completion provider to use.
"inline_completion_provider": "copilot"
},
// The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Plex Mono",
@@ -93,13 +92,6 @@
// workspace when the centered layout is used.
"right_padding": 0.2
},
// All settings related to the image viewer.
"image_viewer": {
// The unit for image file sizes.
// By default we're setting it to binary.
// The second option is decimal.
"unit": "binary"
},
// The key to use for adding multiple cursors
// Currently "alt" or "cmd_or_ctrl" (also aliased as
// "cmd" and "ctrl") are supported.
@@ -168,9 +160,9 @@
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
/// Whether to show the edit predictions next to the completions provided by a language server.
/// Only has an effect if edit prediction provider supports it.
"show_edit_predictions_in_menu": true,
/// Whether to show the inline completions next to the completions provided by a language server.
/// Only has an effect if inline completion provider supports it.
"show_inline_completions_in_menu": true,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -203,14 +195,14 @@
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
// no matter how they were inserted.
"always_treat_brackets_as_autoclosed": false,
// Controls whether edit predictions are shown immediately (true)
// or manually by triggering `editor::ShowEditPrediction` (false).
"show_edit_predictions": true,
// Controls whether edit predictions are shown in a given language scope.
// Controls whether inline completions are shown immediately (true)
// or manually by triggering `editor::ShowInlineCompletion` (false).
"show_inline_completions": true,
// Controls whether inline completions are shown in a given language scope.
// Example: ["string", "comment"]
"edit_predictions_disabled_in": [],
"inline_completions_disabled_in": [],
// Whether to show tabs and spaces in the editor.
// This setting can take four values:
// This setting can take three values:
//
// 1. Draw tabs and spaces only for the selected text (default):
// "selection"
@@ -400,7 +392,7 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take five values:
/// This setting can take four values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
@@ -472,7 +464,7 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take five values:
/// This setting can take four values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
@@ -600,9 +592,7 @@
// Whether or not to show the tab bar in the editor
"show": true,
// Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true,
/// Whether or not to show the tab bar buttons.
"show_tab_bar_buttons": true
"show_nav_history_buttons": true
},
// Settings related to the editor's tabs
"tabs": {
@@ -781,25 +771,9 @@
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
"load_direnv": "direct",
"edit_predictions": {
// A list of globs representing files that edit predictions should be disabled for.
// There's a sensible default list of globs already included.
// Any addition to this list will be merged with the default list.
"disabled_globs": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
// When to show edit predictions previews in buffer.
// This setting takes two possible values:
// 1. Display inline when there are no language server completions available.
// "inline_preview": "auto"
// 2. Display inline when holding modifier key (alt by default).
// "inline_preview": "when_holding_modifier"
"inline_preview": "auto"
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"disabled_globs": [".env"]
},
// Settings specific to journaling
"journal": {
@@ -938,7 +912,7 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the terminal.
/// This setting can take five values:
/// This setting can take four values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
@@ -1192,9 +1166,6 @@
},
"lmstudio": {
"api_url": "http://localhost:1234/api/v0"
},
"deepseek": {
"api_url": "https://api.deepseek.com"
}
},
// Zed's Prettier integration settings.

View File

@@ -81,7 +81,7 @@
"terminal.ansi.bright_green": "#4d6140ff",
"terminal.ansi.dim_green": "#d1e0bfff",
"terminal.ansi.yellow": "#dec184ff",
"terminal.ansi.bright_yellow": "#e5c07bff",
"terminal.ansi.bright_yellow": "#786441ff",
"terminal.ansi.dim_yellow": "#f1dfc1ff",
"terminal.ansi.blue": "#74ade8ff",
"terminal.ansi.bright_blue": "#385378ff",
@@ -457,7 +457,7 @@
"terminal.ansi.bright_green": "#b2cfa9ff",
"terminal.ansi.dim_green": "#354d2eff",
"terminal.ansi.yellow": "#dec184ff",
"terminal.ansi.bright_yellow": "#826221ff",
"terminal.ansi.bright_yellow": "#f1dfc1ff",
"terminal.ansi.dim_yellow": "#786441ff",
"terminal.ansi.blue": "#5c78e2ff",
"terminal.ansi.bright_blue": "#b5baf2ff",

View File

@@ -3,9 +3,9 @@ use editor::Editor;
use extension_host::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, percentage, Animation, AnimationExt as _, App, Context, CursorStyle, Entity,
EventEmitter, InteractiveElement as _, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, Window,
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
use lsp::LanguageServerName;
@@ -27,8 +27,8 @@ pub enum Event {
pub struct ActivityIndicator {
statuses: Vec<LspStatus>,
project: Entity<Project>,
auto_updater: Option<Entity<AutoUpdater>>,
project: Model<Project>,
auto_updater: Option<Model<AutoUpdater>>,
context_menu_handle: PopoverMenuHandle<ContextMenu>,
}
@@ -46,24 +46,22 @@ struct PendingWork<'a> {
struct Content {
icon: Option<gpui::AnyElement>,
message: String,
on_click:
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
}
impl ActivityIndicator {
pub fn new(
workspace: &mut Workspace,
languages: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<ActivityIndicator> {
cx: &mut ViewContext<Workspace>,
) -> View<ActivityIndicator> {
let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx);
let this = cx.new(|cx| {
let this = cx.new_view(|cx: &mut ViewContext<Self>| {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn(|this, mut cx| async move {
while let Some((name, status)) = status_events.next().await {
this.update(&mut cx, |this: &mut ActivityIndicator, cx| {
this.update(&mut cx, |this, cx| {
this.statuses.retain(|s| s.name != name);
this.statuses.push(LspStatus { name, status });
cx.notify();
@@ -72,7 +70,6 @@ impl ActivityIndicator {
anyhow::Ok(())
})
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
if let Some(auto_updater) = auto_updater.as_ref() {
@@ -87,13 +84,13 @@ impl ActivityIndicator {
}
});
cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
cx.subscribe(&this, move |_, _, event, cx| match event {
Event::ShowError { lsp_name, error } => {
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
let project = project.clone();
let error = error.clone();
let lsp_name = lsp_name.clone();
cx.spawn_in(window, |workspace, mut cx| async move {
cx.spawn(|workspace, mut cx| async move {
let buffer = create_buffer.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.edit(
@@ -106,14 +103,13 @@ impl ActivityIndicator {
);
buffer.set_capability(language::Capability::ReadOnly, cx);
})?;
workspace.update_in(&mut cx, |workspace, window, cx| {
workspace.update(&mut cx, |workspace, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
Box::new(cx.new_view(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), cx)
})),
None,
true,
window,
cx,
);
})?;
@@ -127,7 +123,7 @@ impl ActivityIndicator {
this
}
fn show_error_message(&mut self, _: &ShowErrorMessage, _: &mut Window, cx: &mut Context<Self>) {
fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
self.statuses.retain(|status| {
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
cx.emit(Event::ShowError {
@@ -143,12 +139,7 @@ impl ActivityIndicator {
cx.notify();
}
fn dismiss_error_message(
&mut self,
_: &DismissErrorMessage,
_: &mut Window,
cx: &mut Context<Self>,
) {
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
if let Some(updater) = &self.auto_updater {
updater.update(cx, |updater, cx| {
updater.dismiss_error(cx);
@@ -159,7 +150,7 @@ impl ActivityIndicator {
fn pending_language_server_work<'a>(
&self,
cx: &'a App,
cx: &'a AppContext,
) -> impl Iterator<Item = PendingWork<'a>> {
self.project
.read(cx)
@@ -187,12 +178,12 @@ impl ActivityIndicator {
fn pending_environment_errors<'a>(
&'a self,
cx: &'a App,
cx: &'a AppContext,
) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> {
self.project.read(cx).shell_environment_errors(cx)
}
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
return Some(Content {
@@ -202,11 +193,11 @@ impl ActivityIndicator {
.into_any_element(),
),
message: error.0.clone(),
on_click: Some(Arc::new(move |this, window, cx| {
on_click: Some(Arc::new(move |this, cx| {
this.project.update(cx, |project, cx| {
project.remove_environment_error(cx, worktree_id);
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
cx.dispatch_action(Box::new(workspace::OpenLog));
})),
});
}
@@ -289,10 +280,10 @@ impl ActivityIndicator {
}
)
),
on_click: Some(Arc::new(move |this, window, cx| {
on_click: Some(Arc::new(move |this, cx| {
this.statuses
.retain(|status| !downloading.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
});
}
@@ -317,10 +308,10 @@ impl ActivityIndicator {
}
),
),
on_click: Some(Arc::new(move |this, window, cx| {
on_click: Some(Arc::new(move |this, cx| {
this.statuses
.retain(|status| !checking_for_update.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
});
}
@@ -345,8 +336,8 @@ impl ActivityIndicator {
acc
}),
),
on_click: Some(Arc::new(|this, window, cx| {
this.show_error_message(&Default::default(), window, cx)
on_click: Some(Arc::new(|this, cx| {
this.show_error_message(&Default::default(), cx)
})),
});
}
@@ -360,11 +351,11 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|indicator, window, cx| {
on_click: Some(Arc::new(|indicator, cx| {
indicator.project.update(cx, |project, cx| {
project.reset_last_formatting_failure(cx);
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
cx.dispatch_action(Box::new(workspace::OpenLog));
})),
});
}
@@ -379,8 +370,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Checking for Zed updates…".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Downloading => Some(Content {
@@ -390,8 +381,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Downloading Zed update…".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Installing => Some(Content {
@@ -401,8 +392,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Installing Zed update…".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Updated { binary_path } => Some(Content {
@@ -412,7 +403,7 @@ impl ActivityIndicator {
let reload = workspace::Reload {
binary_path: Some(binary_path.clone()),
};
move |_, _, cx| workspace::reload(&reload, cx)
move |_, cx| workspace::reload(&reload, cx)
})),
}),
AutoUpdateStatus::Errored => Some(Content {
@@ -422,8 +413,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Idle => None,
@@ -441,8 +432,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Updating {extension_id} extension…"),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
});
}
@@ -451,12 +442,8 @@ impl ActivityIndicator {
None
}
fn toggle_language_server_work_context_menu(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_menu_handle.toggle(window, cx);
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
self.context_menu_handle.toggle(cx);
}
}
@@ -465,7 +452,7 @@ impl EventEmitter<Event> for ActivityIndicator {}
const MAX_MESSAGE_LEN: usize = 50;
impl Render for ActivityIndicator {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let result = h_flex()
.id("activity-indicator")
.on_action(cx.listener(Self::show_error_message))
@@ -473,7 +460,7 @@ impl Render for ActivityIndicator {
let Some(content) = self.content_to_render(cx) else {
return result;
};
let this = cx.entity().downgrade();
let this = cx.view().downgrade();
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
result.gap_2().child(
PopoverMenu::new("activity-indicator-popover")
@@ -493,24 +480,24 @@ impl Render for ActivityIndicator {
))
.size(LabelSize::Small),
)
.tooltip(Tooltip::text(content.message))
.tooltip(move |cx| Tooltip::text(&content.message, cx))
} else {
button.child(Label::new(content.message).size(LabelSize::Small))
}
})
.when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, window, cx| {
handler(this, window, cx);
this.on_click(cx.listener(move |this, _, cx| {
handler(this, cx);
}))
.cursor(CursorStyle::PointingHand)
}),
),
)
.anchor(gpui::Corner::BottomLeft)
.menu(move |window, cx| {
.menu(move |cx| {
let strong_this = this.upgrade()?;
let mut has_work = false;
let menu = ContextMenu::build(window, cx, |mut menu, _, cx| {
let menu = ContextMenu::build(cx, |mut menu, cx| {
for work in strong_this.read(cx).pending_language_server_work(cx) {
has_work = true;
let this = this.clone();
@@ -526,7 +513,7 @@ impl Render for ActivityIndicator {
let token = work.progress_token.to_string();
let title = SharedString::from(title);
menu = menu.custom_entry(
move |_, _| {
move |_| {
h_flex()
.w_full()
.justify_between()
@@ -534,7 +521,7 @@ impl Render for ActivityIndicator {
.child(Icon::new(IconName::XCircle))
.into_any_element()
},
move |_, cx| {
move |cx| {
this.update(cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.cancel_language_server_work(
@@ -567,11 +554,5 @@ impl Render for ActivityIndicator {
}
impl StatusItemView for ActivityIndicator {
fn set_active_pane_item(
&mut self,
_: Option<&dyn ItemHandle>,
_window: &mut Window,
_: &mut Context<Self>,
) {
}
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
}

View File

@@ -3,7 +3,7 @@ name = "anthropic"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
license = "AGPL-3.0-or-later"
[features]
default = []

View File

@@ -0,0 +1 @@
../../LICENSE-AGPL

View File

@@ -2,7 +2,7 @@ mod supported_countries;
use std::{pin::Pin, str::FromStr};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use http_client::http::{HeaderMap, HeaderValue};
@@ -148,13 +148,8 @@ impl Model {
}
}
pub const DEFAULT_BETA_HEADERS: &[&str] = &["prompt-caching-2024-07-31"];
pub fn beta_headers(&self) -> String {
let mut headers = Self::DEFAULT_BETA_HEADERS
.into_iter()
.map(|header| header.to_string())
.collect::<Vec<_>>();
let mut headers = vec!["prompt-caching-2024-07-31".to_string()];
if let Self::Custom {
extra_beta_headers, ..
@@ -191,14 +186,12 @@ pub async fn complete(
request: Request,
) -> Result<Response, AnthropicError> {
let uri = format!("{api_url}/v1/messages");
let beta_headers = Model::from_id(&request.model)
.map(|model| model.beta_headers())
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
let model = Model::from_id(&request.model)?;
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", beta_headers)
.header("Anthropic-Beta", model.beta_headers())
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
@@ -250,7 +243,7 @@ pub async fn stream_completion(
.map(|output| output.0)
}
/// <https://docs.anthropic.com/en/api/rate-limits#response-headers>
/// https://docs.anthropic.com/en/api/rate-limits#response-headers
#[derive(Debug)]
pub struct RateLimitInfo {
pub requests_limit: usize,
@@ -309,14 +302,12 @@ pub async fn stream_completion_with_rate_limit_info(
stream: true,
};
let uri = format!("{api_url}/v1/messages");
let beta_headers = Model::from_id(&request.base.model)
.map(|model| model.beta_headers())
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
let model = Model::from_id(&request.base.model)?;
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", beta_headers)
.header("Anthropic-Beta", model.beta_headers())
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
let serialized_request =
@@ -626,7 +617,7 @@ pub struct ApiError {
}
/// An Anthropic API error code.
/// <https://docs.anthropic.com/en/api/errors#http-errors>
/// https://docs.anthropic.com/en/api/errors#http-errors
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum ApiErrorCode {

View File

@@ -3,7 +3,7 @@ use std::sync::LazyLock;
/// Returns whether the given country code is supported by Anthropic.
///
/// <https://www.anthropic.com/supported-countries>
/// https://www.anthropic.com/supported-countries
pub fn is_supported_country(country_code: &str) -> bool {
SUPPORTED_COUNTRIES.contains(&country_code)
}

View File

@@ -1,7 +1,7 @@
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
use anyhow::anyhow;
use gpui::{App, AssetSource, Result, SharedString};
use gpui::{AppContext, AssetSource, Result, SharedString};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
@@ -39,7 +39,7 @@ impl AssetSource for Assets {
impl Assets {
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
pub fn load_fonts(&self, cx: &App) -> gpui::Result<()> {
pub fn load_fonts(&self, cx: &AppContext) -> gpui::Result<()> {
let font_paths = self.list("fonts")?;
let mut embedded_fonts = Vec::new();
for font_path in font_paths {
@@ -55,7 +55,7 @@ impl Assets {
cx.text_system().add_fonts(embedded_fonts)
}
pub fn load_test_fonts(&self, cx: &App) {
pub fn load_test_fonts(&self, cx: &AppContext) {
cx.text_system()
.add_fonts(vec![self
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")

View File

@@ -15,7 +15,7 @@ use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::{actions, App, Global, UpdateGlobal};
use gpui::{actions, AppContext, Global, UpdateGlobal};
use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
};
@@ -67,7 +67,7 @@ impl Global for Assistant {}
impl Assistant {
const NAMESPACE: &'static str = "assistant";
fn set_enabled(&mut self, enabled: bool, cx: &mut App) {
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
if self.enabled == enabled {
return;
}
@@ -92,7 +92,7 @@ pub fn init(
fs: Arc<dyn Fs>,
client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>,
cx: &mut App,
cx: &mut AppContext,
) {
cx.set_global(Assistant::default());
AssistantSettings::register(cx);
@@ -165,7 +165,7 @@ pub fn init(
.detach();
}
fn init_language_model_settings(cx: &mut App) {
fn init_language_model_settings(cx: &mut AppContext) {
update_active_language_model_from_settings(cx);
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
@@ -184,7 +184,7 @@ fn init_language_model_settings(cx: &mut App) {
.detach();
}
fn update_active_language_model_from_settings(cx: &mut App) {
fn update_active_language_model_from_settings(cx: &mut AppContext) {
let settings = AssistantSettings::get_global(cx);
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
let model_id = LanguageModelId::from(settings.default_model.model.clone());
@@ -204,7 +204,7 @@ fn update_active_language_model_from_settings(cx: &mut App) {
});
}
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut App) {
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut AppContext) {
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
@@ -278,7 +278,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
.detach();
}
fn update_slash_commands_from_settings(cx: &mut App) {
fn update_slash_commands_from_settings(cx: &mut AppContext) {
let slash_command_registry = SlashCommandRegistry::global(cx);
let settings = SlashCommandSettings::get_global(cx);

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::{canvas, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
use gpui::{canvas, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, ElevationIndex};
use workspace::Item;
@@ -13,17 +13,16 @@ pub struct ConfigurationView {
}
impl ConfigurationView {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe_in(
let registry_subscription = cx.subscribe(
&LanguageModelRegistry::global(cx),
window,
|this, _, event: &language_model::Event, window, cx| match event {
|this, _, event: &language_model::Event, cx| match event {
language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider {
this.add_configuration_view(&provider, window, cx);
this.add_configuration_view(&provider, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
@@ -38,14 +37,14 @@ impl ConfigurationView {
configuration_views: HashMap::default(),
_registry_subscription: registry_subscription,
};
this.build_configuration_views(window, cx);
this.build_configuration_views(cx);
this
}
fn build_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_configuration_view(&provider, window, cx);
self.add_configuration_view(&provider, cx);
}
}
@@ -56,10 +55,9 @@ impl ConfigurationView {
fn add_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
let configuration_view = provider.configuration_view(window, cx);
let configuration_view = provider.configuration_view(cx);
self.configuration_views
.insert(provider.id(), configuration_view);
}
@@ -67,7 +65,7 @@ impl ConfigurationView {
fn render_provider_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Div {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
@@ -75,7 +73,7 @@ impl ConfigurationView {
let open_new_context = cx.listener({
let provider = provider.clone();
move |_, _, _window, cx| {
move |_, _, cx| {
cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
provider.clone(),
))
@@ -125,7 +123,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
let provider_views = providers
.into_iter()
@@ -165,12 +163,12 @@ impl Render for ConfigurationView {
// We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
// because we couldn't the element to take up the size of the parent.
canvas(
move |bounds, window, cx| {
element.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
move |bounds, cx| {
element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
element
},
|_, mut element, window, cx| {
element.paint(window, cx);
|_, mut element, cx| {
element.paint(cx);
},
)
.flex_1()
@@ -184,8 +182,8 @@ pub enum ConfigurationViewEvent {
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
impl Focusable for ConfigurationView {
fn focus_handle(&self, _: &App) -> FocusHandle {
impl FocusableView for ConfigurationView {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
@@ -193,7 +191,7 @@ impl Focusable for ConfigurationView {
impl Item for ConfigurationView {
type Event = ConfigurationViewEvent;
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
Some("Configuration".into())
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use gpui::App;
use gpui::AppContext;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
@@ -36,7 +36,7 @@ impl Settings for SlashCommandSettings {
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut AppContext) -> Result<Self> {
SettingsSources::<Self::FileContent>::json_merge_with(
[sources.default]
.into_iter()

View File

@@ -11,8 +11,8 @@ use editor::{
use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{
App, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription, Task,
TextStyle, UpdateGlobal, WeakEntity,
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,
};
use language::Buffer;
use language_model::{
@@ -39,7 +39,7 @@ pub fn init(
fs: Arc<dyn Fs>,
prompt_builder: Arc<PromptBuilder>,
telemetry: Arc<Telemetry>,
cx: &mut App,
cx: &mut AppContext,
) {
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
}
@@ -86,20 +86,20 @@ impl TerminalInlineAssistant {
pub fn assist(
&mut self,
terminal_view: &Entity<TerminalView>,
workspace: Option<WeakEntity<Workspace>>,
assistant_panel: Option<&Entity<AssistantPanel>>,
terminal_view: &View<TerminalView>,
workspace: Option<WeakView<Workspace>>,
assistant_panel: Option<&View<AssistantPanel>>,
initial_prompt: Option<String>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc();
let prompt_buffer = cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
let codegen = cx.new(|_| Codegen::new(terminal, self.telemetry.clone()));
let prompt_buffer =
cx.new_model(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new(|cx| {
let prompt_editor = cx.new_view(|cx| {
PromptEditor::new(
assist_id,
self.prompt_history.clone(),
@@ -108,7 +108,6 @@ impl TerminalInlineAssistant {
assistant_panel,
workspace.clone(),
self.fs.clone(),
window,
cx,
)
});
@@ -118,7 +117,7 @@ impl TerminalInlineAssistant {
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
};
terminal_view.update(cx, |terminal_view, cx| {
terminal_view.set_block_below_cursor(block, window, cx);
terminal_view.set_block_below_cursor(block, cx);
});
let terminal_assistant = TerminalInlineAssist::new(
@@ -127,27 +126,21 @@ impl TerminalInlineAssistant {
assistant_panel.is_some(),
prompt_editor,
workspace.clone(),
window,
cx,
);
self.assists.insert(assist_id, terminal_assistant);
self.focus_assist(assist_id, window, cx);
self.focus_assist(assist_id, cx);
}
fn focus_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut App,
) {
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = &self.assists[&assist_id];
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
prompt_editor.update(cx, |this, cx| {
this.editor.update(cx, |editor, cx| {
window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, window, cx);
editor.focus(cx);
editor.select_all(&SelectAll, cx);
});
});
}
@@ -155,10 +148,9 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: Entity<PromptEditor>,
prompt_editor: View<PromptEditor>,
event: &PromptEditorEvent,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
let assist_id = prompt_editor.read(cx).id;
match event {
@@ -169,21 +161,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx);
}
PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, window, cx);
self.finish_assist(assist_id, false, *execute, cx);
}
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, window, cx);
self.finish_assist(assist_id, true, false, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
}
}
}
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -221,7 +213,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
}
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -234,7 +226,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist(
&self,
assist_id: TerminalInlineAssistId,
cx: &mut App,
cx: &mut WindowContext,
) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?;
@@ -242,7 +234,7 @@ impl TerminalInlineAssistant {
let (latest_output, working_directory) = assist
.terminal
.update(cx, |terminal, cx| {
let terminal = terminal.entity().read(cx);
let terminal = terminal.model().read(cx);
let latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES);
let working_directory = terminal
.working_directory()
@@ -304,17 +296,16 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId,
undo: bool,
execute: bool,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, cx);
if let Some(assist) = self.assists.remove(&assist_id) {
assist
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(window);
this.focus_handle(cx).focus(cx);
})
.log_err();
@@ -357,8 +348,7 @@ impl TerminalInlineAssistant {
fn dismiss_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else {
return false;
@@ -371,7 +361,7 @@ impl TerminalInlineAssistant {
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(window);
this.focus_handle(cx).focus(cx);
})
.is_ok()
}
@@ -380,8 +370,7 @@ impl TerminalInlineAssistant {
&mut self,
assist_id: TerminalInlineAssistId,
height: u8,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@@ -393,7 +382,7 @@ impl TerminalInlineAssistant {
height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
};
terminal.set_block_below_cursor(block, window, cx);
terminal.set_block_below_cursor(block, cx);
})
.log_err();
}
@@ -402,10 +391,10 @@ impl TerminalInlineAssistant {
}
struct TerminalInlineAssist {
terminal: WeakEntity<TerminalView>,
prompt_editor: Option<Entity<PromptEditor>>,
codegen: Entity<Codegen>,
workspace: Option<WeakEntity<Workspace>>,
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor>>,
codegen: Model<Codegen>,
workspace: Option<WeakView<Workspace>>,
include_context: bool,
_subscriptions: Vec<Subscription>,
}
@@ -413,12 +402,11 @@ struct TerminalInlineAssist {
impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &Entity<TerminalView>,
terminal: &View<TerminalView>,
include_context: bool,
prompt_editor: Entity<PromptEditor>,
workspace: Option<WeakEntity<Workspace>>,
window: &mut Window,
cx: &mut App,
prompt_editor: View<PromptEditor>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen.clone();
Self {
@@ -428,12 +416,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(),
include_context,
_subscriptions: vec![
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| {
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
this.handle_prompt_editor_event(prompt_editor, event, cx)
})
}),
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
cx.subscribe(&codegen, move |codegen, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) {
@@ -466,7 +454,7 @@ impl TerminalInlineAssist {
}
if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, window, cx);
this.finish_assist(assist_id, false, false, cx);
}
}
})
@@ -488,25 +476,25 @@ enum PromptEditorEvent {
struct PromptEditor {
id: TerminalInlineAssistId,
height_in_lines: u8,
editor: Entity<Editor>,
language_model_selector: Entity<LanguageModelSelector>,
editor: View<Editor>,
language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
pending_prompt: String,
codegen: Entity<Codegen>,
codegen: Model<Codegen>,
_codegen_subscription: Subscription,
editor_subscriptions: Vec<Subscription>,
pending_token_count: Task<Result<()>>,
token_count: Option<usize>,
_token_count_subscriptions: Vec<Subscription>,
workspace: Option<WeakEntity<Workspace>>,
workspace: Option<WeakView<Workspace>>,
}
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status;
let buttons = match status {
CodegenStatus::Idle => {
@@ -514,20 +502,16 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
),
IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action("Generate", &menu::Confirm, window, cx)
})
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
),
]
}
@@ -536,24 +520,23 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text("Cancel Assist"))
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|cx| {
Tooltip::with_meta(
"Interrupt Generation",
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
),
]
}
@@ -561,12 +544,8 @@ impl Render for PromptEditor {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
);
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
@@ -575,16 +554,15 @@ impl Render for PromptEditor {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|cx| {
Tooltip::with_meta(
"Restart Generation",
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
})),
]
@@ -594,29 +572,23 @@ impl Render for PromptEditor {
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action(
"Accept Generated Command",
&menu::Confirm,
window,
cx,
)
.tooltip(|cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
})),
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
})),
]
@@ -646,22 +618,21 @@ impl Render for PromptEditor {
IconButton::new("context", IconName::SettingsAlt)
.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,
)
},
.icon_color(Color::Muted)
.tooltip(move |cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
cx,
)
}),
))
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
@@ -669,7 +640,7 @@ impl Render for PromptEditor {
Some(
div()
.id("error")
.tooltip(Tooltip::text(error_message))
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
@@ -692,8 +663,8 @@ impl Render for PromptEditor {
}
}
impl Focusable for PromptEditor {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for PromptEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
}
@@ -705,15 +676,14 @@ impl PromptEditor {
fn new(
id: TerminalInlineAssistId,
prompt_history: VecDeque<String>,
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<Codegen>,
assistant_panel: Option<&Entity<AssistantPanel>>,
workspace: Option<WeakEntity<Workspace>>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<Codegen>,
assistant_panel: Option<&View<AssistantPanel>>,
workspace: Option<WeakView<Workspace>>,
fs: Arc<dyn Fs>,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let prompt_editor = cx.new(|cx| {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -721,28 +691,24 @@ impl PromptEditor {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(window), cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor
});
let mut token_count_subscriptions = Vec::new();
if let Some(assistant_panel) = assistant_panel {
token_count_subscriptions.push(cx.subscribe_in(
assistant_panel,
window,
Self::handle_assistant_panel_event,
));
token_count_subscriptions
.push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
}
let mut this = Self {
id,
height_in_lines: 1,
editor: prompt_editor,
language_model_selector: cx.new(|cx| {
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
@@ -752,7 +718,6 @@ impl PromptEditor {
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
@@ -760,7 +725,7 @@ impl PromptEditor {
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
_codegen_subscription: cx.observe_in(&codegen, window, Self::handle_codegen_changed),
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(),
codegen,
pending_token_count: Task::ready(Ok(())),
@@ -774,15 +739,15 @@ impl PromptEditor {
this
}
fn placeholder_text(window: &Window) -> String {
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, window)
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} • ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut Context<Self>) {
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
@@ -790,11 +755,11 @@ impl PromptEditor {
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
}
fn prompt(&self, cx: &App) -> String {
fn prompt(&self, cx: &AppContext) -> String {
self.editor.read(cx).text(cx)
}
fn count_lines(&mut self, cx: &mut Context<Self>) {
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
@@ -812,16 +777,15 @@ impl PromptEditor {
fn handle_assistant_panel_event(
&mut self,
_: &Entity<AssistantPanel>,
_: View<AssistantPanel>,
event: &AssistantPanelEvent,
_: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
let AssistantPanelEvent::ContextEdited { .. } = event;
self.count_tokens(cx);
}
fn count_tokens(&mut self, cx: &mut Context<Self>) {
fn count_tokens(&mut self, cx: &mut ViewContext<Self>) {
let assist_id = self.id;
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
@@ -841,15 +805,15 @@ impl PromptEditor {
})
}
fn handle_prompt_editor_changed(&mut self, _: Entity<Editor>, cx: &mut Context<Self>) {
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
self.count_lines(cx);
}
fn handle_prompt_editor_events(
&mut self,
_: Entity<Editor>,
_: View<Editor>,
event: &EditorEvent,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
@@ -872,12 +836,7 @@ impl PromptEditor {
}
}
fn handle_codegen_changed(
&mut self,
_: Entity<Codegen>,
_: &mut Window,
cx: &mut Context<Self>,
) {
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
self.editor
@@ -895,7 +854,7 @@ impl PromptEditor {
}
}
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
@@ -906,7 +865,7 @@ impl PromptEditor {
}
}
fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut Context<Self>) {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
if !self.editor.read(cx).text(cx).trim().is_empty() {
@@ -929,58 +888,53 @@ impl PromptEditor {
}
}
fn secondary_confirm(
&mut self,
_: &menu::SecondaryConfirm,
_: &mut Window,
cx: &mut Context<Self>,
) {
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}
}
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
}
}
}
fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
fn render_token_count(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
let model = LanguageModelRegistry::read_global(cx).active_model()?;
let token_count = self.token_count?;
let max_token_count = model.max_token_count();
@@ -1010,35 +964,34 @@ impl PromptEditor {
);
if let Some(workspace) = self.workspace.clone() {
token_count = token_count
.tooltip(|window, cx| {
.tooltip(|cx| {
Tooltip::with_meta(
"Tokens Used by Inline Assistant",
None,
"Click to Open Assistant Panel",
window,
cx,
)
})
.cursor_pointer()
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(move |_, window, cx| {
.on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
cx.stop_propagation();
workspace
.update(cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(window, cx)
workspace.focus_panel::<AssistantPanel>(cx)
})
.ok();
});
} else {
token_count = token_count
.cursor_default()
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
.tooltip(|cx| Tooltip::text("Tokens Used by Inline Assistant", cx));
}
Some(token_count)
}
fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {
@@ -1076,27 +1029,27 @@ const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
terminal: Entity<Terminal>,
terminal: Model<Terminal>,
}
impl TerminalTransaction {
pub fn start(terminal: Entity<Terminal>) -> Self {
pub fn start(terminal: Model<Terminal>) -> Self {
Self { terminal }
}
pub fn push(&mut self, hunk: String, cx: &mut App) {
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
pub fn undo(&self, cx: &mut App) {
pub fn undo(&self, cx: &mut AppContext) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
}
pub fn complete(&self, cx: &mut App) {
pub fn complete(&self, cx: &mut AppContext) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});
@@ -1110,14 +1063,14 @@ impl TerminalTransaction {
pub struct Codegen {
status: CodegenStatus,
telemetry: Option<Arc<Telemetry>>,
terminal: Entity<Terminal>,
terminal: Model<Terminal>,
generation: Task<()>,
message_id: Option<String>,
transaction: Option<TerminalTransaction>,
}
impl Codegen {
pub fn new(terminal: Entity<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self {
terminal,
telemetry,
@@ -1128,7 +1081,7 @@ impl Codegen {
}
}
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut Context<Self>) {
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
@@ -1228,20 +1181,20 @@ impl Codegen {
cx.notify();
}
pub fn stop(&mut self, cx: &mut Context<Self>) {
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished);
cx.notify();
}
pub fn complete(&mut self, cx: &mut Context<Self>) {
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.complete(cx);
}
}
pub fn undo(&mut self, cx: &mut Context<Self>) {
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.undo(cx);
}

View File

@@ -3,9 +3,9 @@ use std::sync::Arc;
use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use gpui::{
list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, Length,
ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, TextStyleRefinement,
UnderlineStyle, WeakEntity,
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, UnderlineStyle, View, WeakView,
};
use language::LanguageRegistry;
use language_model::Role;
@@ -20,31 +20,30 @@ use crate::thread_store::ThreadStore;
use crate::ui::ContextPill;
pub struct ActiveThread {
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
thread_store: Entity<ThreadStore>,
thread: Entity<Thread>,
thread_store: Model<ThreadStore>,
thread: Model<Thread>,
messages: Vec<MessageId>,
list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>,
}
impl ActiveThread {
pub fn new(
thread: Entity<Thread>,
thread_store: Entity<ThreadStore>,
workspace: WeakEntity<Workspace>,
thread: Model<Thread>,
thread_store: Model<ThreadStore>,
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&thread, window, Self::handle_thread_event),
cx.subscribe(&thread, Self::handle_thread_event),
];
let mut this = Self {
@@ -56,8 +55,8 @@ impl ActiveThread {
messages: Vec::new(),
rendered_messages_by_id: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade();
move |ix, _: &mut Window, cx: &mut App| {
let this = cx.view().downgrade();
move |ix, cx: &mut WindowContext| {
this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
}
@@ -67,13 +66,13 @@ impl ActiveThread {
};
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(), cx);
}
this
}
pub fn thread(&self) -> &Entity<Thread> {
pub fn thread(&self) -> &Model<Thread> {
&self.thread
}
@@ -81,15 +80,15 @@ impl ActiveThread {
self.messages.is_empty()
}
pub fn summary(&self, cx: &App) -> Option<SharedString> {
pub fn summary(&self, cx: &AppContext) -> Option<SharedString> {
self.thread.read(cx).summary()
}
pub fn summary_or_default(&self, cx: &App) -> SharedString {
pub fn summary_or_default(&self, cx: &AppContext) -> SharedString {
self.thread.read(cx).summary_or_default()
}
pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
pub fn cancel_last_completion(&mut self, cx: &mut AppContext) -> bool {
self.last_error.take();
self.thread
.update(cx, |thread, _cx| thread.cancel_last_completion())
@@ -103,13 +102,7 @@ impl ActiveThread {
self.last_error.take();
}
fn push_message(
&mut self,
id: &MessageId,
text: String,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn push_message(&mut self, id: &MessageId, text: String, cx: &mut ViewContext<Self>) {
let old_len = self.messages.len();
self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1);
@@ -118,7 +111,7 @@ impl ActiveThread {
let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = TextSize::Small.rems(cx);
let mut text_style = window.text_style();
let mut text_style = cx.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
@@ -177,13 +170,12 @@ impl ActiveThread {
..Default::default()
};
let markdown = cx.new(|cx| {
let markdown = cx.new_view(|cx| {
Markdown::new(
text,
markdown_style,
Some(self.language_registry.clone()),
None,
window,
cx,
)
});
@@ -196,10 +188,9 @@ impl ActiveThread {
fn handle_thread_event(
&mut self,
_: &Entity<Thread>,
_: Model<Thread>,
event: &ThreadEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
ThreadEvent::ShowError(error) => {
@@ -215,7 +206,7 @@ impl ActiveThread {
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
markdown.update(cx, |markdown, cx| {
markdown.append(text, window, cx);
markdown.append(text, cx);
});
}
}
@@ -226,7 +217,7 @@ impl ActiveThread {
.message(*message_id)
.map(|message| message.text.clone())
{
self.push_message(message_id, message_text, window, cx);
self.push_message(message_id, message_text, cx);
}
self.thread_store
@@ -249,7 +240,7 @@ impl ActiveThread {
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);
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(
@@ -266,7 +257,7 @@ impl ActiveThread {
}
}
fn render_message(&self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
@@ -298,7 +289,7 @@ impl ActiveThread {
let styled_message = match message.role {
Role::User => v_flex()
.id(("message-container", ix))
.pt_2p5()
.py_1()
.px_2p5()
.child(
v_flex()
@@ -347,9 +338,10 @@ impl ActiveThread {
}
impl Render for ActiveThread {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.size_full()
.pt_1p5()
.child(list(self.list_state.clone()).flex_grow())
}
}

View File

@@ -24,7 +24,7 @@ use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use fs::Fs;
use gpui::{actions, App};
use gpui::{actions, AppContext};
use prompt_library::PromptBuilder;
use settings::Settings as _;
@@ -63,7 +63,7 @@ pub fn init(
fs: Arc<dyn Fs>,
client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>,
cx: &mut App,
cx: &mut AppContext,
) {
AssistantSettings::register(cx);
assistant_panel::init(cx);
@@ -84,7 +84,7 @@ pub fn init(
feature_gate_assistant2_actions(cx);
}
fn feature_gate_assistant2_actions(cx: &mut App) {
fn feature_gate_assistant2_actions(cx: &mut AppContext) {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE);
});

View File

@@ -1,9 +1,9 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
use gpui::{Action, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
use ui::{prelude::*, ElevationIndex};
use zed_actions::assistant::DeployPromptLibrary;
pub struct AssistantConfiguration {
@@ -13,17 +13,16 @@ pub struct AssistantConfiguration {
}
impl AssistantConfiguration {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe_in(
let registry_subscription = cx.subscribe(
&LanguageModelRegistry::global(cx),
window,
|this, _, event: &language_model::Event, window, cx| match event {
|this, _, event: &language_model::Event, cx| match event {
language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider {
this.add_provider_configuration_view(&provider, window, cx);
this.add_provider_configuration_view(&provider, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
@@ -38,14 +37,14 @@ impl AssistantConfiguration {
configuration_views_by_provider: HashMap::default(),
_registry_subscription: registry_subscription,
};
this.build_provider_configuration_views(window, cx);
this.build_provider_configuration_views(cx);
this
}
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn build_provider_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_provider_configuration_view(&provider, window, cx);
self.add_provider_configuration_view(&provider, cx);
}
}
@@ -56,17 +55,16 @@ impl AssistantConfiguration {
fn add_provider_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
let configuration_view = provider.configuration_view(window, cx);
let configuration_view = provider.configuration_view(cx);
self.configuration_views_by_provider
.insert(provider.id(), configuration_view);
}
}
impl Focusable for AssistantConfiguration {
fn focus_handle(&self, _: &App) -> FocusHandle {
impl FocusableView for AssistantConfiguration {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
@@ -81,7 +79,7 @@ impl AssistantConfiguration {
fn render_provider_configuration(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
@@ -91,47 +89,38 @@ impl AssistantConfiguration {
.cloned();
v_flex()
.gap_1p5()
.gap_2()
.child(
h_flex()
.justify_between()
.child(
h_flex()
.gap_2()
.child(
Icon::new(provider.icon())
.size(IconSize::Small)
.color(Color::Muted),
)
.child(Label::new(provider_name.clone())),
)
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
.when(provider.is_authenticated(cx), |parent| {
parent.child(
Button::new(
SharedString::from(format!("new-thread-{provider_id}")),
"Start New Thread",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.on_click(cx.listener({
let provider = provider.clone();
move |_this, _event, _window, cx| {
cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(),
))
}
})),
h_flex().justify_end().child(
Button::new(
SharedString::from(format!("new-thread-{provider_id}")),
"Open New Thread",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.on_click(cx.listener({
let provider = provider.clone();
move |_this, _event, cx| {
cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(),
))
}
})),
),
)
}),
)
.child(
div()
.p(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().editor_background)
.bg(cx.theme().colors().surface_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
@@ -146,49 +135,34 @@ impl AssistantConfiguration {
}
impl Render for AssistantConfiguration {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.id("assistant-configuration")
.track_focus(&self.focus_handle(cx))
.bg(cx.theme().colors().panel_background)
.bg(cx.theme().colors().editor_background)
.size_full()
.overflow_y_scroll()
.child(
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.gap_1()
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
.child(
Button::new("open-prompt-library", "Open Prompt Library")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.full_width()
.icon(IconName::Book)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, _window, cx| {
cx.dispatch_action(&DeployPromptLibrary)
}),
),
h_flex().p(DynamicSpacing::Base16.rems(cx)).child(
Button::new("open-prompt-library", "Open Prompt Library")
.style(ButtonStyle::Filled)
.full_width()
.icon(IconName::Book)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, cx| {
cx.dispatch_action(DeployPromptLibrary.boxed_clone())
}),
),
)
.child(Divider::horizontal().color(DividerColor::Border))
.child(
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.mt_1()
.gap_6()
.flex_1()
.child(
v_flex()
.gap_0p5()
.child(Headline::new("LLM Providers").size(HeadlineSize::Small))
.child(
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
),
)
.children(
providers
.into_iter()

View File

@@ -1,6 +1,6 @@
use assistant_settings::AssistantSettings;
use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString};
use gpui::{FocusHandle, View};
use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file;
@@ -10,7 +10,7 @@ use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::ToggleModelSelector;
pub struct AssistantModelSelector {
selector: Entity<LanguageModelSelector>,
selector: View<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
}
@@ -20,11 +20,10 @@ impl AssistantModelSelector {
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Self {
Self {
selector: cx.new(|cx| {
selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
@@ -34,7 +33,6 @@ impl AssistantModelSelector {
move |settings, _cx| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
@@ -45,13 +43,9 @@ impl AssistantModelSelector {
}
impl Render for AssistantModelSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.focus_handle.clone();
let model_name = match active_model {
Some(model) => model.name().0,
_ => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
@@ -61,29 +55,33 @@ impl Render for AssistantModelSelector {
h_flex()
.gap_0p5()
.child(
div().max_w_32().child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted)
.text_ellipsis()
.into_any_element(),
),
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match active_model {
Some(model) => h_flex()
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
}),
)
.with_handle(self.menu_handle.clone())
}

View File

@@ -14,8 +14,9 @@ use client::zed_urls;
use editor::Editor;
use fs::Fs;
use gpui::{
prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, Corner, EventEmitter,
FocusHandle, FocusableView, FontWeight, Model, Pixels, Subscription, Task, UpdateGlobal, View,
ViewContext, WeakView, WindowContext,
};
use language::LanguageRegistry;
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
@@ -40,38 +41,38 @@ use crate::{
OpenPromptEditorHistory,
};
pub fn init(cx: &mut App) {
cx.observe_new(
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace
.register_action(|workspace, _: &NewThread, window, cx| {
.register_action(|workspace, _: &NewThread, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| panel.new_thread(window, cx));
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.new_thread(cx));
workspace.focus_panel::<AssistantPanel>(cx);
}
})
.register_action(|workspace, _: &OpenHistory, window, cx| {
.register_action(|workspace, _: &OpenHistory, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_history(window, cx));
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_history(cx));
}
})
.register_action(|workspace, _: &NewPromptEditor, window, cx| {
.register_action(|workspace, _: &NewPromptEditor, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.new_prompt_editor(cx));
}
})
.register_action(|workspace, _: &OpenPromptEditorHistory, window, cx| {
.register_action(|workspace, _: &OpenPromptEditorHistory, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(window, cx));
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(cx));
}
})
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
.register_action(|workspace, _: &OpenConfiguration, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_configuration(cx));
}
});
},
@@ -88,22 +89,22 @@ enum ActiveView {
}
pub struct AssistantPanel {
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
workspace: WeakView<Workspace>,
project: Model<Project>,
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
thread_store: Entity<ThreadStore>,
thread: Entity<ActiveThread>,
message_editor: Entity<MessageEditor>,
context_store: Entity<assistant_context_editor::ContextStore>,
context_editor: Option<Entity<ContextEditor>>,
context_history: Option<Entity<ContextHistory>>,
configuration: Option<Entity<AssistantConfiguration>>,
thread_store: Model<ThreadStore>,
thread: View<ActiveThread>,
message_editor: View<MessageEditor>,
context_store: Model<assistant_context_editor::ContextStore>,
context_editor: Option<View<ContextEditor>>,
context_history: Option<View<ContextHistory>>,
configuration: Option<View<AssistantConfiguration>>,
configuration_subscription: Option<Subscription>,
tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset,
active_view: ActiveView,
history: Entity<ThreadHistory>,
history: View<ThreadHistory>,
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
width: Option<Pixels>,
@@ -112,21 +113,20 @@ pub struct AssistantPanel {
impl AssistantPanel {
pub fn load(
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
prompt_builder: Arc<PromptBuilder>,
cx: AsyncWindowContext,
) -> Task<Result<Entity<Self>>> {
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default());
log::info!("[assistant2-debug] initializing ThreadStore");
let thread_store = workspace.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
ThreadStore::new(project, tools.clone(), cx)
})??;
log::info!("[assistant2-debug] finished initializing ThreadStore");
let thread_store = workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
ThreadStore::new(project, tools.clone(), cx)
})?
.await?;
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
log::info!("[assistant2-debug] initializing ContextStore");
let context_store = workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
@@ -134,41 +134,38 @@ impl AssistantPanel {
project,
prompt_builder.clone(),
slash_commands,
tools.clone(),
cx,
)
})?
.await?;
log::info!("[assistant2-debug] finished initializing ContextStore");
workspace.update_in(&mut cx, |workspace, window, cx| {
cx.new(|cx| Self::new(workspace, thread_store, context_store, tools, window, cx))
workspace.update(&mut cx, |workspace, cx| {
cx.new_view(|cx| Self::new(workspace, thread_store, context_store, tools, cx))
})
})
}
fn new(
workspace: &Workspace,
thread_store: Entity<ThreadStore>,
context_store: Entity<assistant_context_editor::ContextStore>,
thread_store: Model<ThreadStore>,
context_store: Model<assistant_context_editor::ContextStore>,
tools: Arc<ToolWorkingSet>,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
log::info!("[assistant2-debug] AssistantPanel::new");
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone();
let project = workspace.project().clone();
let language_registry = project.read(cx).languages().clone();
let workspace = workspace.weak_handle();
let weak_self = cx.entity().downgrade();
let weak_self = cx.view().downgrade();
let message_editor = cx.new(|cx| {
let message_editor = cx.new_view(|cx| {
MessageEditor::new(
fs.clone(),
workspace.clone(),
thread_store.downgrade(),
thread.clone(),
window,
cx,
)
});
@@ -180,14 +177,13 @@ impl AssistantPanel {
fs: fs.clone(),
language_registry: language_registry.clone(),
thread_store: thread_store.clone(),
thread: cx.new(|cx| {
thread: cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
thread_store.clone(),
workspace,
language_registry,
tools.clone(),
window,
cx,
)
}),
@@ -202,7 +198,7 @@ impl AssistantPanel {
chrono::Local::now().offset().local_minus_utc(),
)
.unwrap(),
history: cx.new(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
new_item_context_menu_handle: PopoverMenuHandle::default(),
open_history_context_menu_handle: PopoverMenuHandle::default(),
width: None,
@@ -213,66 +209,58 @@ impl AssistantPanel {
pub fn toggle_focus(
workspace: &mut Workspace,
_: &ToggleFocus,
window: &mut Window,
cx: &mut Context<Workspace>,
cx: &mut ViewContext<Workspace>,
) {
let settings = AssistantSettings::get_global(cx);
if !settings.enabled {
return;
}
workspace.toggle_panel_focus::<Self>(window, cx);
workspace.toggle_panel_focus::<Self>(cx);
}
pub(crate) fn local_timezone(&self) -> UtcOffset {
self.local_timezone
}
pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
pub(crate) fn thread_store(&self) -> &Model<ThreadStore> {
&self.thread_store
}
fn cancel(
&mut self,
_: &editor::actions::Cancel,
_window: &mut Window,
cx: &mut Context<Self>,
) {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
self.thread
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
}
fn new_thread(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
let thread = self
.thread_store
.update(cx, |this, cx| this.create_thread(cx));
self.active_view = ActiveView::Thread;
self.thread = cx.new(|cx| {
self.thread = cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
self.thread_store.clone(),
self.workspace.clone(),
self.language_registry.clone(),
self.tools.clone(),
window,
cx,
)
});
self.message_editor = cx.new(|cx| {
self.message_editor = cx.new_view(|cx| {
MessageEditor::new(
self.fs.clone(),
self.workspace.clone(),
self.thread_store.downgrade(),
thread,
window,
cx,
)
});
self.message_editor.focus_handle(cx).focus(window);
self.message_editor.focus_handle(cx).focus(cx);
}
fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn new_prompt_editor(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::PromptEditor;
let context = self
@@ -282,31 +270,25 @@ impl AssistantPanel {
.log_err()
.flatten();
self.context_editor = Some(cx.new(|cx| {
self.context_editor = Some(cx.new_view(|cx| {
let mut editor = ContextEditor::for_context(
context,
self.fs.clone(),
self.workspace.clone(),
self.project.clone(),
lsp_adapter_delegate,
window,
cx,
);
editor.insert_default_prompt(window, cx);
editor.insert_default_prompt(cx);
editor
}));
if let Some(context_editor) = self.context_editor.as_ref() {
context_editor.focus_handle(cx).focus(window);
context_editor.focus_handle(cx).focus(cx);
}
}
fn deploy_prompt_library(
&mut self,
_: &DeployPromptLibrary,
_window: &mut Window,
cx: &mut Context<Self>,
) {
fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
open_prompt_library(
self.language_registry.clone(),
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
@@ -322,26 +304,25 @@ impl AssistantPanel {
.detach_and_log_err(cx);
}
fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(window);
self.history.focus_handle(cx).focus(cx);
cx.notify();
}
fn open_prompt_editor_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn open_prompt_editor_history(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::PromptEditorHistory;
self.context_history = Some(cx.new(|cx| {
self.context_history = Some(cx.new_view(|cx| {
ContextHistory::new(
self.project.clone(),
self.context_store.clone(),
self.workspace.clone(),
window,
cx,
)
}));
if let Some(context_history) = self.context_history.as_ref() {
context_history.focus_handle(cx).focus(window);
context_history.focus_handle(cx).focus(cx);
}
cx.notify();
@@ -350,8 +331,7 @@ impl AssistantPanel {
fn open_saved_prompt_editor(
&mut self,
path: PathBuf,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
let context = self
.context_store
@@ -362,17 +342,16 @@ impl AssistantPanel {
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let context = context.await?;
this.update_in(&mut cx, |this, window, cx| {
let editor = cx.new(|cx| {
this.update(&mut cx, |this, cx| {
let editor = cx.new_view(|cx| {
ContextEditor::for_context(
context,
fs,
workspace,
project,
lsp_adapter_delegate,
window,
cx,
)
});
@@ -388,64 +367,57 @@ impl AssistantPanel {
pub(crate) fn open_thread(
&mut self,
thread_id: &ThreadId,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
let open_thread_task = self
.thread_store
.update(cx, |this, cx| this.open_thread(thread_id, cx));
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let thread = open_thread_task.await?;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.active_view = ActiveView::Thread;
this.thread = cx.new(|cx| {
this.thread = cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
this.thread_store.clone(),
this.workspace.clone(),
this.language_registry.clone(),
this.tools.clone(),
window,
cx,
)
});
this.message_editor = cx.new(|cx| {
this.message_editor = cx.new_view(|cx| {
MessageEditor::new(
this.fs.clone(),
this.workspace.clone(),
this.thread_store.downgrade(),
thread,
window,
cx,
)
});
this.message_editor.focus_handle(cx).focus(window);
this.message_editor.focus_handle(cx).focus(cx);
})
})
}
pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
pub(crate) fn open_configuration(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::Configuration;
self.configuration = Some(cx.new(|cx| AssistantConfiguration::new(window, cx)));
self.configuration = Some(cx.new_view(AssistantConfiguration::new));
if let Some(configuration) = self.configuration.as_ref() {
self.configuration_subscription = Some(cx.subscribe_in(
configuration,
window,
Self::handle_assistant_configuration_event,
));
self.configuration_subscription =
Some(cx.subscribe(configuration, Self::handle_assistant_configuration_event));
configuration.focus_handle(cx).focus(window);
configuration.focus_handle(cx).focus(cx);
}
}
fn handle_assistant_configuration_event(
&mut self,
_entity: &Entity<AssistantConfiguration>,
_view: View<AssistantConfiguration>,
event: &AssistantConfigurationEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
AssistantConfigurationEvent::NewThread(provider) => {
@@ -464,24 +436,24 @@ impl AssistantPanel {
}
}
self.new_thread(window, cx);
self.new_thread(cx);
}
}
}
pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
self.thread.read(cx).thread().clone()
}
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut Context<Self>) {
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
self.thread_store
.update(cx, |this, cx| this.delete_thread(thread_id, cx))
.detach_and_log_err(cx);
}
}
impl Focusable for AssistantPanel {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx),
@@ -517,7 +489,7 @@ impl Panel for AssistantPanel {
"AssistantPanel2"
}
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
fn position(&self, cx: &WindowContext) -> DockPosition {
match AssistantSettings::get_global(cx).dock {
AssistantDockPosition::Left => DockPosition::Left,
AssistantDockPosition::Bottom => DockPosition::Bottom,
@@ -529,7 +501,7 @@ impl Panel for AssistantPanel {
true
}
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
settings::update_settings_file::<AssistantSettings>(
self.fs.clone(),
cx,
@@ -544,9 +516,9 @@ impl Panel for AssistantPanel {
);
}
fn size(&self, window: &Window, cx: &App) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
let settings = AssistantSettings::get_global(cx);
match self.position(window, cx) {
match self.position(cx) {
DockPosition::Left | DockPosition::Right => {
self.width.unwrap_or(settings.default_width)
}
@@ -554,21 +526,21 @@ impl Panel for AssistantPanel {
}
}
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
match self.position(window, cx) {
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
match self.position(cx) {
DockPosition::Left | DockPosition::Right => self.width = size,
DockPosition::Bottom => self.height = size,
}
cx.notify();
}
fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
fn remote_id() -> Option<proto::PanelId> {
Some(proto::PanelId::AssistantPanel)
}
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
let settings = AssistantSettings::get_global(cx);
if !settings.enabled || !settings.button {
return None;
@@ -577,7 +549,7 @@ impl Panel for AssistantPanel {
Some(IconName::ZedAssistant)
}
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
Some("Assistant Panel")
}
@@ -591,7 +563,7 @@ impl Panel for AssistantPanel {
}
impl AssistantPanel {
fn render_toolbar(&self, cx: &mut Context<Self>) -> impl IntoElement {
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let thread = self.thread.read(cx);
let title = match self.active_view {
@@ -611,16 +583,9 @@ impl AssistantPanel {
SharedString::from(context_editor.read(cx).title(cx).to_string())
})
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
ActiveView::History | ActiveView::PromptEditorHistory => "History".into(),
ActiveView::Configuration => "Assistant Settings".into(),
};
let sub_title = match self.active_view {
ActiveView::Thread => None,
ActiveView::PromptEditor => None,
ActiveView::History => Some("Thread"),
ActiveView::PromptEditorHistory => Some("Prompt Editor"),
ActiveView::Configuration => None,
ActiveView::History => "History / Thread".into(),
ActiveView::PromptEditorHistory => "History / Prompt Editor".into(),
ActiveView::Configuration => "Configuration".into(),
};
h_flex()
@@ -633,24 +598,7 @@ impl AssistantPanel {
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
h_flex()
.child(Label::new(title))
.when(sub_title.is_some(), |this| {
this.child(
h_flex()
.pl_1p5()
.gap_1p5()
.child(
Label::new("/")
.size(LabelSize::Small)
.color(Color::Disabled)
.alpha(0.5),
)
.child(Label::new(sub_title.unwrap())),
)
}),
)
.child(h_flex().child(Label::new(title)))
.child(
h_flex()
.h_full()
@@ -660,16 +608,16 @@ impl AssistantPanel {
.gap(DynamicSpacing::Base02.rems(cx))
.child(
PopoverMenu::new("assistant-toolbar-new-popover-menu")
.trigger_with_tooltip(
.trigger(
IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle),
Tooltip::text("New…"),
.style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("New…", cx)),
)
.anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone())
.menu(move |window, cx| {
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
.menu(move |cx| {
Some(ContextMenu::build(cx, |menu, _| {
menu.action("New Thread", NewThread.boxed_clone())
.action("New Prompt Editor", NewPromptEditor.boxed_clone())
}))
@@ -677,16 +625,16 @@ impl AssistantPanel {
)
.child(
PopoverMenu::new("assistant-toolbar-history-popover-menu")
.trigger_with_tooltip(
.trigger(
IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle),
Tooltip::text("History…"),
.style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("History…", cx)),
)
.anchor(Corner::TopRight)
.with_handle(self.open_history_context_menu_handle.clone())
.menu(move |window, cx| {
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
.menu(move |cx| {
Some(ContextMenu::build(cx, |menu, _| {
menu.action("Thread History", OpenHistory.boxed_clone())
.action(
"Prompt Editor History",
@@ -699,29 +647,23 @@ impl AssistantPanel {
IconButton::new("configure-assistant", IconName::Settings)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(Tooltip::text("Assistant Settings"))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
.on_click(move |_event, cx| {
cx.dispatch_action(OpenConfiguration.boxed_clone());
}),
),
)
}
fn render_active_thread_or_empty_state(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> AnyElement {
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
if self.thread.read(cx).is_empty() {
return self
.render_thread_empty_state(window, cx)
.into_any_element();
return self.render_thread_empty_state(cx).into_any_element();
}
self.thread.clone().into_any_element()
self.thread.clone().into_any()
}
fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
fn configuration_error(&self, cx: &AppContext) -> Option<ConfigurationError> {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return Some(ConfigurationError::NoProvider);
};
@@ -737,11 +679,7 @@ impl AssistantPanel {
None
}
fn render_thread_empty_state(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let recent_threads = self
.thread_store
.update(cx, |this, _cx| this.recent_threads(3));
@@ -791,8 +729,8 @@ impl AssistantPanel {
.icon(Some(IconName::Sliders))
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(cx.listener(|this, _, window, cx| {
this.open_configuration(window, cx);
.on_click(cx.listener(|this, _, cx| {
this.open_configuration(cx);
})),
),
),
@@ -837,7 +775,7 @@ impl AssistantPanel {
.child(v_flex().mx_auto().w_4_5().gap_2().children(
recent_threads.into_iter().map(|thread| {
// TODO: keyboard navigation
PastThread::new(thread, cx.entity().downgrade(), false)
PastThread::new(thread, cx.view().downgrade(), false)
}),
))
.child(
@@ -848,17 +786,17 @@ impl AssistantPanel {
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
}),
),
)
})
}
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let last_error = self.thread.read(cx).last_error()?;
Some(
@@ -884,7 +822,7 @@ impl AssistantPanel {
)
}
fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
v_flex()
@@ -908,7 +846,7 @@ impl AssistantPanel {
.justify_end()
.mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -918,7 +856,7 @@ impl AssistantPanel {
},
)))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -930,7 +868,7 @@ impl AssistantPanel {
.into_any()
}
fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
v_flex()
@@ -955,7 +893,7 @@ impl AssistantPanel {
.mt_1()
.child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, _, cx| {
cx.listener(|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -966,7 +904,7 @@ impl AssistantPanel {
),
)
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -981,7 +919,7 @@ impl AssistantPanel {
fn render_error_message(
&self,
error_message: &SharedString,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> AnyElement {
v_flex()
.gap_0p5()
@@ -1007,7 +945,7 @@ impl AssistantPanel {
.justify_end()
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -1021,23 +959,23 @@ impl AssistantPanel {
}
impl Render for AssistantPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.key_context("AssistantPanel2")
.justify_between()
.size_full()
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(|this, _: &NewThread, window, cx| {
this.new_thread(window, cx);
.on_action(cx.listener(|this, _: &NewThread, cx| {
this.new_thread(cx);
}))
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
this.open_history(window, cx);
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
this.open_history(cx);
}))
.on_action(cx.listener(Self::deploy_prompt_library))
.child(self.render_toolbar(cx))
.map(|parent| match self.active_view {
ActiveView::Thread => parent
.child(self.render_active_thread_or_empty_state(window, cx))
.child(self.render_active_thread_or_empty_state(cx))
.child(
h_flex()
.border_t_1()
@@ -1054,11 +992,11 @@ impl Render for AssistantPanel {
}
struct PromptLibraryInlineAssist {
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
}
impl PromptLibraryInlineAssist {
pub fn new(workspace: WeakEntity<Workspace>) -> Self {
pub fn new(workspace: WeakView<Workspace>) -> Self {
Self { workspace }
}
}
@@ -1066,25 +1004,21 @@ impl PromptLibraryInlineAssist {
impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
fn assist(
&self,
prompt_editor: &Entity<Editor>,
prompt_editor: &View<Editor>,
_initial_prompt: Option<String>,
window: &mut Window,
cx: &mut Context<PromptLibrary>,
cx: &mut ViewContext<PromptLibrary>,
) {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(&prompt_editor, self.workspace.clone(), None, window, cx)
assistant.assist(&prompt_editor, self.workspace.clone(), None, cx)
})
}
fn focus_assistant_panel(
&self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
cx: &mut ViewContext<Workspace>,
) -> bool {
workspace
.focus_panel::<AssistantPanel>(window, cx)
.is_some()
workspace.focus_panel::<AssistantPanel>(cx).is_some()
}
}
@@ -1094,9 +1028,8 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_context_editor(
&self,
workspace: &mut Workspace,
_window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<ContextEditor>> {
cx: &mut ViewContext<Workspace>,
) -> Option<View<ContextEditor>> {
let panel = workspace.panel::<AssistantPanel>(cx)?;
panel.update(cx, |panel, _cx| panel.context_editor.clone())
}
@@ -1105,25 +1038,21 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
&self,
workspace: &mut Workspace,
path: std::path::PathBuf,
window: &mut Window,
cx: &mut Context<Workspace>,
cx: &mut ViewContext<Workspace>,
) -> Task<Result<()>> {
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
return Task::ready(Err(anyhow!("Assistant panel not found")));
};
panel.update(cx, |panel, cx| {
panel.open_saved_prompt_editor(path, window, cx)
})
panel.update(cx, |panel, cx| panel.open_saved_prompt_editor(path, cx))
}
fn open_remote_context(
&self,
_workspace: &mut Workspace,
_context_id: assistant_context_editor::ContextId,
_window: &mut Window,
_cx: &mut Context<Workspace>,
) -> Task<Result<Entity<ContextEditor>>> {
_cx: &mut ViewContext<Workspace>,
) -> Task<Result<View<ContextEditor>>> {
Task::ready(Err(anyhow!("opening remote context not implemented")))
}
@@ -1131,8 +1060,7 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
&self,
_workspace: &mut Workspace,
_creases: Vec<(String, String)>,
_window: &mut Window,
_cx: &mut Context<Workspace>,
_cx: &mut ViewContext<Workspace>,
) {
}
}

View File

@@ -6,7 +6,7 @@ use client::telemetry::Telemetry;
use collections::HashSet;
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, StreamExt};
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
use gpui::{AppContext, Context as _, EventEmitter, Model, ModelContext, Subscription, Task};
use language::{Buffer, IndentKind, Point, TransactionId};
use language_model::{
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
@@ -32,14 +32,14 @@ use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
pub struct BufferCodegen {
alternatives: Vec<Entity<CodegenAlternative>>,
alternatives: Vec<Model<CodegenAlternative>>,
pub active_alternative: usize,
seen_alternatives: HashSet<usize>,
subscriptions: Vec<Subscription>,
buffer: Entity<MultiBuffer>,
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
context_store: Entity<ContextStore>,
context_store: Model<ContextStore>,
telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>,
pub is_insertion: bool,
@@ -47,15 +47,15 @@ pub struct BufferCodegen {
impl BufferCodegen {
pub fn new(
buffer: Entity<MultiBuffer>,
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
context_store: Entity<ContextStore>,
context_store: Model<ContextStore>,
telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Self {
let codegen = cx.new(|cx| {
let codegen = cx.new_model(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
@@ -83,7 +83,7 @@ impl BufferCodegen {
this
}
fn subscribe_to_alternative(&mut self, cx: &mut Context<Self>) {
fn subscribe_to_alternative(&mut self, cx: &mut ModelContext<Self>) {
let codegen = self.active_alternative().clone();
self.subscriptions.clear();
self.subscriptions
@@ -92,22 +92,22 @@ impl BufferCodegen {
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
}
pub fn active_alternative(&self) -> &Entity<CodegenAlternative> {
pub fn active_alternative(&self) -> &Model<CodegenAlternative> {
&self.alternatives[self.active_alternative]
}
pub fn status<'a>(&self, cx: &'a App) -> &'a CodegenStatus {
pub fn status<'a>(&self, cx: &'a AppContext) -> &'a CodegenStatus {
&self.active_alternative().read(cx).status
}
pub fn alternative_count(&self, cx: &App) -> usize {
pub fn alternative_count(&self, cx: &AppContext) -> usize {
LanguageModelRegistry::read_global(cx)
.inline_alternative_models()
.len()
+ 1
}
pub fn cycle_prev(&mut self, cx: &mut Context<Self>) {
pub fn cycle_prev(&mut self, cx: &mut ModelContext<Self>) {
let next_active_ix = if self.active_alternative == 0 {
self.alternatives.len() - 1
} else {
@@ -116,12 +116,12 @@ impl BufferCodegen {
self.activate(next_active_ix, cx);
}
pub fn cycle_next(&mut self, cx: &mut Context<Self>) {
pub fn cycle_next(&mut self, cx: &mut ModelContext<Self>) {
let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
self.activate(next_active_ix, cx);
}
fn activate(&mut self, index: usize, cx: &mut Context<Self>) {
fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
self.active_alternative()
.update(cx, |codegen, cx| codegen.set_active(false, cx));
self.seen_alternatives.insert(index);
@@ -132,7 +132,7 @@ impl BufferCodegen {
cx.notify();
}
pub fn start(&mut self, user_prompt: String, cx: &mut Context<Self>) -> Result<()> {
pub fn start(&mut self, user_prompt: String, cx: &mut ModelContext<Self>) -> Result<()> {
let alternative_models = LanguageModelRegistry::read_global(cx)
.inline_alternative_models()
.to_vec();
@@ -143,7 +143,7 @@ impl BufferCodegen {
self.alternatives.truncate(1);
for _ in 0..alternative_models.len() {
self.alternatives.push(cx.new(|cx| {
self.alternatives.push(cx.new_model(|cx| {
CodegenAlternative::new(
self.buffer.clone(),
self.range.clone(),
@@ -172,13 +172,13 @@ impl BufferCodegen {
Ok(())
}
pub fn stop(&mut self, cx: &mut Context<Self>) {
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
for codegen in &self.alternatives {
codegen.update(cx, |codegen, cx| codegen.stop(cx));
}
}
pub fn undo(&mut self, cx: &mut Context<Self>) {
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
self.active_alternative()
.update(cx, |codegen, cx| codegen.undo(cx));
@@ -190,27 +190,27 @@ impl BufferCodegen {
});
}
pub fn buffer(&self, cx: &App) -> Entity<MultiBuffer> {
pub fn buffer(&self, cx: &AppContext) -> Model<MultiBuffer> {
self.active_alternative().read(cx).buffer.clone()
}
pub fn old_buffer(&self, cx: &App) -> Entity<Buffer> {
pub fn old_buffer(&self, cx: &AppContext) -> Model<Buffer> {
self.active_alternative().read(cx).old_buffer.clone()
}
pub fn snapshot(&self, cx: &App) -> MultiBufferSnapshot {
pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
self.active_alternative().read(cx).snapshot.clone()
}
pub fn edit_position(&self, cx: &App) -> Option<Anchor> {
pub fn edit_position(&self, cx: &AppContext) -> Option<Anchor> {
self.active_alternative().read(cx).edit_position
}
pub fn diff<'a>(&self, cx: &'a App) -> &'a Diff {
pub fn diff<'a>(&self, cx: &'a AppContext) -> &'a Diff {
&self.active_alternative().read(cx).diff
}
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
pub fn last_equal_ranges<'a>(&self, cx: &'a AppContext) -> &'a [Range<Anchor>] {
self.active_alternative().read(cx).last_equal_ranges()
}
}
@@ -218,8 +218,8 @@ impl BufferCodegen {
impl EventEmitter<CodegenEvent> for BufferCodegen {}
pub struct CodegenAlternative {
buffer: Entity<MultiBuffer>,
old_buffer: Entity<Buffer>,
buffer: Model<MultiBuffer>,
old_buffer: Model<Buffer>,
snapshot: MultiBufferSnapshot,
edit_position: Option<Anchor>,
range: Range<Anchor>,
@@ -228,7 +228,7 @@ pub struct CodegenAlternative {
status: CodegenStatus,
generation: Task<()>,
diff: Diff,
context_store: Option<Entity<ContextStore>>,
context_store: Option<Model<ContextStore>>,
telemetry: Option<Arc<Telemetry>>,
_subscription: gpui::Subscription,
builder: Arc<PromptBuilder>,
@@ -245,13 +245,13 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
impl CodegenAlternative {
pub fn new(
buffer: Entity<MultiBuffer>,
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
active: bool,
context_store: Option<Entity<ContextStore>>,
context_store: Option<Model<ContextStore>>,
telemetry: Option<Arc<Telemetry>>,
builder: Arc<PromptBuilder>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Self {
let snapshot = buffer.read(cx).snapshot(cx);
@@ -259,7 +259,7 @@ impl CodegenAlternative {
.range_to_buffer_ranges(range.clone())
.pop()
.unwrap();
let old_buffer = cx.new(|cx| {
let old_buffer = cx.new_model(|cx| {
let text = old_buffer.as_rope().clone();
let line_ending = old_buffer.line_ending();
let language = old_buffer.language().cloned();
@@ -303,7 +303,7 @@ impl CodegenAlternative {
}
}
pub fn set_active(&mut self, active: bool, cx: &mut Context<Self>) {
pub fn set_active(&mut self, active: bool, cx: &mut ModelContext<Self>) {
if active != self.active {
self.active = active;
@@ -327,9 +327,9 @@ impl CodegenAlternative {
fn handle_buffer_event(
&mut self,
_buffer: Entity<MultiBuffer>,
_buffer: Model<MultiBuffer>,
event: &multi_buffer::Event,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
if self.transformation_transaction_id == Some(*transaction_id) {
@@ -348,7 +348,7 @@ impl CodegenAlternative {
&mut self,
user_prompt: String,
model: Arc<dyn LanguageModel>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Result<()> {
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
self.buffer.update(cx, |buffer, cx| {
@@ -375,7 +375,11 @@ impl CodegenAlternative {
Ok(())
}
fn build_request(&self, user_prompt: String, cx: &mut App) -> Result<LanguageModelRequest> {
fn build_request(
&self,
user_prompt: String,
cx: &mut AppContext,
) -> Result<LanguageModelRequest> {
let buffer = self.buffer.read(cx).snapshot(cx);
let language = buffer.language_at(self.range.start);
let language_name = if let Some(language) = language.as_ref() {
@@ -434,7 +438,7 @@ impl CodegenAlternative {
model_provider_id: String,
model_api_key: Option<String>,
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let start_time = Instant::now();
let snapshot = self.snapshot.clone();
@@ -692,7 +696,7 @@ impl CodegenAlternative {
cx.notify();
}
pub fn stop(&mut self, cx: &mut Context<Self>) {
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.last_equal_ranges.clear();
if self.diff.is_empty() {
self.status = CodegenStatus::Idle;
@@ -704,7 +708,7 @@ impl CodegenAlternative {
cx.notify();
}
pub fn undo(&mut self, cx: &mut Context<Self>) {
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
self.buffer.update(cx, |buffer, cx| {
if let Some(transaction_id) = self.transformation_transaction_id.take() {
buffer.undo_transaction(transaction_id, cx);
@@ -716,7 +720,7 @@ impl CodegenAlternative {
fn apply_edits(
&mut self,
edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
cx: &mut Context<CodegenAlternative>,
cx: &mut ModelContext<CodegenAlternative>,
) {
let transaction = self.buffer.update(cx, |buffer, cx| {
// Avoid grouping assistant edits with user edits.
@@ -743,7 +747,7 @@ impl CodegenAlternative {
fn reapply_line_based_diff(
&mut self,
line_operations: impl IntoIterator<Item = LineOperation>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let old_snapshot = self.snapshot.clone();
let old_range = self.range.to_point(&old_snapshot);
@@ -799,7 +803,7 @@ impl CodegenAlternative {
}
}
fn reapply_batch_diff(&mut self, cx: &mut Context<Self>) -> Task<()> {
fn reapply_batch_diff(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
let old_snapshot = self.snapshot.clone();
let old_range = self.range.to_point(&old_snapshot);
let new_snapshot = self.buffer.read(cx).snapshot(cx);
@@ -1077,14 +1081,15 @@ mod tests {
}
}
"};
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new(|cx| {
let codegen = cx.new_model(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
@@ -1141,14 +1146,15 @@ mod tests {
le
}
"};
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new(|cx| {
let codegen = cx.new_model(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
@@ -1208,14 +1214,15 @@ mod tests {
" \n",
"}\n" //
);
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new(|cx| {
let codegen = cx.new_model(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
@@ -1275,14 +1282,14 @@ mod tests {
\t}
}
"};
let buffer = cx.new(|cx| Buffer::local(text, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new(|cx| {
let codegen = cx.new_model(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
@@ -1330,14 +1337,15 @@ mod tests {
let x = 0;
}
"};
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new(|cx| {
let codegen = cx.new_model(|cx| {
CodegenAlternative::new(
buffer.clone(),
range.clone(),
@@ -1424,7 +1432,7 @@ mod tests {
}
fn simulate_response_stream(
codegen: Entity<CodegenAlternative>,
codegen: Model<CodegenAlternative>,
cx: &mut TestAppContext,
) -> mpsc::UnboundedSender<String> {
let (chunks_tx, chunks_rx) = mpsc::unbounded();

View File

@@ -2,7 +2,7 @@ use std::path::Path;
use std::rc::Rc;
use file_icons::FileIcons;
use gpui::{App, Entity, SharedString};
use gpui::{AppContext, Model, SharedString};
use language::Buffer;
use language_model::{LanguageModelRequestMessage, MessageContent};
use serde::{Deserialize, Serialize};
@@ -63,14 +63,14 @@ impl ContextKind {
}
#[derive(Debug)]
pub enum AssistantContext {
pub enum Context {
File(FileContext),
Directory(DirectoryContext),
FetchedUrl(FetchedUrlContext),
Thread(ThreadContext),
}
impl AssistantContext {
impl Context {
pub fn id(&self) -> ContextId {
match self {
Self::File(file) => file.id,
@@ -107,7 +107,7 @@ pub struct FetchedUrlContext {
#[derive(Debug)]
pub struct ThreadContext {
pub id: ContextId,
pub thread: Entity<Thread>,
pub thread: Model<Thread>,
pub text: SharedString,
}
@@ -117,13 +117,13 @@ pub struct ThreadContext {
#[derive(Debug, Clone)]
pub struct ContextBuffer {
pub id: BufferId,
pub buffer: Entity<Buffer>,
pub buffer: Model<Buffer>,
pub version: clock::Global,
pub text: SharedString,
}
impl AssistantContext {
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
impl Context {
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
match &self {
Self::File(file_context) => file_context.snapshot(cx),
Self::Directory(directory_context) => Some(directory_context.snapshot()),
@@ -134,7 +134,7 @@ impl AssistantContext {
}
impl FileContext {
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
let buffer = self.context_buffer.buffer.read(cx);
let path = buffer_path_log_err(buffer)?;
let full_path: SharedString = path.to_string_lossy().into_owned().into();
@@ -221,7 +221,7 @@ impl FetchedUrlContext {
}
impl ThreadContext {
pub fn snapshot(&self, cx: &App) -> ContextSnapshot {
pub fn snapshot(&self, cx: &AppContext) -> ContextSnapshot {
let thread = self.thread.read(cx);
ContextSnapshot {
id: self.id,

View File

@@ -9,7 +9,10 @@ use std::sync::Arc;
use anyhow::{anyhow, Result};
use editor::Editor;
use file_context_picker::render_file_context_entry;
use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity};
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Task, View, WeakModel,
WeakView,
};
use project::ProjectPath;
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
@@ -32,38 +35,33 @@ pub enum ConfirmBehavior {
#[derive(Debug, Clone)]
enum ContextPickerMode {
Default(Entity<ContextMenu>),
File(Entity<FileContextPicker>),
Directory(Entity<DirectoryContextPicker>),
Fetch(Entity<FetchContextPicker>),
Thread(Entity<ThreadContextPicker>),
Default(View<ContextMenu>),
File(View<FileContextPicker>),
Directory(View<DirectoryContextPicker>),
Fetch(View<FetchContextPicker>),
Thread(View<ThreadContextPicker>),
}
pub(super) struct ContextPicker {
mode: ContextPickerMode,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
workspace: WeakView<Workspace>,
editor: WeakView<Editor>,
context_store: WeakModel<ContextStore>,
thread_store: Option<WeakModel<ThreadStore>>,
confirm_behavior: ConfirmBehavior,
}
impl ContextPicker {
pub fn new(
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
context_store: WeakEntity<ContextStore>,
editor: WeakEntity<Editor>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
editor: WeakView<Editor>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
ContextPicker {
mode: ContextPickerMode::Default(ContextMenu::build(
window,
cx,
|menu, _window, _cx| menu,
)),
mode: ContextPickerMode::Default(ContextMenu::build(cx, |menu, _cx| menu)),
workspace,
context_store,
thread_store,
@@ -72,15 +70,15 @@ impl ContextPicker {
}
}
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
pub fn init(&mut self, cx: &mut ViewContext<Self>) {
self.mode = ContextPickerMode::Default(self.build_menu(cx));
cx.notify();
}
fn build_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<ContextMenu> {
let context_picker = cx.entity().clone();
fn build_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
let context_picker = cx.view().clone();
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
let menu = ContextMenu::build(cx, move |menu, cx| {
let recent = self.recent_entries(cx);
let has_recent = !recent.is_empty();
let recent_entries = recent
@@ -99,7 +97,7 @@ impl ContextPicker {
let menu = menu
.when(has_recent, |menu| {
menu.custom_row(|_, _| {
menu.custom_row(|_| {
div()
.mb_1()
.child(
@@ -119,8 +117,8 @@ impl ContextPicker {
.icon(kind.icon())
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.handler(move |window, cx| {
context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
.handler(move |cx| {
context_picker.update(cx, |this, cx| this.select_kind(kind, cx))
})
}));
@@ -143,56 +141,52 @@ impl ContextPicker {
self.thread_store.is_some()
}
fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
let context_picker = cx.entity().downgrade();
fn select_kind(&mut self, kind: ContextKind, cx: &mut ViewContext<Self>) {
let context_picker = cx.view().downgrade();
match kind {
ContextKind::File => {
self.mode = ContextPickerMode::File(cx.new(|cx| {
self.mode = ContextPickerMode::File(cx.new_view(|cx| {
FileContextPicker::new(
context_picker.clone(),
self.workspace.clone(),
self.editor.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Directory => {
self.mode = ContextPickerMode::Directory(cx.new(|cx| {
self.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
DirectoryContextPicker::new(
context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::FetchedUrl => {
self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
self.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
FetchContextPicker::new(
context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Thread => {
if let Some(thread_store) = self.thread_store.as_ref() {
self.mode = ContextPickerMode::Thread(cx.new(|cx| {
self.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
ThreadContextPicker::new(
thread_store.clone(),
context_picker.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
@@ -201,12 +195,12 @@ impl ContextPicker {
}
cx.notify();
cx.focus_self(window);
cx.focus_self();
}
fn recent_menu_item(
&self,
context_picker: Entity<ContextPicker>,
context_picker: View<ContextPicker>,
ix: usize,
entry: RecentEntry,
) -> ContextMenuItem {
@@ -219,7 +213,7 @@ impl ContextPicker {
let path = project_path.path.clone();
ContextMenuItem::custom_entry(
move |_window, cx| {
move |cx| {
render_file_context_entry(
ElementId::NamedInteger("ctx-recent".into(), ix),
&path,
@@ -229,9 +223,9 @@ impl ContextPicker {
)
.into_any()
},
move |window, cx| {
move |cx| {
context_picker.update(cx, |this, cx| {
this.add_recent_file(project_path.clone(), window, cx);
this.add_recent_file(project_path.clone(), cx);
})
},
)
@@ -241,11 +235,11 @@ impl ContextPicker {
let view_thread = thread.clone();
ContextMenuItem::custom_entry(
move |_window, cx| {
move |cx| {
render_thread_context_entry(&view_thread, context_store.clone(), cx)
.into_any()
},
move |_window, cx| {
move |cx| {
context_picker.update(cx, |this, cx| {
this.add_recent_thread(thread.clone(), cx)
.detach_and_log_err(cx);
@@ -256,12 +250,7 @@ impl ContextPicker {
}
}
fn add_recent_file(
&self,
project_path: ProjectPath,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn add_recent_file(&self, project_path: ProjectPath, cx: &mut ViewContext<Self>) {
let Some(context_store) = self.context_store.upgrade() else {
return;
};
@@ -270,10 +259,8 @@ impl ContextPicker {
context_store.add_file_from_path(project_path.clone(), cx)
});
cx.spawn_in(window, |_, mut cx| async move {
task.await.notify_async_err(&mut cx)
})
.detach();
cx.spawn(|_, mut cx| async move { task.await.notify_async_err(&mut cx) })
.detach();
cx.notify();
}
@@ -281,7 +268,7 @@ impl ContextPicker {
fn add_recent_thread(
&self,
thread: ThreadContextEntry,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
let Some(context_store) = self.context_store.upgrade() else {
return Task::ready(Err(anyhow!("context store not available")));
@@ -306,7 +293,7 @@ impl ContextPicker {
})
}
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
fn recent_entries(&self, cx: &mut WindowContext) -> Vec<RecentEntry> {
let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
return vec![];
};
@@ -376,7 +363,7 @@ impl ContextPicker {
recent
}
fn active_singleton_buffer_path(workspace: &Workspace, cx: &App) -> Option<PathBuf> {
fn active_singleton_buffer_path(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
let active_item = workspace.active_item(cx)?;
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
@@ -389,8 +376,8 @@ impl ContextPicker {
impl EventEmitter<DismissEvent> for ContextPicker {}
impl Focusable for ContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for ContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match &self.mode {
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
@@ -402,7 +389,7 @@ impl Focusable for ContextPicker {
}
impl Render for ContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.w(px(400.))
.min_w(px(400.))

View File

@@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use fuzzy::PathMatch;
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{prelude::*, ListItem};
@@ -14,17 +14,16 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct DirectoryContextPicker {
picker: Entity<Picker<DirectoryContextPickerDelegate>>,
picker: View<Picker<DirectoryContextPickerDelegate>>,
}
impl DirectoryContextPicker {
pub fn new(
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = DirectoryContextPickerDelegate::new(
context_picker,
@@ -32,28 +31,28 @@ impl DirectoryContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl Focusable for DirectoryContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for DirectoryContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for DirectoryContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct DirectoryContextPickerDelegate {
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
@@ -61,9 +60,9 @@ pub struct DirectoryContextPickerDelegate {
impl DirectoryContextPickerDelegate {
pub fn new(
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
@@ -80,8 +79,8 @@ impl DirectoryContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Entity<Workspace>,
cx: &mut Context<Picker<Self>>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -147,25 +146,15 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search folders…".into()
}
fn update_matches(
&mut self,
query: String,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
@@ -184,7 +173,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
@@ -205,19 +194,19 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
};
let confirm_behavior = self.confirm_behavior;
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
match task.await.notify_async_err(&mut cx) {
None => anyhow::Ok(()),
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}),
}
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |_, cx| {
cx.emit(DismissEvent);
@@ -229,8 +218,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
let directory_name = path_match.path.to_string_lossy().to_string();

View File

@@ -4,28 +4,27 @@ use std::sync::Arc;
use anyhow::{bail, Context as _, Result};
use futures::AsyncReadExt as _;
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClientWithUrl};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, Context, ListItem, Window};
use ui::{prelude::*, ListItem, ViewContext};
use workspace::Workspace;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FetchContextPicker {
picker: Entity<Picker<FetchContextPickerDelegate>>,
picker: View<Picker<FetchContextPickerDelegate>>,
}
impl FetchContextPicker {
pub fn new(
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FetchContextPickerDelegate::new(
context_picker,
@@ -33,20 +32,20 @@ impl FetchContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl Focusable for FetchContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for FetchContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FetchContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -59,18 +58,18 @@ enum ContentType {
}
pub struct FetchContextPickerDelegate {
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
url: String,
}
impl FetchContextPickerDelegate {
pub fn new(
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
FetchContextPickerDelegate {
@@ -167,7 +166,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
}
}
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
"Enter the URL that you would like to fetch".into()
}
@@ -175,30 +174,19 @@ impl PickerDelegate for FetchContextPickerDelegate {
0
}
fn set_selected_index(
&mut self,
_ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
}
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Enter a URL…".into()
}
fn update_matches(
&mut self,
query: String,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
self.url = query;
Task::ready(())
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
@@ -206,13 +194,13 @@ impl PickerDelegate for FetchContextPickerDelegate {
let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone();
let confirm_behavior = self.confirm_behavior;
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let text = cx
.background_executor()
.spawn(Self::build_message(http_client, url.clone()))
.await?;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, _cx| {
@@ -221,7 +209,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
@@ -232,7 +220,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |_, cx| {
cx.emit(DismissEvent);
@@ -244,8 +232,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let added = self.context_store.upgrade().map_or(false, |context_store| {
context_store.read(cx).includes_url(&self.url).is_some()

View File

@@ -11,8 +11,8 @@ use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
use file_icons::FileIcons;
use fuzzy::PathMatch;
use gpui::{
AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
WeakEntity,
AnyElement, AppContext, DismissEvent, Empty, FocusHandle, FocusableView, Stateful, Task, View,
WeakModel, WeakView,
};
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
use picker::{Picker, PickerDelegate};
@@ -27,18 +27,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::{ContextStore, FileInclusion};
pub struct FileContextPicker {
picker: Entity<Picker<FileContextPickerDelegate>>,
picker: View<Picker<FileContextPickerDelegate>>,
}
impl FileContextPicker {
pub fn new(
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
editor: WeakView<Editor>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FileContextPickerDelegate::new(
context_picker,
@@ -47,29 +46,29 @@ impl FileContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl Focusable for FileContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for FileContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FileContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct FileContextPickerDelegate {
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
editor: WeakView<Editor>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
@@ -77,10 +76,10 @@ pub struct FileContextPickerDelegate {
impl FileContextPickerDelegate {
pub fn new(
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
editor: WeakView<Editor>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
@@ -98,9 +97,8 @@ impl FileContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Entity<Workspace>,
cx: &mut Context<Picker<Self>>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -124,7 +122,7 @@ impl FileContextPickerDelegate {
let file_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.files(false, 0).map(move |entry| PathMatch {
worktree.files(true, 0).map(move |entry| PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
@@ -182,32 +180,22 @@ impl PickerDelegate for FileContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search files…".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
// TODO: This should be probably be run in the background.
let paths = search_task.await;
@@ -218,7 +206,7 @@ impl PickerDelegate for FileContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
@@ -243,7 +231,7 @@ impl PickerDelegate for FileContextPickerDelegate {
};
editor.update(cx, |editor, cx| {
editor.transact(window, cx, |editor, window, cx| {
editor.transact(cx, |editor, cx| {
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
{
let mut selections = editor.selections.all::<MultiBufferPoint>(cx);
@@ -259,9 +247,7 @@ impl PickerDelegate for FileContextPickerDelegate {
}
}
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select(selections)
});
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
}
let start_anchors = {
@@ -274,7 +260,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.collect::<Vec<_>>()
};
editor.insert(&full_path, window, cx);
editor.insert(&full_path, cx);
let end_anchors = {
let snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -286,15 +272,14 @@ impl PickerDelegate for FileContextPickerDelegate {
.collect::<Vec<_>>()
};
editor.insert("\n", window, cx); // Needed to end the fold
editor.insert("\n", cx); // Needed to end the fold
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(IconName::File, file_name.into()),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
let render_trailer = move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
let buffer = editor.buffer().read(cx).snapshot(cx);
let mut rows_to_fold = BTreeSet::new();
@@ -315,7 +300,7 @@ impl PickerDelegate for FileContextPickerDelegate {
editor.insert_creases(crease_iter, cx);
for buffer_row in rows_to_fold {
editor.fold_at(&FoldAt { buffer_row }, window, cx);
editor.fold_at(&FoldAt { buffer_row }, cx);
}
});
});
@@ -331,19 +316,19 @@ impl PickerDelegate for FileContextPickerDelegate {
};
let confirm_behavior = self.confirm_behavior;
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
match task.await.notify_async_err(&mut cx) {
None => anyhow::Ok(()),
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}),
}
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |_, cx| {
cx.emit(DismissEvent);
@@ -355,8 +340,7 @@ impl PickerDelegate for FileContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
@@ -379,8 +363,8 @@ pub fn render_file_context_entry(
id: ElementId,
path: &Path,
path_prefix: &Arc<str>,
context_store: WeakEntity<ContextStore>,
cx: &App,
context_store: WeakModel<ContextStore>,
cx: &WindowContext,
) -> Stateful<Div> {
let (file_name, directory) = if path == Path::new("") {
(SharedString::from(path_prefix.clone()), None)
@@ -453,7 +437,7 @@ pub fn render_file_context_entry(
)
.child(Label::new("Included").size(LabelSize::Small)),
)
.tooltip(Tooltip::text(format!("in {dir_name}")))
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
}
})
}
@@ -461,8 +445,8 @@ pub fn render_file_context_entry(
fn render_fold_icon_button(
icon: IconName,
label: SharedString,
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
Arc::new(move |fold_id, _fold_range, _window, _cx| {
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
Arc::new(move |fold_id, _fold_range, _cx| {
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
@@ -477,14 +461,13 @@ fn fold_toggle(
) -> impl Fn(
MultiBufferRow,
bool,
Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
&mut Window,
&mut App,
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
&mut WindowContext,
) -> AnyElement {
move |row, is_folded, fold, _window, _cx| {
move |row, is_folded, fold, _cx| {
Disclosure::new((name, row.0 as u64), !is_folded)
.toggle_state(is_folded)
.on_click(move |_e, window, cx| fold(!is_folded, window, cx))
.on_click(move |_e, cx| fold(!is_folded, cx))
.into_any_element()
}
}

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use fuzzy::StringMatchCandidate;
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem};
@@ -11,17 +11,16 @@ use crate::thread::ThreadId;
use crate::thread_store::ThreadStore;
pub struct ThreadContextPicker {
picker: Entity<Picker<ThreadContextPickerDelegate>>,
picker: View<Picker<ThreadContextPickerDelegate>>,
}
impl ThreadContextPicker {
pub fn new(
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = ThreadContextPickerDelegate::new(
thread_store,
@@ -29,20 +28,20 @@ impl ThreadContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
ThreadContextPicker { picker }
}
}
impl Focusable for ThreadContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for ThreadContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ThreadContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -54,9 +53,9 @@ pub struct ThreadContextEntry {
}
pub struct ThreadContextPickerDelegate {
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>,
selected_index: usize,
@@ -64,9 +63,9 @@ pub struct ThreadContextPickerDelegate {
impl ThreadContextPickerDelegate {
pub fn new(
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
ThreadContextPickerDelegate {
@@ -91,25 +90,15 @@ impl PickerDelegate for ThreadContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search threads…".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Ok(threads) = self.thread_store.update(cx, |this, _cx| {
this.threads()
.into_iter()
@@ -149,7 +138,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
});
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let matches = search_task.await;
this.update(&mut cx, |this, cx| {
this.delegate.matches = matches;
@@ -160,7 +149,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
@@ -171,9 +160,9 @@ impl PickerDelegate for ThreadContextPickerDelegate {
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let thread = open_thread_task.await?;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
@@ -181,14 +170,14 @@ impl PickerDelegate for ThreadContextPickerDelegate {
match this.delegate.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
})
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |_, cx| {
cx.emit(DismissEvent);
@@ -200,8 +189,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let thread = &self.matches[ix];
@@ -213,8 +201,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
pub fn render_thread_context_entry(
thread: &ThreadContextEntry,
context_store: WeakEntity<ContextStore>,
cx: &mut App,
context_store: WeakModel<ContextStore>,
cx: &mut WindowContext,
) -> Div {
let added = context_store.upgrade().map_or(false, |ctx_store| {
ctx_store.read(cx).includes_thread(&thread.id).is_some()

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
use anyhow::{anyhow, bail, Result};
use collections::{BTreeMap, HashMap, HashSet};
use futures::{self, future, Future, FutureExt};
use gpui::{App, AsyncApp, Context, Entity, SharedString, Task, WeakEntity};
use gpui::{AppContext, AsyncAppContext, Model, ModelContext, SharedString, Task, WeakView};
use language::Buffer;
use project::{ProjectPath, Worktree};
use rope::Rope;
@@ -12,15 +12,15 @@ use text::BufferId;
use workspace::Workspace;
use crate::context::{
AssistantContext, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext,
FetchedUrlContext, FileContext, ThreadContext,
Context, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext, FetchedUrlContext,
FileContext, ThreadContext,
};
use crate::context_strip::SuggestedContext;
use crate::thread::{Thread, ThreadId};
pub struct ContextStore {
workspace: WeakEntity<Workspace>,
context: Vec<AssistantContext>,
workspace: WeakView<Workspace>,
context: Vec<Context>,
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
next_context_id: ContextId,
files: BTreeMap<BufferId, ContextId>,
@@ -30,7 +30,7 @@ pub struct ContextStore {
}
impl ContextStore {
pub fn new(workspace: WeakEntity<Workspace>) -> Self {
pub fn new(workspace: WeakView<Workspace>) -> Self {
Self {
workspace,
context: Vec::new(),
@@ -42,13 +42,16 @@ impl ContextStore {
}
}
pub fn snapshot<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ContextSnapshot> + 'a {
pub fn snapshot<'a>(
&'a self,
cx: &'a AppContext,
) -> impl Iterator<Item = ContextSnapshot> + 'a {
self.context()
.iter()
.flat_map(|context| context.snapshot(cx))
}
pub fn context(&self) -> &Vec<AssistantContext> {
pub fn context(&self) -> &Vec<Context> {
&self.context
}
@@ -63,7 +66,7 @@ impl ContextStore {
pub fn add_file_from_path(
&mut self,
project_path: ProjectPath,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let workspace = self.workspace.clone();
@@ -79,8 +82,8 @@ impl ContextStore {
project.open_buffer(project_path.clone(), cx)
})?;
let buffer_entity = open_buffer_task.await?;
let buffer_id = this.update(&mut cx, |_, cx| buffer_entity.read(cx).remote_id())?;
let buffer_model = open_buffer_task.await?;
let buffer_id = this.update(&mut cx, |_, cx| buffer_model.read(cx).remote_id())?;
let already_included = this.update(&mut cx, |this, _cx| {
match this.will_include_buffer(buffer_id, &project_path.path) {
@@ -98,10 +101,10 @@ impl ContextStore {
}
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
let buffer = buffer_entity.read(cx);
let buffer = buffer_model.read(cx);
collect_buffer_info_and_text(
project_path.path.clone(),
buffer_entity,
buffer_model,
buffer,
cx.to_async(),
)
@@ -119,18 +122,18 @@ impl ContextStore {
pub fn add_file_from_buffer(
&mut self,
buffer_entity: Entity<Buffer>,
cx: &mut Context<Self>,
buffer_model: Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
cx.spawn(|this, mut cx| async move {
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
let buffer = buffer_entity.read(cx);
let buffer = buffer_model.read(cx);
let Some(file) = buffer.file() else {
return Err(anyhow!("Buffer has no path."));
};
Ok(collect_buffer_info_and_text(
file.path().clone(),
buffer_entity,
buffer_model,
buffer,
cx.to_async(),
))
@@ -150,13 +153,13 @@ impl ContextStore {
let id = self.next_context_id.post_inc();
self.files.insert(context_buffer.id, id);
self.context
.push(AssistantContext::File(FileContext { id, context_buffer }));
.push(Context::File(FileContext { id, context_buffer }));
}
pub fn add_directory(
&mut self,
project_path: ProjectPath,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let workspace = self.workspace.clone();
let Some(project) = workspace
@@ -207,11 +210,11 @@ impl ContextStore {
let mut buffer_infos = Vec::new();
let mut text_tasks = Vec::new();
this.update(&mut cx, |_, cx| {
for (path, buffer_entity) in files.into_iter().zip(buffers) {
let buffer_entity = buffer_entity?;
let buffer = buffer_entity.read(cx);
for (path, buffer_model) in files.into_iter().zip(buffers) {
let buffer_model = buffer_model?;
let buffer = buffer_model.read(cx);
let (buffer_info, text_task) =
collect_buffer_info_and_text(path, buffer_entity, buffer, cx.to_async());
collect_buffer_info_and_text(path, buffer_model, buffer, cx.to_async());
buffer_infos.push(buffer_info);
text_tasks.push(text_task);
}
@@ -241,15 +244,14 @@ impl ContextStore {
let id = self.next_context_id.post_inc();
self.directories.insert(path.to_path_buf(), id);
self.context
.push(AssistantContext::Directory(DirectoryContext::new(
id,
path,
context_buffers,
)));
self.context.push(Context::Directory(DirectoryContext::new(
id,
path,
context_buffers,
)));
}
pub fn add_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
pub fn add_thread(&mut self, thread: Model<Thread>, cx: &mut ModelContext<Self>) {
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
self.remove_context(context_id);
} else {
@@ -257,13 +259,13 @@ impl ContextStore {
}
}
fn insert_thread(&mut self, thread: Entity<Thread>, cx: &App) {
fn insert_thread(&mut self, thread: Model<Thread>, cx: &AppContext) {
let id = self.next_context_id.post_inc();
let text = thread.read(cx).text().into();
self.threads.insert(thread.read(cx).id().clone(), id);
self.context
.push(AssistantContext::Thread(ThreadContext { id, thread, text }));
.push(Context::Thread(ThreadContext { id, thread, text }));
}
pub fn add_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
@@ -276,18 +278,17 @@ impl ContextStore {
let id = self.next_context_id.post_inc();
self.fetched_urls.insert(url.clone(), id);
self.context
.push(AssistantContext::FetchedUrl(FetchedUrlContext {
id,
url: url.into(),
text: text.into(),
}));
self.context.push(Context::FetchedUrl(FetchedUrlContext {
id,
url: url.into(),
text: text.into(),
}));
}
pub fn accept_suggested_context(
&mut self,
suggested: &SuggestedContext,
cx: &mut Context<ContextStore>,
cx: &mut ModelContext<ContextStore>,
) -> Task<Result<()>> {
match suggested {
SuggestedContext::File {
@@ -314,16 +315,16 @@ impl ContextStore {
};
match self.context.remove(ix) {
AssistantContext::File(_) => {
Context::File(_) => {
self.files.retain(|_, context_id| *context_id != id);
}
AssistantContext::Directory(_) => {
Context::Directory(_) => {
self.directories.retain(|_, context_id| *context_id != id);
}
AssistantContext::FetchedUrl(_) => {
Context::FetchedUrl(_) => {
self.fetched_urls.retain(|_, context_id| *context_id != id);
}
AssistantContext::Thread(_) => {
Context::Thread(_) => {
self.threads.retain(|_, context_id| *context_id != id);
}
}
@@ -342,10 +343,10 @@ impl ContextStore {
/// Returns whether this file path is already included directly in the context, or if it will be
/// included in the context via a directory.
pub fn will_include_file_path(&self, path: &Path, cx: &App) -> Option<FileInclusion> {
pub fn will_include_file_path(&self, path: &Path, cx: &AppContext) -> Option<FileInclusion> {
if !self.files.is_empty() {
let found_file_context = self.context.iter().find(|context| match &context {
AssistantContext::File(file_context) => {
Context::File(file_context) => {
let buffer = file_context.context_buffer.buffer.read(cx);
if let Some(file_path) = buffer_path_log_err(buffer) {
*file_path == *path
@@ -392,7 +393,7 @@ impl ContextStore {
}
/// Replaces the context that matches the ID of the new context, if any match.
fn replace_context(&mut self, new_context: AssistantContext) {
fn replace_context(&mut self, new_context: Context) {
let id = new_context.id();
for context in self.context.iter_mut() {
if context.id() == id {
@@ -402,17 +403,15 @@ impl ContextStore {
}
}
pub fn file_paths(&self, cx: &App) -> HashSet<PathBuf> {
pub fn file_paths(&self, cx: &AppContext) -> HashSet<PathBuf> {
self.context
.iter()
.filter_map(|context| match context {
AssistantContext::File(file) => {
Context::File(file) => {
let buffer = file.context_buffer.buffer.read(cx);
buffer_path_log_err(buffer).map(|p| p.to_path_buf())
}
AssistantContext::Directory(_)
| AssistantContext::FetchedUrl(_)
| AssistantContext::Thread(_) => None,
Context::Directory(_) | Context::FetchedUrl(_) | Context::Thread(_) => None,
})
.collect()
}
@@ -429,7 +428,7 @@ pub enum FileInclusion {
// ContextBuffer without text.
struct BufferInfo {
buffer_entity: Entity<Buffer>,
buffer_model: Model<Buffer>,
id: BufferId,
version: clock::Global,
}
@@ -437,7 +436,7 @@ struct BufferInfo {
fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
ContextBuffer {
id: info.id,
buffer: info.buffer_entity,
buffer: info.buffer_model,
version: info.version,
text,
}
@@ -445,13 +444,13 @@ fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
fn collect_buffer_info_and_text(
path: Arc<Path>,
buffer_entity: Entity<Buffer>,
buffer_model: Model<Buffer>,
buffer: &Buffer,
cx: AsyncApp,
cx: AsyncAppContext,
) -> (BufferInfo, Task<SharedString>) {
let buffer_info = BufferInfo {
id: buffer.remote_id(),
buffer_entity,
buffer_model,
version: buffer.version(),
};
// Important to collect version at the same time as content so that staleness logic is correct.
@@ -526,32 +525,32 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
}
pub fn refresh_context_store_text(
context_store: Entity<ContextStore>,
cx: &App,
context_store: Model<ContextStore>,
cx: &AppContext,
) -> impl Future<Output = ()> {
let mut tasks = Vec::new();
for context in &context_store.read(cx).context {
match context {
AssistantContext::File(file_context) => {
Context::File(file_context) => {
let context_store = context_store.clone();
if let Some(task) = refresh_file_text(context_store, file_context, cx) {
tasks.push(task);
}
}
AssistantContext::Directory(directory_context) => {
Context::Directory(directory_context) => {
let context_store = context_store.clone();
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
tasks.push(task);
}
}
AssistantContext::Thread(thread_context) => {
Context::Thread(thread_context) => {
let context_store = context_store.clone();
tasks.push(refresh_thread_text(context_store, thread_context, cx));
}
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful,
// and doing the caching properly could be tricky (unless it's already handled by
// the HttpClient?).
AssistantContext::FetchedUrl(_) => {}
Context::FetchedUrl(_) => {}
}
}
@@ -559,9 +558,9 @@ pub fn refresh_context_store_text(
}
fn refresh_file_text(
context_store: Entity<ContextStore>,
context_store: Model<ContextStore>,
file_context: &FileContext,
cx: &App,
cx: &AppContext,
) -> Option<Task<()>> {
let id = file_context.id;
let task = refresh_context_buffer(&file_context.context_buffer, cx);
@@ -571,7 +570,7 @@ fn refresh_file_text(
context_store
.update(&mut cx, |context_store, _| {
let new_file_context = FileContext { id, context_buffer };
context_store.replace_context(AssistantContext::File(new_file_context));
context_store.replace_context(Context::File(new_file_context));
})
.ok();
}))
@@ -581,9 +580,9 @@ fn refresh_file_text(
}
fn refresh_directory_text(
context_store: Entity<ContextStore>,
context_store: Model<ContextStore>,
directory_context: &DirectoryContext,
cx: &App,
cx: &AppContext,
) -> Option<Task<()>> {
let mut stale = false;
let futures = directory_context
@@ -612,16 +611,16 @@ fn refresh_directory_text(
context_store
.update(&mut cx, |context_store, _| {
let new_directory_context = DirectoryContext::new(id, &path, context_buffers);
context_store.replace_context(AssistantContext::Directory(new_directory_context));
context_store.replace_context(Context::Directory(new_directory_context));
})
.ok();
}))
}
fn refresh_thread_text(
context_store: Entity<ContextStore>,
context_store: Model<ContextStore>,
thread_context: &ThreadContext,
cx: &App,
cx: &AppContext,
) -> Task<()> {
let id = thread_context.id;
let thread = thread_context.thread.clone();
@@ -629,11 +628,7 @@ fn refresh_thread_text(
context_store
.update(&mut cx, |context_store, cx| {
let text = thread.read(cx).text().into();
context_store.replace_context(AssistantContext::Thread(ThreadContext {
id,
thread,
text,
}));
context_store.replace_context(Context::Thread(ThreadContext { id, thread, text }));
})
.ok();
})
@@ -641,7 +636,7 @@ fn refresh_thread_text(
fn refresh_context_buffer(
context_buffer: &ContextBuffer,
cx: &App,
cx: &AppContext,
) -> Option<impl Future<Output = ContextBuffer>> {
let buffer = context_buffer.buffer.read(cx);
let path = buffer_path_log_err(buffer)?;

View File

@@ -4,8 +4,8 @@ use collections::HashSet;
use editor::Editor;
use file_icons::FileIcons;
use gpui::{
App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
WeakEntity,
AppContext, Bounds, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
Subscription, View, WeakModel, WeakView,
};
use itertools::Itertools;
use language::Buffer;
@@ -24,37 +24,34 @@ use crate::{
};
pub struct ContextStrip {
context_store: Entity<ContextStore>,
pub context_picker: Entity<ContextPicker>,
context_store: Model<ContextStore>,
pub context_picker: View<ContextPicker>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle,
suggest_context_kind: SuggestContextKind,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
_subscriptions: Vec<Subscription>,
focused_index: Option<usize>,
children_bounds: Option<Vec<Bounds<Pixels>>>,
}
impl ContextStrip {
#[allow(clippy::too_many_arguments)]
pub fn new(
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
thread_store: Option<WeakEntity<ThreadStore>>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
editor: WeakView<Editor>,
thread_store: Option<WeakModel<ThreadStore>>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
suggest_context_kind: SuggestContextKind,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let context_picker = cx.new(|cx| {
let context_picker = cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
thread_store.clone(),
context_store.downgrade(),
editor.clone(),
ConfirmBehavior::KeepOpen,
window,
cx,
)
});
@@ -62,9 +59,9 @@ impl ContextStrip {
let focus_handle = cx.focus_handle();
let subscriptions = vec![
cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
cx.on_focus(&focus_handle, window, Self::handle_focus),
cx.on_blur(&focus_handle, window, Self::handle_blur),
cx.subscribe(&context_picker, Self::handle_context_picker_event),
cx.on_focus(&focus_handle, Self::handle_focus),
cx.on_blur(&focus_handle, Self::handle_blur),
];
Self {
@@ -80,20 +77,20 @@ impl ContextStrip {
}
}
fn suggested_context(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
fn suggested_context(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
match self.suggest_context_kind {
SuggestContextKind::File => self.suggested_file(cx),
SuggestContextKind::Thread => self.suggested_thread(cx),
}
}
fn suggested_file(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
fn suggested_file(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
let workspace = self.workspace.upgrade()?;
let active_item = workspace.read(cx).active_item(cx)?;
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
let active_buffer_entity = editor.buffer().read(cx).as_singleton()?;
let active_buffer = active_buffer_entity.read(cx);
let active_buffer_model = editor.buffer().read(cx).as_singleton()?;
let active_buffer = active_buffer_model.read(cx);
let path = active_buffer.file()?.path();
@@ -115,12 +112,12 @@ impl ContextStrip {
Some(SuggestedContext::File {
name,
buffer: active_buffer_entity.downgrade(),
buffer: active_buffer_model.downgrade(),
icon_path,
})
}
fn suggested_thread(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
fn suggested_thread(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
if !self.context_picker.read(cx).allow_threads() {
return None;
}
@@ -152,25 +149,24 @@ impl ContextStrip {
fn handle_context_picker_event(
&mut self,
_picker: &Entity<ContextPicker>,
_picker: View<ContextPicker>,
_event: &DismissEvent,
_window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
cx.emit(ContextStripEvent::PickerDismissed);
}
fn handle_focus(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
self.focused_index = self.last_pill_index();
cx.notify();
}
fn handle_blur(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
self.focused_index = None;
cx.notify();
}
fn focus_left(&mut self, _: &FocusLeft, _window: &mut Window, cx: &mut Context<Self>) {
fn focus_left(&mut self, _: &FocusLeft, cx: &mut ViewContext<Self>) {
self.focused_index = match self.focused_index {
Some(index) if index > 0 => Some(index - 1),
_ => self.last_pill_index(),
@@ -179,7 +175,7 @@ impl ContextStrip {
cx.notify();
}
fn focus_right(&mut self, _: &FocusRight, _window: &mut Window, cx: &mut Context<Self>) {
fn focus_right(&mut self, _: &FocusRight, cx: &mut ViewContext<Self>) {
let Some(last_index) = self.last_pill_index() else {
return;
};
@@ -192,7 +188,7 @@ impl ContextStrip {
cx.notify();
}
fn focus_up(&mut self, _: &FocusUp, _window: &mut Window, cx: &mut Context<Self>) {
fn focus_up(&mut self, _: &FocusUp, cx: &mut ViewContext<Self>) {
let Some(focused_index) = self.focused_index else {
return;
};
@@ -210,7 +206,7 @@ impl ContextStrip {
cx.notify();
}
fn focus_down(&mut self, _: &FocusDown, _window: &mut Window, cx: &mut Context<Self>) {
fn focus_down(&mut self, _: &FocusDown, cx: &mut ViewContext<Self>) {
let Some(focused_index) = self.focused_index else {
return;
};
@@ -280,12 +276,7 @@ impl ContextStrip {
best.map(|(index, _, _)| index)
}
fn remove_focused_context(
&mut self,
_: &RemoveFocusedContext,
_window: &mut Window,
cx: &mut Context<Self>,
) {
fn remove_focused_context(&mut self, _: &RemoveFocusedContext, cx: &mut ViewContext<Self>) {
if let Some(index) = self.focused_index {
let mut is_empty = false;
@@ -311,32 +302,22 @@ impl ContextStrip {
self.focused_index == Some(context.len())
}
fn accept_suggested_context(
&mut self,
_: &AcceptSuggestedContext,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn accept_suggested_context(&mut self, _: &AcceptSuggestedContext, cx: &mut ViewContext<Self>) {
if let Some(suggested) = self.suggested_context(cx) {
let context_store = self.context_store.read(cx);
if self.is_suggested_focused(context_store.context()) {
self.add_suggested_context(&suggested, window, cx);
self.add_suggested_context(&suggested, cx);
}
}
}
fn add_suggested_context(
&mut self,
suggested: &SuggestedContext,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn add_suggested_context(&mut self, suggested: &SuggestedContext, cx: &mut ViewContext<Self>) {
let task = self.context_store.update(cx, |context_store, cx| {
context_store.accept_suggested_context(&suggested, cx)
});
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
match task.await.notify_async_err(&mut cx) {
None => {}
Some(()) => {
@@ -353,14 +334,14 @@ impl ContextStrip {
}
}
impl Focusable for ContextStrip {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
impl FocusableView for ContextStrip {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ContextStrip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let context_store = self.context_store.read(cx);
let context = context_store
.context()
@@ -393,40 +374,39 @@ impl Render for ContextStrip {
.on_action(cx.listener(Self::remove_focused_context))
.on_action(cx.listener(Self::accept_suggested_context))
.on_children_prepainted({
let entity = cx.entity().downgrade();
move |children_bounds, _window, cx| {
entity
.update(cx, |this, _| {
this.children_bounds = Some(children_bounds);
})
.ok();
let view = cx.view().downgrade();
move |children_bounds, cx| {
view.update(cx, |this, _| {
this.children_bounds = Some(children_bounds);
})
.ok();
}
})
.child(
PopoverMenu::new("context-picker")
.menu(move |window, cx| {
.menu(move |cx| {
context_picker.update(cx, |this, cx| {
this.init(window, cx);
this.init(cx);
});
Some(context_picker.clone())
})
.trigger_with_tooltip(
.trigger(
IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
},
.style(ui::ButtonStyle::Filled)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
cx,
)
}
}),
)
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
@@ -449,12 +429,8 @@ impl Render for ContextStrip {
)
.opacity(0.5)
.children(
KeyBinding::for_action_in(
&ToggleContextPicker,
&focus_handle,
window,
)
.map(|binding| binding.into_any_element()),
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
),
)
}
@@ -467,7 +443,7 @@ impl Render for ContextStrip {
Some({
let id = context.id;
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, _window, cx| {
Rc::new(cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| {
this.remove_context(id);
});
@@ -475,7 +451,7 @@ impl Render for ContextStrip {
}))
}),
)
.on_click(Rc::new(cx.listener(move |this, _, _window, cx| {
.on_click(Rc::new(cx.listener(move |this, _, cx| {
this.focused_index = Some(i);
cx.notify();
})))
@@ -488,11 +464,9 @@ impl Render for ContextStrip {
suggested.kind(),
self.is_suggested_focused(&context),
)
.on_click(Rc::new(cx.listener(
move |this, _event, window, cx| {
this.add_suggested_context(&suggested, window, cx);
},
))),
.on_click(Rc::new(cx.listener(move |this, _event, cx| {
this.add_suggested_context(&suggested, cx);
}))),
)
})
.when(!context.is_empty(), {
@@ -502,20 +476,19 @@ impl Render for ContextStrip {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |cx| {
Tooltip::for_action_in(
"Remove All Context",
&RemoveAllContext,
&focus_handle,
window,
cx,
)
}
})
.on_click(cx.listener({
let focus_handle = focus_handle.clone();
move |_this, _event, window, cx| {
focus_handle.dispatch_action(&RemoveAllContext, window, cx);
move |_this, _event, cx| {
focus_handle.dispatch_action(&RemoveAllContext, cx);
}
})),
)
@@ -543,11 +516,11 @@ pub enum SuggestedContext {
File {
name: SharedString,
icon_path: Option<SharedString>,
buffer: WeakEntity<Buffer>,
buffer: WeakModel<Buffer>,
},
Thread {
name: SharedString,
thread: WeakEntity<Thread>,
thread: WeakModel<Thread>,
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,8 +16,9 @@ use editor::{
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
use fs::Fs;
use gpui::{
anchored, deferred, point, AnyElement, App, ClickEvent, Context, CursorStyle, Entity,
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
FocusHandle, FocusableView, FontWeight, Model, Subscription, TextStyle, View, ViewContext,
WeakModel, WeakView, WindowContext,
};
use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
@@ -34,12 +35,12 @@ use util::ResultExt;
use workspace::Workspace;
pub struct PromptEditor<T> {
pub editor: Entity<Editor>,
pub editor: View<Editor>,
mode: PromptEditorMode,
context_store: Entity<ContextStore>,
context_strip: Entity<ContextStrip>,
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
@@ -55,7 +56,7 @@ pub struct PromptEditor<T> {
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
impl<T: 'static> Render for PromptEditor<T> {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let mut buttons = Vec::new();
@@ -86,7 +87,7 @@ impl<T: 'static> Render for PromptEditor<T> {
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
};
buttons.extend(self.render_buttons(window, cx));
buttons.extend(self.render_buttons(cx));
v_flex()
.key_context("PromptEditor")
@@ -162,7 +163,9 @@ impl<T: 'static> Render for PromptEditor<T> {
el.child(
div()
.id("error")
.tooltip(Tooltip::text(error_message))
.tooltip(move |cx| {
Tooltip::text(error_message.clone(), cx)
})
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
@@ -176,7 +179,7 @@ impl<T: 'static> Render for PromptEditor<T> {
h_flex()
.w_full()
.justify_between()
.child(div().flex_1().child(self.render_editor(window, cx)))
.child(div().flex_1().child(self.render_editor(cx)))
.child(
WithRemSize::new(ui_font_size)
.flex()
@@ -206,8 +209,8 @@ impl<T: 'static> Render for PromptEditor<T> {
}
}
impl<T: 'static> Focusable for PromptEditor<T> {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl<T: 'static> FocusableView for PromptEditor<T> {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
}
@@ -215,50 +218,47 @@ impl<T: 'static> Focusable for PromptEditor<T> {
impl<T: 'static> PromptEditor<T> {
const MAX_LINES: u8 = 8;
fn codegen_status<'a>(&'a self, cx: &'a App) -> &'a CodegenStatus {
fn codegen_status<'a>(&'a self, cx: &'a AppContext) -> &'a CodegenStatus {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => codegen.read(cx).status(cx),
PromptEditorMode::Terminal { codegen, .. } => &codegen.read(cx).status,
}
}
fn subscribe_to_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions.push(cx.subscribe_in(
&self.editor,
window,
Self::handle_prompt_editor_events,
));
self.editor_subscriptions
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
}
pub fn set_show_cursor_when_unfocused(
&mut self,
show_cursor_when_unfocused: bool,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
});
}
pub fn unlink(&mut self, window: &mut Window, cx: &mut Context<Self>) {
pub fn unlink(&mut self, cx: &mut ViewContext<Self>) {
let prompt = self.prompt(cx);
let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
self.editor = cx.new(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, cx);
let focus = self.editor.focus_handle(cx).contains_focused(cx);
self.editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(&self.mode, window, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&self.mode, cx), cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor.set_text(prompt, window, cx);
editor.set_text(prompt, cx);
if focus {
window.focus(&editor.focus_handle(cx));
editor.focus(cx);
}
editor
});
self.subscribe_to_editor(window, cx);
self.subscribe_to_editor(cx);
}
pub fn placeholder_text(mode: &PromptEditorMode, window: &mut Window, cx: &mut App) -> String {
pub fn placeholder_text(mode: &PromptEditorMode, cx: &WindowContext) -> String {
let action = match mode {
PromptEditorMode::Buffer { codegen, .. } => {
if codegen.read(cx).is_insertion {
@@ -271,50 +271,46 @@ impl<T: 'static> PromptEditor<T> {
};
let assistant_panel_keybinding =
ui::text_for_action(&zed_actions::assistant::ToggleFocus, window)
ui::text_for_action(&zed_actions::assistant::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} to chat ― "))
.unwrap_or_default();
format!("{action}… ({assistant_panel_keybinding}↓↑ for history)")
}
pub fn prompt(&self, cx: &App) -> String {
pub fn prompt(&self, cx: &AppContext) -> String {
self.editor.read(cx).text(cx)
}
fn toggle_rate_limit_notice(
&mut self,
_: &ClickEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
self.show_rate_limit_notice = !self.show_rate_limit_notice;
if self.show_rate_limit_notice {
window.focus(&self.editor.focus_handle(cx));
cx.focus_view(&self.editor);
}
cx.notify();
}
fn handle_prompt_editor_events(
&mut self,
_: &Entity<Editor>,
_: View<Editor>,
event: &EditorEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
if let Some(workspace) = window.root::<Workspace>().flatten() {
workspace.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
});
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
})
.log_err();
}
let prompt = self.editor.read(cx).text(cx);
if self
@@ -338,40 +334,20 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn toggle_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx);
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx);
}
pub fn remove_all_context(
&mut self,
_: &RemoveAllContext,
_window: &mut Window,
cx: &mut Context<Self>,
) {
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
self.context_store.update(cx, |store, _cx| store.clear());
cx.notify();
}
fn cancel(
&mut self,
_: &editor::actions::Cancel,
_window: &mut Window,
cx: &mut Context<Self>,
) {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
@@ -382,7 +358,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle => {
cx.emit(PromptEditorEvent::StartRequested);
@@ -403,49 +379,49 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
}
} else {
self.context_strip.focus_handle(cx).focus(window);
cx.focus_view(&self.context_strip);
}
}
fn render_buttons(&self, _window: &mut Window, cx: &mut Context<Self>) -> Vec<AnyElement> {
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
let mode = match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
let codegen = codegen.read(cx);
@@ -467,22 +443,21 @@ impl<T: 'static> PromptEditor<T> {
.icon(IconName::Return)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element()]
}
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element()],
CodegenStatus::Done | CodegenStatus::Error(_) => {
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
@@ -490,16 +465,15 @@ impl<T: 'static> PromptEditor<T> {
vec![IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element()]
@@ -507,10 +481,10 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
.tooltip(move |cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element();
@@ -521,15 +495,14 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}))
.into_any_element(),
@@ -541,12 +514,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn cycle_prev(
&mut self,
_: &CyclePreviousInlineAssist,
_: &mut Window,
cx: &mut Context<Self>,
) {
fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_prev(cx));
@@ -557,7 +525,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn cycle_next(&mut self, _: &CycleNextInlineAssist, _: &mut Window, cx: &mut Context<Self>) {
fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext<Self>) {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_next(cx));
@@ -568,16 +536,16 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn render_close_button(&self, cx: &mut Context<Self>) -> AnyElement {
fn render_close_button(&self, cx: &ViewContext<Self>) -> AnyElement {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text("Close Assistant"))
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element()
}
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &Context<Self>) -> AnyElement {
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &ViewContext<Self>) -> AnyElement {
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
let model_registry = LanguageModelRegistry::read_global(cx);
@@ -617,13 +585,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
cx.new(|_| {
move |cx| {
cx.new_view(|cx| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
window,
cx,
),
);
if !disabled && current_index != 0 {
@@ -634,8 +602,8 @@ impl<T: 'static> PromptEditor<T> {
.into()
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, window, cx);
.on_click(cx.listener(|this, _, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, cx);
})),
)
.child(
@@ -658,13 +626,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
cx.new(|_| {
move |cx| {
cx.new_view(|cx| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
window,
cx,
),
);
if !disabled && current_index != total_models - 1 {
@@ -675,14 +643,14 @@ impl<T: 'static> PromptEditor<T> {
.into()
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.cycle_next(&CycleNextInlineAssist, window, cx)
})),
.on_click(
cx.listener(|this, _, cx| this.cycle_next(&CycleNextInlineAssist, cx)),
),
)
.into_any_element()
}
fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement {
fn render_rate_limit_notice(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
Popover::new().child(
v_flex()
.occlude()
@@ -706,7 +674,7 @@ impl<T: 'static> PromptEditor<T> {
} else {
ui::ToggleState::Unselected
},
|selection, _, cx| {
|selection, cx| {
let is_dismissed = match selection {
ui::ToggleState::Unselected => false,
ui::ToggleState::Indeterminate => return,
@@ -725,11 +693,10 @@ impl<T: 'static> PromptEditor<T> {
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
)
.child(Button::new("more-info", "More Info").on_click(
|_event, window, cx| {
window.dispatch_action(
Box::new(zed_actions::OpenAccountSettings),
cx,
)
|_event, cx| {
cx.dispatch_action(Box::new(
zed_actions::OpenAccountSettings,
))
},
)),
),
@@ -737,9 +704,9 @@ impl<T: 'static> PromptEditor<T> {
)
}
fn render_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
fn render_editor(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
div()
.key_context("InlineAssistEditor")
@@ -773,15 +740,17 @@ impl<T: 'static> PromptEditor<T> {
fn handle_context_strip_event(
&mut self,
_context_strip: &Entity<ContextStrip>,
_context_strip: View<ContextStrip>,
event: &ContextStripEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
ContextStripEvent::PickerDismissed
| ContextStripEvent::BlurredEmpty
| ContextStripEvent::BlurredUp => self.editor.focus_handle(cx).focus(window),
| ContextStripEvent::BlurredUp => {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
}
ContextStripEvent::BlurredDown => {}
}
}
@@ -790,12 +759,12 @@ impl<T: 'static> PromptEditor<T> {
pub enum PromptEditorMode {
Buffer {
id: InlineAssistId,
codegen: Entity<BufferCodegen>,
codegen: Model<BufferCodegen>,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
},
Terminal {
id: TerminalInlineAssistId,
codegen: Entity<TerminalCodegen>,
codegen: Model<TerminalCodegen>,
height_in_lines: u8,
},
}
@@ -826,14 +795,13 @@ impl PromptEditor<BufferCodegen> {
id: InlineAssistId,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
prompt_history: VecDeque<String>,
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<BufferCodegen>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<BufferCodegen>,
fs: Arc<dyn Fs>,
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
window: &mut Window,
cx: &mut Context<PromptEditor<BufferCodegen>>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
) -> PromptEditor<BufferCodegen> {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Buffer {
@@ -842,7 +810,7 @@ impl PromptEditor<BufferCodegen> {
gutter_dimensions,
};
let prompt_editor = cx.new(|cx| {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -850,7 +818,6 @@ impl PromptEditor<BufferCodegen> {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
@@ -858,13 +825,13 @@ impl PromptEditor<BufferCodegen> {
// always show the cursor (even when it isn't focused) because
// typing in one will make what you typed appear in all of them.
editor.set_show_cursor_when_unfocused(true, cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
editor
});
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_view(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
@@ -872,25 +839,23 @@ impl PromptEditor<BufferCodegen> {
thread_store.clone(),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
window,
cx,
)
});
let context_strip_subscription =
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
cx.subscribe(&context_strip, Self::handle_context_strip_event);
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
editor: prompt_editor.clone(),
context_store,
context_strip,
context_picker_menu_handle,
model_selector: cx.new(|cx| {
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
}),
@@ -907,14 +872,14 @@ impl PromptEditor<BufferCodegen> {
_phantom: Default::default(),
};
this.subscribe_to_editor(window, cx);
this.subscribe_to_editor(cx);
this
}
fn handle_codegen_changed(
&mut self,
_: Entity<BufferCodegen>,
cx: &mut Context<PromptEditor<BufferCodegen>>,
_: Model<BufferCodegen>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
) {
match self.codegen_status(cx) {
CodegenStatus::Idle => {
@@ -953,7 +918,7 @@ impl PromptEditor<BufferCodegen> {
}
}
pub fn codegen(&self) -> &Entity<BufferCodegen> {
pub fn codegen(&self) -> &Model<BufferCodegen> {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => codegen,
PromptEditorMode::Terminal { .. } => unreachable!(),
@@ -986,14 +951,13 @@ impl PromptEditor<TerminalCodegen> {
pub fn new_terminal(
id: TerminalInlineAssistId,
prompt_history: VecDeque<String>,
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<TerminalCodegen>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<TerminalCodegen>,
fs: Arc<dyn Fs>,
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
window: &mut Window,
cx: &mut Context<Self>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut ViewContext<Self>,
) -> Self {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Terminal {
@@ -1002,7 +966,7 @@ impl PromptEditor<TerminalCodegen> {
height_in_lines: 1,
};
let prompt_editor = cx.new(|cx| {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -1010,17 +974,16 @@ impl PromptEditor<TerminalCodegen> {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
editor
});
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_view(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
@@ -1028,25 +991,23 @@ impl PromptEditor<TerminalCodegen> {
thread_store.clone(),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
window,
cx,
)
});
let context_strip_subscription =
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
cx.subscribe(&context_strip, Self::handle_context_strip_event);
let mut this = Self {
editor: prompt_editor.clone(),
context_store,
context_strip,
context_picker_menu_handle,
model_selector: cx.new(|cx| {
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
}),
@@ -1063,11 +1024,11 @@ impl PromptEditor<TerminalCodegen> {
_phantom: Default::default(),
};
this.count_lines(cx);
this.subscribe_to_editor(window, cx);
this.subscribe_to_editor(cx);
this
}
fn count_lines(&mut self, cx: &mut Context<Self>) {
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
@@ -1091,7 +1052,7 @@ impl PromptEditor<TerminalCodegen> {
}
}
fn handle_codegen_changed(&mut self, _: Entity<TerminalCodegen>, cx: &mut Context<Self>) {
fn handle_codegen_changed(&mut self, _: Model<TerminalCodegen>, cx: &mut ViewContext<Self>) {
match &self.codegen().read(cx).status {
CodegenStatus::Idle => {
self.editor
@@ -1109,7 +1070,7 @@ impl PromptEditor<TerminalCodegen> {
}
}
pub fn codegen(&self) -> &Entity<TerminalCodegen> {
pub fn codegen(&self) -> &Model<TerminalCodegen> {
match &self.mode {
PromptEditorMode::Buffer { .. } => unreachable!(),
PromptEditorMode::Terminal { codegen, .. } => codegen,
@@ -1133,7 +1094,7 @@ fn dismissed_rate_limit_notice() -> bool {
.map_or(false, |s| s.is_some())
}
fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut App) {
fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut AppContext) {
db::write_and_log(cx, move || async move {
if is_dismissed {
db::kvp::KEY_VALUE_STORE

View File

@@ -4,15 +4,14 @@ use editor::actions::MoveUp;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs;
use gpui::{
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
TextStyle, WeakEntity,
pulsating_between, Animation, AnimationExt, AppContext, DismissEvent, FocusableView, Model,
Subscription, TextStyle, View, WeakModel, WeakView,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
use rope::Point;
use settings::Settings;
use std::time::Duration;
use text::Bias;
use theme::ThemeSettings;
use ui::{
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
@@ -28,14 +27,14 @@ use crate::thread_store::ThreadStore;
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor {
thread: Entity<Thread>,
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
context_strip: Entity<ContextStrip>,
thread: Model<Thread>,
editor: View<Editor>,
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: Entity<ContextPicker>,
inline_context_picker: View<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
@@ -44,38 +43,36 @@ pub struct MessageEditor {
impl MessageEditor {
pub fn new(
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
thread_store: WeakEntity<ThreadStore>,
thread: Entity<Thread>,
window: &mut Window,
cx: &mut Context<Self>,
workspace: WeakView<Workspace>,
thread_store: WeakModel<ThreadStore>,
thread: Model<Thread>,
cx: &mut ViewContext<Self>,
) -> Self {
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx);
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(10, cx);
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
editor.set_show_indent_guides(false, cx);
editor
});
let inline_context_picker = cx.new(|cx| {
let inline_context_picker = cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
Some(thread_store.clone()),
context_store.downgrade(),
editor.downgrade(),
ConfirmBehavior::Close,
window,
cx,
)
});
let context_strip = cx.new(|cx| {
let context_strip = cx.new_view(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
@@ -83,19 +80,17 @@ impl MessageEditor {
Some(thread_store.clone()),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
window,
cx,
)
});
let subscriptions = vec![
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(
&inline_context_picker,
window,
Self::handle_inline_context_picker_event,
),
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
cx.subscribe(&context_strip, Self::handle_context_strip_event),
];
Self {
@@ -106,12 +101,11 @@ impl MessageEditor {
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new(|cx| {
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
editor.focus_handle(cx),
window,
cx,
)
}),
@@ -121,59 +115,39 @@ impl MessageEditor {
}
}
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx)
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx)
}
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
fn toggle_chat_mode(&mut self, _: &ChatMode, cx: &mut ViewContext<Self>) {
self.use_tools = !self.use_tools;
cx.notify();
}
fn toggle_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
pub fn remove_all_context(
&mut self,
_: &RemoveAllContext,
_window: &mut Window,
cx: &mut Context<Self>,
) {
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
self.context_store.update(cx, |store, _cx| store.clear());
cx.notify();
}
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
self.send_to_model(RequestKind::Chat, window, cx);
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx);
}
fn is_editor_empty(&self, cx: &App) -> bool {
fn is_editor_empty(&self, cx: &AppContext) -> bool {
self.editor.read(cx).text(cx).is_empty()
}
fn is_model_selected(&self, cx: &App) -> bool {
fn is_model_selected(&self, cx: &AppContext) -> bool {
LanguageModelRegistry::read_global(cx)
.active_model()
.is_some()
}
fn send_to_model(
&mut self,
request_kind: RequestKind,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn send_to_model(&mut self, request_kind: RequestKind, cx: &mut ViewContext<Self>) {
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
.as_ref()
@@ -190,7 +164,7 @@ impl MessageEditor {
let user_message = self.editor.update(cx, |editor, cx| {
let text = editor.text(cx);
editor.clear(window, cx);
editor.clear(cx);
text
});
@@ -229,10 +203,9 @@ impl MessageEditor {
fn handle_editor_event(
&mut self,
editor: &Entity<Editor>,
editor: View<Editor>,
event: &EditorEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::SelectionsChanged { .. } => {
@@ -240,13 +213,10 @@ impl MessageEditor {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let newest_cursor = editor.selections.newest::<Point>(cx).head();
if newest_cursor.column > 0 {
let behind_cursor = snapshot.clip_point(
Point::new(newest_cursor.row, newest_cursor.column - 1),
Bias::Left,
);
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
if char_behind_cursor == Some('@') {
self.inline_context_picker_menu_handle.show(window, cx);
self.inline_context_picker_menu_handle.show(cx);
}
}
});
@@ -257,54 +227,52 @@ impl MessageEditor {
fn handle_inline_context_picker_event(
&mut self,
_inline_context_picker: &Entity<ContextPicker>,
_inline_context_picker: View<ContextPicker>,
_event: &DismissEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
window.focus(&editor_focus_handle);
cx.focus(&editor_focus_handle);
}
fn handle_context_strip_event(
&mut self,
_context_strip: &Entity<ContextStrip>,
_context_strip: View<ContextStrip>,
event: &ContextStripEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
ContextStripEvent::PickerDismissed
| ContextStripEvent::BlurredEmpty
| ContextStripEvent::BlurredDown => {
let editor_focus_handle = self.editor.focus_handle(cx);
window.focus(&editor_focus_handle);
cx.focus(&editor_focus_handle);
}
ContextStripEvent::BlurredUp => {}
}
}
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if self.context_picker_menu_handle.is_deployed()
|| self.inline_context_picker_menu_handle.is_deployed()
{
cx.propagate();
} else {
self.context_strip.focus_handle(cx).focus(window);
cx.focus_view(&self.context_strip);
}
}
}
impl Focusable for MessageEditor {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
impl FocusableView for MessageEditor {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl Render for MessageEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
let focus_handle = self.editor.focus_handle(cx);
let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background;
@@ -358,9 +326,9 @@ impl Render for MessageEditor {
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |window, cx| {
.menu(move |cx| {
inline_context_picker.update(cx, |this, cx| {
this.init(window, cx);
this.init(cx);
});
Some(inline_context_picker.clone())
@@ -383,7 +351,7 @@ impl Render for MessageEditor {
.child(
Switch::new("use-tools", self.use_tools.into())
.label("Tools")
.on_click(cx.listener(|this, selection, _window, _cx| {
.on_click(cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected
@@ -393,7 +361,7 @@ impl Render for MessageEditor {
.key_binding(KeyBinding::for_action_in(
&ChatMode,
&focus_handle,
window,
cx,
)),
)
.child(h_flex().gap_1().child(self.model_selector.clone()).child(
@@ -422,17 +390,14 @@ impl Render for MessageEditor {
KeyBinding::for_action_in(
&editor::actions::Cancel,
&focus_handle,
window,
cx,
)
.map(|binding| binding.into_any_element()),
),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(
&editor::actions::Cancel,
window,
cx,
);
.on_click(move |_event, cx| {
focus_handle
.dispatch_action(&editor::actions::Cancel, cx);
})
} else {
ButtonLike::new("submit-message")
@@ -452,22 +417,23 @@ impl Render for MessageEditor {
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);
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
})
.when(is_editor_empty, |button| {
button
.tooltip(Tooltip::text("Type a message to submit"))
button.tooltip(|cx| {
Tooltip::text("Type a message to submit", cx)
})
})
.when(!is_model_selected, |button| {
button.tooltip(Tooltip::text(
"Select a model to continue",
))
button.tooltip(|cx| {
Tooltip::text("Select a model to continue", cx)
})
})
},
)),

View File

@@ -1,7 +1,7 @@
use crate::inline_prompt_editor::CodegenStatus;
use client::telemetry::Telemetry;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{App, Context, Entity, EventEmitter, Task};
use gpui::{AppContext, EventEmitter, Model, ModelContext, Task};
use language_model::{LanguageModelRegistry, LanguageModelRequest};
use language_models::report_assistant_event;
use std::{sync::Arc, time::Instant};
@@ -11,7 +11,7 @@ use terminal::Terminal;
pub struct TerminalCodegen {
pub status: CodegenStatus,
pub telemetry: Option<Arc<Telemetry>>,
terminal: Entity<Terminal>,
terminal: Model<Terminal>,
generation: Task<()>,
pub message_id: Option<String>,
transaction: Option<TerminalTransaction>,
@@ -20,7 +20,7 @@ pub struct TerminalCodegen {
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
impl TerminalCodegen {
pub fn new(terminal: Entity<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self {
terminal,
telemetry,
@@ -31,7 +31,7 @@ impl TerminalCodegen {
}
}
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut Context<Self>) {
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
@@ -131,20 +131,20 @@ impl TerminalCodegen {
cx.notify();
}
pub fn stop(&mut self, cx: &mut Context<Self>) {
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished);
cx.notify();
}
pub fn complete(&mut self, cx: &mut Context<Self>) {
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.complete(cx);
}
}
pub fn undo(&mut self, cx: &mut Context<Self>) {
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.undo(cx);
}
@@ -160,27 +160,27 @@ pub const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
terminal: Entity<Terminal>,
terminal: Model<Terminal>,
}
impl TerminalTransaction {
pub fn start(terminal: Entity<Terminal>) -> Self {
pub fn start(terminal: Model<Terminal>) -> Self {
Self { terminal }
}
pub fn push(&mut self, hunk: String, cx: &mut App) {
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
pub fn undo(&self, cx: &mut App) {
pub fn undo(&self, cx: &mut AppContext) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
}
pub fn complete(&self, cx: &mut App) {
pub fn complete(&self, cx: &mut AppContext) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});

View File

@@ -10,7 +10,10 @@ use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque};
use editor::{actions::SelectAll, MultiBuffer};
use fs::Fs;
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
use gpui::{
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
WeakView,
};
use language::Buffer;
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
@@ -28,7 +31,7 @@ pub fn init(
fs: Arc<dyn Fs>,
prompt_builder: Arc<PromptBuilder>,
telemetry: Arc<Telemetry>,
cx: &mut App,
cx: &mut AppContext,
) {
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
}
@@ -65,20 +68,20 @@ impl TerminalInlineAssistant {
pub fn assist(
&mut self,
terminal_view: &Entity<TerminalView>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
window: &mut Window,
cx: &mut App,
terminal_view: &View<TerminalView>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut WindowContext,
) {
let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc();
let prompt_buffer =
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(String::new(), cx)), cx));
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_buffer = cx.new_model(|cx| {
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
});
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new(|cx| {
let prompt_editor = cx.new_view(|cx| {
PromptEditor::new_terminal(
assist_id,
self.prompt_history.clone(),
@@ -88,7 +91,6 @@ impl TerminalInlineAssistant {
context_store.clone(),
workspace.clone(),
thread_store.clone(),
window,
cx,
)
});
@@ -98,7 +100,7 @@ impl TerminalInlineAssistant {
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
};
terminal_view.update(cx, |terminal_view, cx| {
terminal_view.set_block_below_cursor(block, window, cx);
terminal_view.set_block_below_cursor(block, cx);
});
let terminal_assistant = TerminalInlineAssist::new(
@@ -107,27 +109,21 @@ impl TerminalInlineAssistant {
prompt_editor,
workspace.clone(),
context_store,
window,
cx,
);
self.assists.insert(assist_id, terminal_assistant);
self.focus_assist(assist_id, window, cx);
self.focus_assist(assist_id, cx);
}
fn focus_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut App,
) {
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = &self.assists[&assist_id];
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
prompt_editor.update(cx, |this, cx| {
this.editor.update(cx, |editor, cx| {
window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, window, cx);
editor.focus(cx);
editor.select_all(&SelectAll, cx);
});
});
}
@@ -135,10 +131,9 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
event: &PromptEditorEvent,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
let assist_id = prompt_editor.read(cx).id();
match event {
@@ -149,21 +144,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx);
}
PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, window, cx);
self.finish_assist(assist_id, false, *execute, cx);
}
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, window, cx);
self.finish_assist(assist_id, true, false, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
}
}
}
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -201,7 +196,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
}
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -214,7 +209,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist(
&self,
assist_id: TerminalInlineAssistId,
cx: &mut App,
cx: &mut WindowContext,
) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?;
@@ -222,7 +217,7 @@ impl TerminalInlineAssistant {
let (latest_output, working_directory) = assist
.terminal
.update(cx, |terminal, cx| {
let terminal = terminal.entity().read(cx);
let terminal = terminal.model().read(cx);
let latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES);
let working_directory = terminal
.working_directory()
@@ -270,17 +265,16 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId,
undo: bool,
execute: bool,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, cx);
if let Some(assist) = self.assists.remove(&assist_id) {
assist
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(window);
this.focus_handle(cx).focus(cx);
})
.log_err();
@@ -323,8 +317,7 @@ impl TerminalInlineAssistant {
fn dismiss_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else {
return false;
@@ -337,7 +330,7 @@ impl TerminalInlineAssistant {
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(window);
this.focus_handle(cx).focus(cx);
})
.is_ok()
}
@@ -346,8 +339,7 @@ impl TerminalInlineAssistant {
&mut self,
assist_id: TerminalInlineAssistId,
height: u8,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) {
if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@@ -359,7 +351,7 @@ impl TerminalInlineAssistant {
height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
};
terminal.set_block_below_cursor(block, window, cx);
terminal.set_block_below_cursor(block, cx);
})
.log_err();
}
@@ -368,23 +360,22 @@ impl TerminalInlineAssistant {
}
struct TerminalInlineAssist {
terminal: WeakEntity<TerminalView>,
prompt_editor: Option<Entity<PromptEditor<TerminalCodegen>>>,
codegen: Entity<TerminalCodegen>,
workspace: WeakEntity<Workspace>,
context_store: Entity<ContextStore>,
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
codegen: Model<TerminalCodegen>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
_subscriptions: Vec<Subscription>,
}
impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &Entity<TerminalView>,
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
workspace: WeakEntity<Workspace>,
context_store: Entity<ContextStore>,
window: &mut Window,
cx: &mut App,
terminal: &View<TerminalView>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
cx: &mut WindowContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen().clone();
Self {
@@ -394,12 +385,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(),
context_store,
_subscriptions: vec![
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| {
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
this.handle_prompt_editor_event(prompt_editor, event, cx)
})
}),
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
cx.subscribe(&codegen, move |codegen, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) {
@@ -428,7 +419,7 @@ impl TerminalInlineAssist {
}
if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, window, cx);
this.finish_assist(assist_id, false, false, cx);
}
}
})

View File

@@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet};
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{App, Context, EventEmitter, SharedString, Task};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
@@ -76,7 +76,7 @@ pub struct Thread {
}
impl Thread {
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut Context<Self>) -> Self {
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
Self {
id: ThreadId::new(),
updated_at: Utc::now(),
@@ -99,7 +99,7 @@ impl Thread {
id: ThreadId,
saved: SavedThread,
tools: Arc<ToolWorkingSet>,
_cx: &mut Context<Self>,
_cx: &mut ModelContext<Self>,
) -> Self {
let next_message_id = MessageId(saved.messages.len());
@@ -154,7 +154,7 @@ impl Thread {
self.summary.clone().unwrap_or(DEFAULT)
}
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) {
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut ModelContext<Self>) {
self.summary = Some(summary.into());
cx.emit(ThreadEvent::SummaryChanged);
}
@@ -194,7 +194,7 @@ impl Thread {
&mut self,
text: impl Into<String>,
context: Vec<ContextSnapshot>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let message_id = self.insert_message(Role::User, text, cx);
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
@@ -207,7 +207,7 @@ impl Thread {
&mut self,
role: Role,
text: impl Into<String>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> MessageId {
let id = self.next_message_id.post_inc();
self.messages.push(Message {
@@ -244,7 +244,7 @@ impl Thread {
pub fn to_completion_request(
&self,
_request_kind: RequestKind,
_cx: &App,
_cx: &AppContext,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
messages: vec![],
@@ -314,7 +314,7 @@ impl Thread {
&mut self,
request: LanguageModelRequest,
model: Arc<dyn LanguageModel>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let pending_completion_id = post_inc(&mut self.completion_count);
@@ -439,7 +439,7 @@ impl Thread {
});
}
pub fn summarize(&mut self, cx: &mut Context<Self>) {
pub fn summarize(&mut self, cx: &mut ModelContext<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return;
};
@@ -497,7 +497,7 @@ impl Thread {
assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let insert_output_task = cx.spawn(|thread, mut cx| {
let tool_use_id = tool_use_id.clone();

View File

@@ -1,6 +1,6 @@
use gpui::{
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
WeakEntity,
uniform_list, AppContext, FocusHandle, FocusableView, Model, ScrollStrategy,
UniformListScrollHandle, WeakView,
};
use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
@@ -10,18 +10,17 @@ use crate::{AssistantPanel, RemoveSelectedThread};
pub struct ThreadHistory {
focus_handle: FocusHandle,
assistant_panel: WeakEntity<AssistantPanel>,
thread_store: Entity<ThreadStore>,
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
scroll_handle: UniformListScrollHandle,
selected_index: usize,
}
impl ThreadHistory {
pub(crate) fn new(
assistant_panel: WeakEntity<AssistantPanel>,
thread_store: Entity<ThreadStore>,
cx: &mut Context<Self>,
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
focus_handle: cx.focus_handle(),
@@ -32,77 +31,62 @@ impl ThreadHistory {
}
}
pub fn select_prev(
&mut self,
_: &menu::SelectPrev,
window: &mut Window,
cx: &mut Context<Self>,
) {
pub fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
if self.selected_index == 0 {
self.set_selected_index(count - 1, window, cx);
self.set_selected_index(count - 1, cx);
} else {
self.set_selected_index(self.selected_index - 1, window, cx);
self.set_selected_index(self.selected_index - 1, cx);
}
}
}
pub fn select_next(
&mut self,
_: &menu::SelectNext,
window: &mut Window,
cx: &mut Context<Self>,
) {
pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
if self.selected_index == count - 1 {
self.set_selected_index(0, window, cx);
self.set_selected_index(0, cx);
} else {
self.set_selected_index(self.selected_index + 1, window, cx);
self.set_selected_index(self.selected_index + 1, cx);
}
}
}
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
self.set_selected_index(0, window, cx);
self.set_selected_index(0, cx);
}
}
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
self.set_selected_index(count - 1, window, cx);
self.set_selected_index(count - 1, cx);
}
}
fn set_selected_index(&mut self, index: usize, _window: &mut Window, cx: &mut Context<Self>) {
fn set_selected_index(&mut self, index: usize, cx: &mut ViewContext<Self>) {
self.selected_index = index;
self.scroll_handle
.scroll_to_item(index, ScrollStrategy::Top);
cx.notify();
}
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(thread) = threads.get(self.selected_index) {
self.assistant_panel
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
.update(cx, move |this, cx| this.open_thread(&thread.id, cx))
.ok();
cx.notify();
}
}
fn remove_selected_thread(
&mut self,
_: &RemoveSelectedThread,
_window: &mut Window,
cx: &mut Context<Self>,
) {
fn remove_selected_thread(&mut self, _: &RemoveSelectedThread, cx: &mut ViewContext<Self>) {
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(thread) = threads.get(self.selected_index) {
@@ -117,14 +101,14 @@ impl ThreadHistory {
}
}
impl Focusable for ThreadHistory {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
impl FocusableView for ThreadHistory {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ThreadHistory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
let selected_index = self.selected_index;
@@ -154,10 +138,10 @@ impl Render for ThreadHistory {
} else {
history.child(
uniform_list(
cx.entity().clone(),
cx.view().clone(),
"thread-history",
threads.len(),
move |history, range, _window, _cx| {
move |history, range, _cx| {
threads[range]
.iter()
.enumerate()
@@ -182,14 +166,14 @@ impl Render for ThreadHistory {
#[derive(IntoElement)]
pub struct PastThread {
thread: SavedThreadMetadata,
assistant_panel: WeakEntity<AssistantPanel>,
assistant_panel: WeakView<AssistantPanel>,
selected: bool,
}
impl PastThread {
pub fn new(
thread: SavedThreadMetadata,
assistant_panel: WeakEntity<AssistantPanel>,
assistant_panel: WeakView<AssistantPanel>,
selected: bool,
) -> Self {
Self {
@@ -201,7 +185,7 @@ impl PastThread {
}
impl RenderOnce for PastThread {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let summary = self.thread.summary;
let thread_timestamp = time_format::format_localized_timestamp(
@@ -225,21 +209,21 @@ impl RenderOnce for PastThread {
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
.end_slot(
h_flex()
.gap_1p5()
.gap_2()
.child(
Label::new(thread_timestamp)
.color(Color::Muted)
.size(LabelSize::XSmall),
.color(Color::Disabled)
.size(LabelSize::Small),
)
.child(
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.tooltip(Tooltip::text("Delete Thread"))
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Delete Thread", cx))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = self.thread.id.clone();
move |_event, _window, cx| {
move |_event, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_thread(&id, cx);
@@ -252,10 +236,10 @@ impl RenderOnce for PastThread {
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = self.thread.id.clone();
move |_event, window, cx| {
move |_event, cx| {
assistant_panel
.update(cx, |this, cx| {
this.open_thread(&id, window, cx).detach_and_log_err(cx);
this.open_thread(&id, cx).detach_and_log_err(cx);
})
.ok();
}

View File

@@ -9,7 +9,7 @@ use context_server::manager::ContextServerManager;
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use futures::future::{self, BoxFuture, Shared};
use futures::FutureExt as _;
use gpui::{prelude::*, App, BackgroundExecutor, Context, Entity, SharedString, Task};
use gpui::{prelude::*, AppContext, BackgroundExecutor, Model, ModelContext, SharedString, Task};
use heed::types::SerdeBincode;
use heed::Database;
use language_model::Role;
@@ -21,9 +21,9 @@ use crate::thread::{MessageId, Thread, ThreadId};
pub struct ThreadStore {
#[allow(unused)]
project: Entity<Project>,
project: Model<Project>,
tools: Arc<ToolWorkingSet>,
context_server_manager: Entity<ContextServerManager>,
context_server_manager: Model<ContextServerManager>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
threads: Vec<SavedThreadMetadata>,
database_future: Shared<BoxFuture<'static, Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>,
@@ -31,42 +31,46 @@ pub struct ThreadStore {
impl ThreadStore {
pub fn new(
project: Entity<Project>,
project: Model<Project>,
tools: Arc<ToolWorkingSet>,
cx: &mut App,
) -> Result<Entity<Self>> {
let this = cx.new(|cx| {
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new_model(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
let executor = cx.background_executor().clone();
let database_future = executor
.spawn({
let executor = executor.clone();
let database_path = paths::support_dir().join("threads/threads-db.0.mdb");
async move { ThreadsDatabase::new(database_path, executor) }
})
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
.shared();
let executor = cx.background_executor().clone();
let database_future = executor
.spawn({
let executor = executor.clone();
let database_path = paths::support_dir().join("threads/threads-db.0.mdb");
async move { ThreadsDatabase::new(database_path, executor) }
})
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
.shared();
let this = Self {
project,
tools,
context_server_manager,
context_server_tool_ids: HashMap::default(),
threads: Vec::new(),
database_future,
};
this.register_context_server_handlers(cx);
this.reload(cx).detach_and_log_err(cx);
let this = Self {
project,
tools,
context_server_manager,
context_server_tool_ids: HashMap::default(),
threads: Vec::new(),
database_future,
};
this.register_context_server_handlers(cx);
this
});
this
})?;
Ok(this)
this.update(&mut cx, |this, cx| this.reload(cx))?.await?;
Ok(this)
})
}
/// Returns the number of threads.
@@ -84,15 +88,15 @@ impl ThreadStore {
self.threads().into_iter().take(limit).collect()
}
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
cx.new(|cx| Thread::new(self.tools.clone(), cx))
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> {
cx.new_model(|cx| Thread::new(self.tools.clone(), cx))
}
pub fn open_thread(
&self,
id: &ThreadId,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Thread>>> {
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Thread>>> {
let id = id.clone();
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
@@ -103,12 +107,16 @@ impl ThreadStore {
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
this.update(&mut cx, |this, cx| {
cx.new(|cx| Thread::from_saved(id.clone(), thread, this.tools.clone(), cx))
cx.new_model(|cx| Thread::from_saved(id.clone(), thread, this.tools.clone(), cx))
})
})
}
pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
pub fn save_thread(
&self,
thread: &Model<Thread>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let (metadata, thread) = thread.update(cx, |thread, _cx| {
let id = thread.id().clone();
let thread = SavedThread {
@@ -136,7 +144,11 @@ impl ThreadStore {
})
}
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
pub fn delete_thread(
&mut self,
id: &ThreadId,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let id = id.clone();
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
@@ -149,7 +161,7 @@ impl ThreadStore {
})
}
fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
fn reload(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
let threads = database_future
@@ -165,7 +177,7 @@ impl ThreadStore {
})
}
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
cx.subscribe(
&self.context_server_manager.clone(),
Self::handle_context_server_event,
@@ -175,9 +187,9 @@ impl ThreadStore {
fn handle_context_server_event(
&mut self,
context_server_manager: Entity<ContextServerManager>,
context_server_manager: Model<ContextServerManager>,
event: &context_server::manager::Event,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let tool_working_set = self.tools.clone();
match event {

View File

@@ -11,15 +11,15 @@ pub enum ContextPill {
context: ContextSnapshot,
dupe_name: bool,
focused: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
},
Suggested {
name: SharedString,
icon_path: Option<SharedString>,
kind: ContextKind,
focused: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
},
}
@@ -28,7 +28,7 @@ impl ContextPill {
context: ContextSnapshot,
dupe_name: bool,
focused: bool,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
) -> Self {
Self::Added {
context,
@@ -54,7 +54,7 @@ impl ContextPill {
}
}
pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>) -> Self {
pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
match &mut self {
ContextPill::Added { on_click, .. } => {
*on_click = Some(listener);
@@ -95,7 +95,7 @@ impl ContextPill {
}
impl RenderOnce for ContextPill {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let color = cx.theme().colors();
let base_pill = h_flex()
@@ -139,7 +139,7 @@ impl RenderOnce for ContextPill {
}
})
.when_some(context.tooltip.clone(), |element, tooltip| {
element.tooltip(Tooltip::text(tooltip.clone()))
element.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
}),
)
.when_some(on_remove.as_ref(), |element, on_remove| {
@@ -147,16 +147,16 @@ impl RenderOnce for ContextPill {
IconButton::new(("remove", context.id.0), IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.tooltip(Tooltip::text("Remove Context"))
.tooltip(|cx| Tooltip::text("Remove Context", cx))
.on_click({
let on_remove = on_remove.clone();
move |event, window, cx| on_remove(event, window, cx)
move |event, cx| on_remove(event, cx)
}),
)
})
.when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone();
element.on_click(move |event, window, cx| on_click(event, window, cx))
element.on_click(move |event, cx| on_click(event, cx))
}),
ContextPill::Suggested {
name,
@@ -195,12 +195,10 @@ impl RenderOnce for ContextPill {
.size(IconSize::XSmall)
.into_any_element(),
)
.tooltip(|window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
})
.tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
.when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone();
element.on_click(move |event, window, cx| on_click(event, window, cx))
element.on_click(move |event, cx| on_click(event, cx))
}),
}
}

View File

@@ -16,12 +16,14 @@ anyhow.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
context_server.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true

View File

@@ -9,7 +9,7 @@ mod slash_command_picker;
use std::sync::Arc;
use client::Client;
use gpui::App;
use gpui::AppContext;
pub use crate::context::*;
pub use crate::context_editor::*;
@@ -18,6 +18,6 @@ pub use crate::context_store::*;
pub use crate::patch::*;
pub use crate::slash_command::*;
pub fn init(client: Arc<Client>, _cx: &mut App) {
pub fn init(client: Arc<Client>, _cx: &mut AppContext) {
context_store::init(&client.into());
}

View File

@@ -8,19 +8,22 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandWorkingSet,
};
use assistant_slash_commands::FileCommandMetadata;
use assistant_tool::ToolWorkingSet;
use client::{self, proto, telemetry::Telemetry};
use clock::ReplicaId;
use collections::{HashMap, HashSet};
use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
use fs::{Fs, RemoveOptions};
use futures::{future::Shared, FutureExt, StreamExt};
use gpui::{
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
Task,
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString,
Subscription, Task,
};
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role, StopReason,
};
use language_models::{
@@ -435,6 +438,11 @@ pub enum ContextEvent {
SlashCommandOutputSectionAdded {
section: SlashCommandOutputSection<language::Anchor>,
},
UsePendingTools,
ToolFinished {
tool_use_id: LanguageModelToolUseId,
output_range: Range<language::Anchor>,
},
Operation(ContextOperation),
}
@@ -520,12 +528,21 @@ pub enum Content {
render_image: Arc<RenderImage>,
image: Shared<Task<Option<LanguageModelImage>>>,
},
ToolUse {
range: Range<language::Anchor>,
tool_use: LanguageModelToolUse,
},
ToolResult {
range: Range<language::Anchor>,
tool_use_id: LanguageModelToolUseId,
},
}
impl Content {
fn range(&self) -> Range<language::Anchor> {
match self {
Self::Image { anchor, .. } => *anchor..*anchor,
Self::ToolUse { range, .. } | Self::ToolResult { range, .. } => range.clone(),
}
}
@@ -571,18 +588,20 @@ pub enum XmlTagKind {
Operation,
}
pub struct AssistantContext {
pub struct Context {
id: ContextId,
timestamp: clock::Lamport,
version: clock::Global,
pending_ops: Vec<ContextOperation>,
operations: Vec<ContextOperation>,
buffer: Entity<Buffer>,
buffer: Model<Buffer>,
parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
message_anchors: Vec<MessageAnchor>,
contents: Vec<Content>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
@@ -600,7 +619,7 @@ pub struct AssistantContext {
language_registry: Arc<LanguageRegistry>,
patches: Vec<AssistantPatch>,
xml_tags: Vec<XmlTag>,
project: Option<Entity<Project>>,
project: Option<Model<Project>>,
prompt_builder: Arc<PromptBuilder>,
}
@@ -626,16 +645,17 @@ impl ContextAnnotation for XmlTag {
}
}
impl EventEmitter<ContextEvent> for AssistantContext {}
impl EventEmitter<ContextEvent> for Context {}
impl AssistantContext {
impl Context {
pub fn local(
language_registry: Arc<LanguageRegistry>,
project: Option<Entity<Project>>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
cx: &mut Context<Self>,
tools: Arc<ToolWorkingSet>,
cx: &mut ModelContext<Self>,
) -> Self {
Self::new(
ContextId::new(),
@@ -644,6 +664,7 @@ impl AssistantContext {
language_registry,
prompt_builder,
slash_commands,
tools,
project,
telemetry,
cx,
@@ -658,11 +679,12 @@ impl AssistantContext {
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
project: Option<Entity<Project>>,
tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Self {
let buffer = cx.new(|_cx| {
let buffer = cx.new_model(|_cx| {
let buffer = Buffer::remote(
language::BufferId::new(1).unwrap(),
replica_id,
@@ -685,6 +707,7 @@ impl AssistantContext {
messages_metadata: Default::default(),
parsed_slash_commands: Vec::new(),
invoked_slash_commands: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
slash_command_output_sections: Vec::new(),
edits_since_last_parse: edits_since_last_slash_command_parse,
summary: None,
@@ -702,6 +725,7 @@ impl AssistantContext {
project,
language_registry,
slash_commands,
tools,
patches: Vec::new(),
xml_tags: Vec::new(),
prompt_builder,
@@ -731,7 +755,7 @@ impl AssistantContext {
this
}
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
pub(crate) fn serialize(&self, cx: &AppContext) -> SavedContext {
let buffer = self.buffer.read(cx);
SavedContext {
id: Some(self.id.clone()),
@@ -778,9 +802,10 @@ impl AssistantContext {
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
project: Option<Entity<Project>>,
tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Self {
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let mut this = Self::new(
@@ -790,6 +815,7 @@ impl AssistantContext {
language_registry,
prompt_builder,
slash_commands,
tools,
project,
telemetry,
cx,
@@ -811,7 +837,7 @@ impl AssistantContext {
self.timestamp.replica_id
}
pub fn version(&self, cx: &App) -> ContextVersion {
pub fn version(&self, cx: &AppContext) -> ContextVersion {
ContextVersion {
context: self.version.clone(),
buffer: self.buffer.read(cx).version(),
@@ -822,7 +848,15 @@ impl AssistantContext {
&self.slash_commands
}
pub fn set_capability(&mut self, capability: language::Capability, cx: &mut Context<Self>) {
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
&self.tools
}
pub fn set_capability(
&mut self,
capability: language::Capability,
cx: &mut ModelContext<Self>,
) {
self.buffer
.update(cx, |buffer, cx| buffer.set_capability(capability, cx));
}
@@ -836,7 +870,7 @@ impl AssistantContext {
pub fn serialize_ops(
&self,
since: &ContextVersion,
cx: &App,
cx: &AppContext,
) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self
.buffer
@@ -871,7 +905,7 @@ impl AssistantContext {
pub fn apply_ops(
&mut self,
ops: impl IntoIterator<Item = ContextOperation>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let mut buffer_ops = Vec::new();
for op in ops {
@@ -885,7 +919,7 @@ impl AssistantContext {
self.flush_ops(cx);
}
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
fn flush_ops(&mut self, cx: &mut ModelContext<Context>) {
let mut changed_messages = HashSet::default();
let mut summary_changed = false;
@@ -1004,7 +1038,7 @@ impl AssistantContext {
}
}
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
fn can_apply_op(&self, op: &ContextOperation, cx: &AppContext) -> bool {
if !self.version.observed_all(op.version()) {
return false;
}
@@ -1035,7 +1069,7 @@ impl AssistantContext {
fn has_received_operations_for_anchor_range(
&self,
range: Range<text::Anchor>,
cx: &App,
cx: &AppContext,
) -> bool {
let version = &self.buffer.read(cx).version;
let observed_start = range.start == language::Anchor::MIN
@@ -1047,12 +1081,12 @@ impl AssistantContext {
observed_start && observed_end
}
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
fn push_op(&mut self, op: ContextOperation, cx: &mut ModelContext<Self>) {
self.operations.push(op.clone());
cx.emit(ContextEvent::Operation(op));
}
pub fn buffer(&self) -> &Entity<Buffer> {
pub fn buffer(&self) -> &Model<Buffer> {
&self.buffer
}
@@ -1060,7 +1094,7 @@ impl AssistantContext {
self.language_registry.clone()
}
pub fn project(&self) -> Option<Entity<Project>> {
pub fn project(&self) -> Option<Model<Project>> {
self.project.clone()
}
@@ -1076,7 +1110,7 @@ impl AssistantContext {
self.summary.as_ref()
}
pub fn patch_containing(&self, position: Point, cx: &App) -> Option<&AssistantPatch> {
pub fn patch_containing(&self, position: Point, cx: &AppContext) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.patches.binary_search_by(|patch| {
let patch_range = patch.range.to_point(&buffer);
@@ -1102,7 +1136,7 @@ impl AssistantContext {
pub fn patch_for_range(
&self,
range: &Range<language::Anchor>,
cx: &App,
cx: &AppContext,
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.patch_index_for_range(range, buffer).ok()?;
@@ -1133,7 +1167,7 @@ impl AssistantContext {
&self.slash_command_output_sections
}
pub fn contains_files(&self, cx: &App) -> bool {
pub fn contains_files(&self, cx: &AppContext) -> bool {
let buffer = self.buffer.read(cx);
self.slash_command_output_sections.iter().any(|section| {
section.is_valid(buffer)
@@ -1147,7 +1181,15 @@ impl AssistantContext {
})
}
fn set_language(&mut self, cx: &mut Context<Self>) {
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect()
}
pub fn get_tool_use_by_id(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> {
self.pending_tool_uses_by_id.get(id)
}
fn set_language(&mut self, cx: &mut ModelContext<Self>) {
let markdown = self.language_registry.language_for_name("Markdown");
cx.spawn(|this, mut cx| async move {
let markdown = markdown.await?;
@@ -1161,9 +1203,9 @@ impl AssistantContext {
fn handle_buffer_event(
&mut self,
_: Entity<Buffer>,
_: Model<Buffer>,
event: &language::BufferEvent,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
match event {
language::BufferEvent::Operation {
@@ -1185,7 +1227,7 @@ impl AssistantContext {
self.token_count
}
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut Context<Self>) {
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
// because otherwise you see in the UI that your empty message has a bunch of tokens already used.
let request = self.to_completion_request(RequestType::Chat, cx);
@@ -1213,7 +1255,7 @@ impl AssistantContext {
&mut self,
cache_configuration: &Option<LanguageModelCacheConfiguration>,
speculative: bool,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> bool {
let cache_configuration =
cache_configuration
@@ -1315,7 +1357,7 @@ impl AssistantContext {
new_anchor_needs_caching
}
fn start_cache_warming(&mut self, model: &Arc<dyn LanguageModel>, cx: &mut Context<Self>) {
fn start_cache_warming(&mut self, model: &Arc<dyn LanguageModel>, cx: &mut ModelContext<Self>) {
let cache_configuration = model.cache_configuration();
if !self.mark_cache_anchors(&cache_configuration, true, cx) {
@@ -1365,7 +1407,7 @@ impl AssistantContext {
});
}
pub fn update_cache_status_for_completion(&mut self, cx: &mut Context<Self>) {
pub fn update_cache_status_for_completion(&mut self, cx: &mut ModelContext<Self>) {
let cached_message_ids: Vec<MessageId> = self
.messages_metadata
.iter()
@@ -1390,7 +1432,7 @@ impl AssistantContext {
cx.notify();
}
pub fn reparse(&mut self, cx: &mut Context<Self>) {
pub fn reparse(&mut self, cx: &mut ModelContext<Self>) {
let buffer = self.buffer.read(cx).text_snapshot();
let mut row_ranges = self
.edits_since_last_parse
@@ -1463,7 +1505,7 @@ impl AssistantContext {
buffer: &BufferSnapshot,
updated: &mut Vec<ParsedSlashCommand>,
removed: &mut Vec<Range<text::Anchor>>,
cx: &App,
cx: &AppContext,
) {
let old_range = self.pending_command_indices_for_range(range.clone(), cx);
@@ -1517,7 +1559,7 @@ impl AssistantContext {
fn invalidate_pending_slash_commands(
&mut self,
buffer: &BufferSnapshot,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let mut invalidated_command_ids = Vec::new();
for (&command_id, command) in self.invoked_slash_commands.iter_mut() {
@@ -1551,7 +1593,7 @@ impl AssistantContext {
buffer: &BufferSnapshot,
updated: &mut Vec<Range<text::Anchor>>,
removed: &mut Vec<Range<text::Anchor>>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
// Rebuild the XML tags in the edited range.
let intersecting_tags_range =
@@ -1594,7 +1636,7 @@ impl AssistantContext {
&self,
buffer: &BufferSnapshot,
range: Range<text::Anchor>,
cx: &App,
cx: &AppContext,
) -> Vec<XmlTag> {
let mut messages = self.messages(cx).peekable();
@@ -1651,7 +1693,7 @@ impl AssistantContext {
tags_start_ix: usize,
buffer_end: text::Anchor,
buffer: &BufferSnapshot,
cx: &App,
cx: &AppContext,
) -> Vec<AssistantPatch> {
let mut new_patches = Vec::new();
let mut pending_patch = None;
@@ -1809,7 +1851,7 @@ impl AssistantContext {
pub fn pending_command_for_position(
&mut self,
position: language::Anchor,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Option<&mut ParsedSlashCommand> {
let buffer = self.buffer.read(cx);
match self
@@ -1833,7 +1875,7 @@ impl AssistantContext {
pub fn pending_commands_for_range(
&self,
range: Range<language::Anchor>,
cx: &App,
cx: &AppContext,
) -> &[ParsedSlashCommand] {
let range = self.pending_command_indices_for_range(range, cx);
&self.parsed_slash_commands[range]
@@ -1842,7 +1884,7 @@ impl AssistantContext {
fn pending_command_indices_for_range(
&self,
range: Range<language::Anchor>,
cx: &App,
cx: &AppContext,
) -> Range<usize> {
self.indices_intersecting_buffer_range(&self.parsed_slash_commands, range, cx)
}
@@ -1851,7 +1893,7 @@ impl AssistantContext {
&self,
all_annotations: &[T],
range: Range<language::Anchor>,
cx: &App,
cx: &AppContext,
) -> Range<usize> {
let buffer = self.buffer.read(cx);
let start_ix = match all_annotations
@@ -1874,7 +1916,7 @@ impl AssistantContext {
name: &str,
output: Task<SlashCommandResult>,
ensure_trailing_newline: bool,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let version = self.version.clone();
let command_id = InvokedSlashCommandId(self.next_timestamp());
@@ -2142,7 +2184,7 @@ impl AssistantContext {
fn insert_slash_command_output_section(
&mut self,
section: SlashCommandOutputSection<language::Anchor>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let buffer = self.buffer.read(cx);
let insertion_ix = match self
@@ -2168,11 +2210,73 @@ impl AssistantContext {
);
}
pub fn completion_provider_changed(&mut self, cx: &mut Context<Self>) {
pub fn insert_tool_output(
&mut self,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut ModelContext<Self>,
) {
let insert_output_task = cx.spawn(|this, mut cx| {
let tool_use_id = tool_use_id.clone();
async move {
let output = output.await;
this.update(&mut cx, |this, cx| match output {
Ok(mut output) => {
const NEWLINE: char = '\n';
if !output.ends_with(NEWLINE) {
output.push(NEWLINE);
}
let anchor_range = this.buffer.update(cx, |buffer, cx| {
let insert_start = buffer.len().to_offset(buffer);
let insert_end = insert_start;
let start = insert_start;
let end = start + output.len() - NEWLINE.len_utf8();
buffer.edit([(insert_start..insert_end, output)], None, cx);
let output_range = buffer.anchor_after(start)..buffer.anchor_after(end);
output_range
});
this.insert_content(
Content::ToolResult {
range: anchor_range.clone(),
tool_use_id: tool_use_id.clone(),
},
cx,
);
cx.emit(ContextEvent::ToolFinished {
tool_use_id,
output_range: anchor_range,
});
}
Err(err) => {
if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Error(err.to_string());
}
}
})
.ok();
}
});
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Running {
_task: insert_output_task.shared(),
};
}
}
pub fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
self.count_remaining_tokens(cx);
}
fn get_last_valid_message_id(&self, cx: &Context<Self>) -> Option<MessageId> {
fn get_last_valid_message_id(&self, cx: &ModelContext<Self>) -> Option<MessageId> {
self.message_anchors.iter().rev().find_map(|message| {
message
.start
@@ -2184,7 +2288,7 @@ impl AssistantContext {
pub fn assist(
&mut self,
request_type: RequestType,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Option<MessageAnchor> {
let model_registry = LanguageModelRegistry::read_global(cx);
let provider = model_registry.active_provider()?;
@@ -2198,7 +2302,23 @@ impl AssistantContext {
// Compute which messages to cache, including the last one.
self.mark_cache_anchors(&model.cache_configuration(), false, cx);
let request = self.to_completion_request(request_type, cx);
let mut request = self.to_completion_request(request_type, cx);
// Don't attach tools for now; we'll be removing tool use from
// Assistant1 shortly.
#[allow(clippy::overly_complex_bool_expr)]
if false && cx.has_flag::<ToolUseFeatureFlag>() {
request.tools = self
.tools
.tools(cx)
.into_iter()
.map(|tool| LanguageModelRequestTool {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
})
.collect();
}
let assistant_message = self
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
@@ -2255,7 +2375,44 @@ impl AssistantContext {
cx,
);
}
LanguageModelCompletionEvent::ToolUse(_) => {}
LanguageModelCompletionEvent::ToolUse(tool_use) => {
const NEWLINE: char = '\n';
let mut text = String::new();
text.push(NEWLINE);
text.push_str(
&serde_json::to_string_pretty(&tool_use)
.expect("failed to serialize tool use to JSON"),
);
text.push(NEWLINE);
let text_len = text.len();
buffer.edit(
[(
message_old_end_offset..message_old_end_offset,
text,
)],
None,
cx,
);
let start_ix = message_old_end_offset + NEWLINE.len_utf8();
let end_ix =
message_old_end_offset + text_len - NEWLINE.len_utf8();
let source_range = buffer.anchor_after(start_ix)
..buffer.anchor_after(end_ix);
this.pending_tool_uses_by_id.insert(
tool_use.id.clone(),
PendingToolUse {
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
status: PendingToolUseStatus::Idle,
source_range,
},
);
}
}
});
@@ -2338,7 +2495,9 @@ impl AssistantContext {
if let Ok(stop_reason) = result {
match stop_reason {
StopReason::ToolUse => {}
StopReason::ToolUse => {
cx.emit(ContextEvent::UsePendingTools);
}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
}
@@ -2360,7 +2519,7 @@ impl AssistantContext {
pub fn to_completion_request(
&self,
request_type: RequestType,
cx: &App,
cx: &AppContext,
) -> LanguageModelRequest {
let buffer = self.buffer.read(cx);
@@ -2417,6 +2576,23 @@ impl AssistantContext {
.push(language_model::MessageContent::Image(image));
}
}
Content::ToolUse { tool_use, .. } => {
request_message
.content
.push(language_model::MessageContent::ToolUse(tool_use.clone()));
}
Content::ToolResult { tool_use_id, .. } => {
request_message.content.push(
language_model::MessageContent::ToolResult(
LanguageModelToolResult {
tool_use_id: tool_use_id.to_string(),
is_error: false,
content: collect_text_content(buffer, range.clone())
.unwrap_or_default(),
},
),
);
}
}
offset = range.end;
@@ -2455,7 +2631,7 @@ impl AssistantContext {
completion_request
}
pub fn cancel_last_assist(&mut self, cx: &mut Context<Self>) -> bool {
pub fn cancel_last_assist(&mut self, cx: &mut ModelContext<Self>) -> bool {
if let Some(pending_completion) = self.pending_completions.pop() {
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
if metadata.status == MessageStatus::Pending {
@@ -2468,7 +2644,7 @@ impl AssistantContext {
}
}
pub fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut Context<Self>) {
pub fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
for id in &ids {
if let Some(metadata) = self.messages_metadata.get(id) {
let role = metadata.role.cycle();
@@ -2479,7 +2655,7 @@ impl AssistantContext {
self.message_roles_updated(ids, cx);
}
fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut Context<Self>) {
fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
let mut ranges = Vec::new();
for message in self.messages(cx) {
if ids.contains(&message.id) {
@@ -2502,7 +2678,7 @@ impl AssistantContext {
pub fn update_metadata(
&mut self,
id: MessageId,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
f: impl FnOnce(&mut MessageMetadata),
) {
let version = self.version.clone();
@@ -2526,7 +2702,7 @@ impl AssistantContext {
message_id: MessageId,
role: Role,
status: MessageStatus,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> Option<MessageAnchor> {
if let Some(prev_message_ix) = self
.message_anchors
@@ -2560,7 +2736,7 @@ impl AssistantContext {
offset: usize,
role: Role,
status: MessageStatus,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> MessageAnchor {
let start = self.buffer.update(cx, |buffer, cx| {
buffer.edit([(offset..offset, "\n")], None, cx);
@@ -2590,7 +2766,7 @@ impl AssistantContext {
anchor
}
pub fn insert_content(&mut self, content: Content, cx: &mut Context<Self>) {
pub fn insert_content(&mut self, content: Content, cx: &mut ModelContext<Self>) {
let buffer = self.buffer.read(cx);
let insertion_ix = match self
.contents
@@ -2606,7 +2782,7 @@ impl AssistantContext {
cx.emit(ContextEvent::MessagesEdited);
}
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
pub fn contents<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Content> {
let buffer = self.buffer.read(cx);
self.contents
.iter()
@@ -2620,7 +2796,7 @@ impl AssistantContext {
pub fn split_message(
&mut self,
range: Range<usize>,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
let start_message = self.message_for_offset(range.start, cx);
let end_message = self.message_for_offset(range.end, cx);
@@ -2746,7 +2922,7 @@ impl AssistantContext {
&mut self,
new_anchor: MessageAnchor,
new_metadata: MessageMetadata,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
cx.emit(ContextEvent::MessagesEdited);
@@ -2764,7 +2940,7 @@ impl AssistantContext {
self.message_anchors.insert(insertion_ix, new_anchor);
}
pub fn summarize(&mut self, replace_old: bool, cx: &mut Context<Self>) {
pub fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return;
};
@@ -2842,14 +3018,14 @@ impl AssistantContext {
}
}
fn message_for_offset(&self, offset: usize, cx: &App) -> Option<Message> {
fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
self.messages_for_offsets([offset], cx).pop()
}
pub fn messages_for_offsets(
&self,
offsets: impl IntoIterator<Item = usize>,
cx: &App,
cx: &AppContext,
) -> Vec<Message> {
let mut result = Vec::new();
@@ -2882,14 +3058,14 @@ impl AssistantContext {
fn messages_from_anchors<'a>(
&'a self,
message_anchors: impl Iterator<Item = &'a MessageAnchor> + 'a,
cx: &'a App,
cx: &'a AppContext,
) -> impl 'a + Iterator<Item = Message> {
let buffer = self.buffer.read(cx);
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
}
pub fn messages<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Message> {
pub fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
self.messages_from_anchors(self.message_anchors.iter(), cx)
}
@@ -2937,7 +3113,7 @@ impl AssistantContext {
&mut self,
debounce: Option<Duration>,
fs: Arc<dyn Fs>,
cx: &mut Context<AssistantContext>,
cx: &mut ModelContext<Context>,
) {
if self.replica_id() != ReplicaId::default() {
// Prevent saving a remote context for now.
@@ -3003,7 +3179,7 @@ impl AssistantContext {
});
}
pub fn custom_summary(&mut self, custom_summary: String, cx: &mut Context<Self>) {
pub fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
let timestamp = self.next_timestamp();
let summary = self.summary.get_or_insert(ContextSummary::default());
summary.timestamp = timestamp;
@@ -3163,8 +3339,8 @@ impl SavedContext {
fn into_ops(
self,
buffer: &Entity<Buffer>,
cx: &mut Context<AssistantContext>,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Context>,
) -> Vec<ContextOperation> {
let mut operations = Vec::new();
let mut version = clock::Global::new();

View File

@@ -1,5 +1,5 @@
use crate::{
AssistantContext, AssistantEdit, AssistantEditKind, CacheStatus, ContextEvent, ContextId,
AssistantEdit, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId,
ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
};
use anyhow::Result;
@@ -8,13 +8,14 @@ use assistant_slash_command::{
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
};
use assistant_slash_commands::FileSlashCommand;
use assistant_tool::ToolWorkingSet;
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{
channel::mpsc,
stream::{self, StreamExt},
};
use gpui::{prelude::*, App, Entity, SharedString, Task, TestAppContext, WeakEntity};
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakView};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
@@ -33,7 +34,7 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
use ui::{IconName, Window};
use ui::{IconName, WindowContext};
use unindent::Unindent;
use util::{
test::{generate_marked_text, marked_text_ranges},
@@ -42,19 +43,20 @@ use util::{
use workspace::Workspace;
#[gpui::test]
fn test_inserting_and_removing_messages(cx: &mut App) {
fn test_inserting_and_removing_messages(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -181,20 +183,21 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
}
#[gpui::test]
fn test_message_splitting(cx: &mut App) {
fn test_message_splitting(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
LanguageModelRegistry::test(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -284,19 +287,20 @@ fn test_message_splitting(cx: &mut App) {
}
#[gpui::test]
fn test_messages_for_offsets(cx: &mut App) {
fn test_messages_for_offsets(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -363,9 +367,9 @@ fn test_messages_for_offsets(cx: &mut App) {
);
fn message_ids_for_offsets(
context: &Entity<AssistantContext>,
context: &Model<Context>,
offsets: &[usize],
cx: &App,
cx: &AppContext,
) -> Vec<MessageId> {
context
.read(cx)
@@ -403,13 +407,14 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -603,7 +608,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
#[track_caller]
fn assert_text_and_context_ranges(
buffer: &Entity<Buffer>,
buffer: &Model<Buffer>,
ranges: &RefCell<ContextRanges>,
expected_marked_text: &str,
cx: &mut TestAppContext,
@@ -692,13 +697,14 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
// Create a new context
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
Some(project),
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -956,13 +962,14 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
// Ensure steps are re-parsed when deserializing.
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
let deserialized_context = cx.new(|cx| {
AssistantContext::deserialize(
let deserialized_context = cx.new_model(|cx| {
Context::deserialize(
serialized_context,
Default::default(),
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -999,11 +1006,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx,
);
fn edit(
context: &Entity<AssistantContext>,
new_text_marked_with_edits: &str,
cx: &mut TestAppContext,
) {
fn edit(context: &Model<Context>, new_text_marked_with_edits: &str, cx: &mut TestAppContext) {
context.update(cx, |context, cx| {
context.buffer.update(cx, |buffer, cx| {
buffer.edit_via_marked_text(&new_text_marked_with_edits.unindent(), None, cx);
@@ -1014,7 +1017,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
#[track_caller]
fn expect_patches(
context: &Entity<AssistantContext>,
context: &Model<Context>,
expected_marked_text: &str,
expected_suggestions: &[&[AssistantEdit]],
cx: &mut TestAppContext,
@@ -1074,13 +1077,14 @@ async fn test_serialization(cx: &mut TestAppContext) {
cx.update(LanguageModelRegistry::test);
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -1117,13 +1121,14 @@ async fn test_serialization(cx: &mut TestAppContext) {
);
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
let deserialized_context = cx.new(|cx| {
AssistantContext::deserialize(
let deserialized_context = cx.new_model(|cx| {
Context::deserialize(
serialized_context,
Default::default(),
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1174,14 +1179,15 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
let context_id = ContextId::new();
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
for i in 0..num_peers {
let context = cx.new(|cx| {
AssistantContext::new(
let context = cx.new_model(|cx| {
Context::new(
context_id.clone(),
i as ReplicaId,
language::Capability::ReadWrite,
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1428,19 +1434,20 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
}
#[gpui::test]
fn test_mark_cache_anchors(cx: &mut App) {
fn test_mark_cache_anchors(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new(|cx| {
AssistantContext::local(
let context = cx.new_model(|cx| {
Context::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -1587,7 +1594,7 @@ fn test_mark_cache_anchors(cx: &mut App) {
);
}
fn messages(context: &Entity<AssistantContext>, cx: &App) -> Vec<(MessageId, Role, Range<usize>)> {
fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> {
context
.read(cx)
.messages(cx)
@@ -1596,8 +1603,8 @@ fn messages(context: &Entity<AssistantContext>, cx: &App) -> Vec<(MessageId, Rol
}
fn messages_cache(
context: &Entity<AssistantContext>,
cx: &App,
context: &Model<Context>,
cx: &AppContext,
) -> Vec<(MessageId, Option<MessageCacheMetadata>)> {
context
.read(cx)
@@ -1626,9 +1633,8 @@ impl SlashCommand for FakeSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(vec![]))
}
@@ -1642,10 +1648,9 @@ impl SlashCommand for FakeSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakEntity<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
_cx: &mut App,
_cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0),

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
use std::sync::Arc;
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
use gpui::{
AppContext, EventEmitter, FocusHandle, FocusableView, Model, Subscription, Task, View, WeakView,
};
use picker::{Picker, PickerDelegate};
use project::Project;
use ui::utils::{format_distance_from_now, DateTimeType};
@@ -23,23 +25,21 @@ enum SavedContextPickerEvent {
}
pub struct ContextHistory {
picker: Entity<Picker<SavedContextPickerDelegate>>,
picker: View<Picker<SavedContextPickerDelegate>>,
_subscriptions: Vec<Subscription>,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
}
impl ContextHistory {
pub fn new(
project: Entity<Project>,
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
project: Model<Project>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
let picker = cx.new(|cx| {
let picker = cx.new_view(|cx| {
Picker::uniform_list(
SavedContextPickerDelegate::new(project, context_store.clone()),
window,
cx,
)
.modal(false)
@@ -47,11 +47,10 @@ impl ContextHistory {
});
let subscriptions = vec![
cx.observe_in(&context_store, window, |this, _, window, cx| {
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.observe(&context_store, |this, _, cx| {
this.picker.update(cx, |picker, cx| picker.refresh(cx));
}),
cx.subscribe_in(&picker, window, Self::handle_picker_event),
cx.subscribe(&picker, Self::handle_picker_event),
];
Self {
@@ -63,10 +62,9 @@ impl ContextHistory {
fn handle_picker_event(
&mut self,
_: &Entity<Picker<SavedContextPickerDelegate>>,
_: View<Picker<SavedContextPickerDelegate>>,
event: &SavedContextPickerEvent,
window: &mut Window,
cx: &mut Context<Self>,
cx: &mut ViewContext<Self>,
) {
let SavedContextPickerEvent::Confirmed(context) = event;
@@ -78,12 +76,12 @@ impl ContextHistory {
.update(cx, |workspace, cx| match context {
ContextMetadata::Remote(metadata) => {
assistant_panel_delegate
.open_remote_context(workspace, metadata.id.clone(), window, cx)
.open_remote_context(workspace, metadata.id.clone(), cx)
.detach_and_log_err(cx);
}
ContextMetadata::Saved(metadata) => {
assistant_panel_delegate
.open_saved_context(workspace, metadata.path.clone(), window, cx)
.open_saved_context(workspace, metadata.path.clone(), cx)
.detach_and_log_err(cx);
}
})
@@ -92,13 +90,13 @@ impl ContextHistory {
}
impl Render for ContextHistory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
div().size_full().child(self.picker.clone())
}
}
impl Focusable for ContextHistory {
fn focus_handle(&self, cx: &App) -> FocusHandle {
impl FocusableView for ContextHistory {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
@@ -108,14 +106,14 @@ impl EventEmitter<()> for ContextHistory {}
impl Item for ContextHistory {
type Event = ();
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
Some("History".into())
}
}
struct SavedContextPickerDelegate {
store: Entity<ContextStore>,
project: Entity<Project>,
store: Model<ContextStore>,
project: Model<Project>,
matches: Vec<ContextMetadata>,
selected_index: usize,
}
@@ -123,7 +121,7 @@ struct SavedContextPickerDelegate {
impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
impl SavedContextPickerDelegate {
fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {
fn new(project: Model<Project>, store: Model<ContextStore>) -> Self {
Self {
project,
store,
@@ -144,25 +142,15 @@ impl PickerDelegate for SavedContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search...".into()
}
fn update_matches(
&mut self,
query: String,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search = self.store.read(cx).search(query, cx);
cx.spawn(|this, mut cx| async move {
let matches = search.await;
@@ -181,20 +169,19 @@ impl PickerDelegate for SavedContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(metadata) = self.matches.get(self.selected_index) {
cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
}
}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let context = self.matches.get(ix)?;
let item = match context {

View File

@@ -1,18 +1,21 @@
use crate::{
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata,
};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
use assistant_tool::{ToolId, ToolWorkingSet};
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
use clock::ReplicaId;
use collections::HashMap;
use context_server::manager::ContextServerManager;
use context_server::ContextServerFactoryRegistry;
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use fs::Fs;
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
};
use language::LanguageRegistry;
use paths::contexts_dir;
use project::Project;
@@ -31,11 +34,11 @@ use std::{
use util::{ResultExt, TryFutureExt};
pub(crate) fn init(client: &AnyProtoClient) {
client.add_entity_message_handler(ContextStore::handle_advertise_contexts);
client.add_entity_request_handler(ContextStore::handle_open_context);
client.add_entity_request_handler(ContextStore::handle_create_context);
client.add_entity_message_handler(ContextStore::handle_update_context);
client.add_entity_request_handler(ContextStore::handle_synchronize_contexts);
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
client.add_model_request_handler(ContextStore::handle_open_context);
client.add_model_request_handler(ContextStore::handle_create_context);
client.add_model_message_handler(ContextStore::handle_update_context);
client.add_model_request_handler(ContextStore::handle_synchronize_contexts);
}
#[derive(Clone)]
@@ -47,16 +50,18 @@ pub struct RemoteContextMetadata {
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
context_server_manager: Entity<ContextServerManager>,
context_server_manager: Model<ContextServerManager>,
context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
host_contexts: Vec<RemoteContextMetadata>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
telemetry: Arc<Telemetry>,
_watch_updates: Task<Option<()>>,
client: Arc<Client>,
project: Entity<Project>,
project: Model<Project>,
project_is_shared: bool,
client_subscription: Option<client::Subscription>,
_project_subscriptions: Vec<gpui::Subscription>,
@@ -70,19 +75,19 @@ pub enum ContextStoreEvent {
impl EventEmitter<ContextStoreEvent> for ContextStore {}
enum ContextHandle {
Weak(WeakEntity<AssistantContext>),
Strong(Entity<AssistantContext>),
Weak(WeakModel<Context>),
Strong(Model<Context>),
}
impl ContextHandle {
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
fn upgrade(&self) -> Option<Model<Context>> {
match self {
ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()),
}
}
fn downgrade(&self) -> WeakEntity<AssistantContext> {
fn downgrade(&self) -> WeakModel<Context> {
match self {
ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(),
@@ -92,11 +97,12 @@ impl ContextHandle {
impl ContextStore {
pub fn new(
project: Entity<Project>,
project: Model<Project>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
cx: &mut App,
) -> Task<Result<Entity<Self>>> {
tools: Arc<ToolWorkingSet>,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
let fs = project.read(cx).fs().clone();
let languages = project.read(cx).languages().clone();
let telemetry = project.read(cx).client().telemetry().clone();
@@ -104,10 +110,10 @@ impl ContextStore {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new(|cx: &mut Context<Self>| {
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new(|cx| {
let context_server_manager = cx.new_model(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
let mut this = Self {
@@ -115,10 +121,12 @@ impl ContextStore {
contexts_metadata: Vec::new(),
context_server_manager,
context_server_slash_command_ids: HashMap::default(),
context_server_tool_ids: HashMap::default(),
host_contexts: Vec::new(),
fs,
languages,
slash_commands,
tools,
telemetry,
_watch_updates: cx.spawn(|this, mut cx| {
async move {
@@ -144,18 +152,20 @@ impl ContextStore {
this.handle_project_changed(project.clone(), cx);
this.synchronize_contexts(cx);
this.register_context_server_handlers(cx);
this.reload(cx).detach_and_log_err(cx);
this
})?;
this.update(&mut cx, |this, cx| this.reload(cx))?
.await
.log_err();
Ok(this)
})
}
async fn handle_advertise_contexts(
this: Entity<Self>,
this: Model<Self>,
envelope: TypedEnvelope<proto::AdvertiseContexts>,
mut cx: AsyncApp,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.host_contexts = envelope
@@ -172,9 +182,9 @@ impl ContextStore {
}
async fn handle_open_context(
this: Entity<Self>,
this: Model<Self>,
envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncApp,
mut cx: AsyncAppContext,
) -> Result<proto::OpenContextResponse> {
let context_id = ContextId::from_proto(envelope.payload.context_id);
let operations = this.update(&mut cx, |this, cx| {
@@ -202,9 +212,9 @@ impl ContextStore {
}
async fn handle_create_context(
this: Entity<Self>,
this: Model<Self>,
_: TypedEnvelope<proto::CreateContext>,
mut cx: AsyncApp,
mut cx: AsyncAppContext,
) -> Result<proto::CreateContextResponse> {
let (context_id, operations) = this.update(&mut cx, |this, cx| {
if this.project.read(cx).is_via_collab() {
@@ -230,9 +240,9 @@ impl ContextStore {
}
async fn handle_update_context(
this: Entity<Self>,
this: Model<Self>,
envelope: TypedEnvelope<proto::UpdateContext>,
mut cx: AsyncApp,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
let context_id = ContextId::from_proto(envelope.payload.context_id);
@@ -246,9 +256,9 @@ impl ContextStore {
}
async fn handle_synchronize_contexts(
this: Entity<Self>,
this: Model<Self>,
envelope: TypedEnvelope<proto::SynchronizeContexts>,
mut cx: AsyncApp,
mut cx: AsyncAppContext,
) -> Result<proto::SynchronizeContextsResponse> {
this.update(&mut cx, |this, cx| {
if this.project.read(cx).is_via_collab() {
@@ -289,7 +299,7 @@ impl ContextStore {
})?
}
fn handle_project_changed(&mut self, _: Entity<Project>, cx: &mut Context<Self>) {
fn handle_project_changed(&mut self, _: Model<Project>, cx: &mut ModelContext<Self>) {
let is_shared = self.project.read(cx).is_shared();
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
if is_shared == was_shared {
@@ -310,7 +320,7 @@ impl ContextStore {
.client
.subscribe_to_entity(remote_id)
.log_err()
.map(|subscription| subscription.set_entity(&cx.entity(), &mut cx.to_async()));
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
self.advertise_contexts(cx);
} else {
self.client_subscription = None;
@@ -319,9 +329,9 @@ impl ContextStore {
fn handle_project_event(
&mut self,
_: Entity<Project>,
_: Model<Project>,
event: &project::Event,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
match event {
project::Event::Reshared => {
@@ -351,14 +361,15 @@ impl ContextStore {
}
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new(|cx| {
AssistantContext::local(
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> {
let context = cx.new_model(|cx| {
Context::local(
self.languages.clone(),
Some(self.project.clone()),
Some(self.telemetry.clone()),
self.prompt_builder.clone(),
self.slash_commands.clone(),
self.tools.clone(),
cx,
)
});
@@ -368,8 +379,8 @@ impl ContextStore {
pub fn create_remote_context(
&mut self,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote")));
@@ -382,19 +393,21 @@ impl ContextStore {
let telemetry = self.telemetry.clone();
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
let tools = self.tools.clone();
let request = self.client.request(proto::CreateContext { project_id });
cx.spawn(|this, mut cx| async move {
let response = request.await?;
let context_id = ContextId::from_proto(response.context_id);
let context_proto = response.context.context("invalid context")?;
let context = cx.new(|cx| {
AssistantContext::new(
let context = cx.new_model(|cx| {
Context::new(
context_id.clone(),
replica_id,
capability,
language_registry,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -426,8 +439,8 @@ impl ContextStore {
pub fn open_local_context(
&mut self,
path: PathBuf,
cx: &Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
cx: &ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
return Task::ready(Ok(existing_context));
}
@@ -445,16 +458,18 @@ impl ContextStore {
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
let tools = self.tools.clone();
cx.spawn(|this, mut cx| async move {
let saved_context = load.await?;
let context = cx.new(|cx| {
AssistantContext::deserialize(
let context = cx.new_model(|cx| {
Context::deserialize(
saved_context,
path.clone(),
languages,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -471,7 +486,7 @@ impl ContextStore {
})
}
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
fn loaded_context_for_path(&self, path: &Path, cx: &AppContext) -> Option<Model<Context>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).path() == Some(path) {
@@ -482,11 +497,7 @@ impl ContextStore {
})
}
pub fn loaded_context_for_id(
&self,
id: &ContextId,
cx: &App,
) -> Option<Entity<AssistantContext>> {
pub fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).id() == id {
@@ -500,8 +511,8 @@ impl ContextStore {
pub fn open_remote_context(
&mut self,
context_id: ContextId,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote")));
@@ -522,17 +533,19 @@ impl ContextStore {
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
let tools = self.tools.clone();
cx.spawn(|this, mut cx| async move {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
let context = cx.new(|cx| {
AssistantContext::new(
let context = cx.new_model(|cx| {
Context::new(
context_id.clone(),
replica_id,
capability,
language_registry,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -561,7 +574,7 @@ impl ContextStore {
})
}
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
fn register_context(&mut self, context: &Model<Context>, cx: &mut ModelContext<Self>) {
let handle = if self.project_is_shared {
ContextHandle::Strong(context.clone())
} else {
@@ -574,9 +587,9 @@ impl ContextStore {
fn handle_context_event(
&mut self,
context: Entity<AssistantContext>,
context: Model<Context>,
event: &ContextEvent,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
@@ -601,7 +614,7 @@ impl ContextStore {
}
}
fn advertise_contexts(&self, cx: &App) {
fn advertise_contexts(&self, cx: &AppContext) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
@@ -635,7 +648,7 @@ impl ContextStore {
.ok();
}
fn synchronize_contexts(&mut self, cx: &mut Context<Self>) {
fn synchronize_contexts(&mut self, cx: &mut ModelContext<Self>) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
@@ -690,7 +703,7 @@ impl ContextStore {
.detach_and_log_err(cx);
}
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> {
let metadata = self.contexts_metadata.clone();
let executor = cx.background_executor().clone();
cx.background_executor().spawn(async move {
@@ -724,7 +737,7 @@ impl ContextStore {
&self.host_contexts
}
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {
fs.create_dir(contexts_dir()).await?;
@@ -773,8 +786,8 @@ impl ContextStore {
})
}
pub fn restart_context_servers(&mut self, cx: &mut Context<Self>) {
cx.update_entity(
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) {
cx.update_model(
&self.context_server_manager,
|context_server_manager, cx| {
for server in context_server_manager.servers() {
@@ -786,7 +799,7 @@ impl ContextStore {
);
}
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
cx.subscribe(
&self.context_server_manager.clone(),
Self::handle_context_server_event,
@@ -796,11 +809,12 @@ impl ContextStore {
fn handle_context_server_event(
&mut self,
context_server_manager: Entity<ContextServerManager>,
context_server_manager: Model<ContextServerManager>,
event: &context_server::manager::Event,
cx: &mut Context<Self>,
cx: &mut ModelContext<Self>,
) {
let slash_command_working_set = self.slash_commands.clone();
let tool_working_set = self.tools.clone();
match event {
context_server::manager::Event::ServerStarted { server_id } => {
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
@@ -840,6 +854,29 @@ impl ContextStore {
.log_err();
}
}
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
if let Some(tools) = protocol.list_tools().await.log_err() {
let tool_ids = tools.tools.into_iter().map(|tool| {
log::info!("registering context server tool: {:?}", tool.name);
tool_working_set.insert(
Arc::new(ContextServerTool::new(
context_server_manager.clone(),
server.id(),
tool,
)),
)
}).collect::<Vec<_>>();
this.update(&mut cx, |this, _cx| {
this.context_server_tool_ids
.insert(server_id, tool_ids);
})
.log_err();
}
}
}
})
.detach();
@@ -851,6 +888,10 @@ impl ContextStore {
{
slash_command_working_set.remove(&slash_command_ids);
}
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
tool_working_set.remove(&tool_ids);
}
}
}
}

View File

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
use editor::ProposedChangesEditor;
use futures::{future, TryFutureExt as _};
use gpui::{App, AsyncApp, Entity, SharedString};
use gpui::{AppContext, AsyncAppContext, Model, SharedString};
use language::{AutoindentMode, Buffer, BufferSnapshot};
use project::{Project, ProjectPath};
use std::{cmp, ops::Range, path::Path, sync::Arc};
@@ -56,7 +56,7 @@ pub enum AssistantEditKind {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedPatch {
pub edit_groups: HashMap<Entity<Buffer>, Vec<ResolvedEditGroup>>,
pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>,
pub errors: Vec<AssistantPatchResolutionError>,
}
@@ -121,7 +121,7 @@ impl SearchMatrix {
}
impl ResolvedPatch {
pub fn apply(&self, editor: &ProposedChangesEditor, cx: &mut App) {
pub fn apply(&self, editor: &ProposedChangesEditor, cx: &mut AppContext) {
for (buffer, groups) in &self.edit_groups {
let branch = editor.branch_buffer_for_base(buffer).unwrap();
Self::apply_edit_groups(groups, &branch, cx);
@@ -129,7 +129,11 @@ impl ResolvedPatch {
editor.recalculate_all_buffer_diffs();
}
fn apply_edit_groups(groups: &Vec<ResolvedEditGroup>, buffer: &Entity<Buffer>, cx: &mut App) {
fn apply_edit_groups(
groups: &Vec<ResolvedEditGroup>,
buffer: &Model<Buffer>,
cx: &mut AppContext,
) {
let mut edits = Vec::new();
for group in groups {
for suggestion in &group.edits {
@@ -228,9 +232,9 @@ impl AssistantEdit {
pub async fn resolve(
&self,
project: Entity<Project>,
mut cx: AsyncApp,
) -> Result<(Entity<Buffer>, ResolvedEdit)> {
project: Model<Project>,
mut cx: AsyncAppContext,
) -> Result<(Model<Buffer>, ResolvedEdit)> {
let path = self.path.clone();
let kind = self.kind.clone();
let buffer = project
@@ -421,7 +425,11 @@ impl AssistantEditKind {
}
impl AssistantPatch {
pub async fn resolve(&self, project: Entity<Project>, cx: &mut AsyncApp) -> ResolvedPatch {
pub async fn resolve(
&self,
project: Model<Project>,
cx: &mut AsyncAppContext,
) -> ResolvedPatch {
let mut resolve_tasks = Vec::new();
for (ix, edit) in self.edits.iter().enumerate() {
if let Ok(edit) = edit.as_ref() {
@@ -547,7 +555,7 @@ impl Eq for AssistantPatch {}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{App, AppContext as _};
use gpui::{AppContext, Context};
use language::{
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
};
@@ -557,7 +565,7 @@ mod tests {
use util::test::{generate_marked_text, marked_text_ranges};
#[gpui::test]
fn test_resolve_location(cx: &mut App) {
fn test_resolve_location(cx: &mut AppContext) {
assert_location_resolution(
concat!(
" Lorem\n",
@@ -628,7 +636,7 @@ mod tests {
}
#[gpui::test]
fn test_resolve_edits(cx: &mut App) {
fn test_resolve_edits(cx: &mut AppContext) {
init_test(cx);
assert_edits(
@@ -894,7 +902,7 @@ mod tests {
);
}
fn init_test(cx: &mut App) {
fn init_test(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
@@ -904,9 +912,13 @@ mod tests {
}
#[track_caller]
fn assert_location_resolution(text_with_expected_range: &str, query: &str, cx: &mut App) {
fn assert_location_resolution(
text_with_expected_range: &str,
query: &str,
cx: &mut AppContext,
) {
let (text, _) = marked_text_ranges(text_with_expected_range, false);
let buffer = cx.new(|cx| Buffer::local(text.clone(), cx));
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
let snapshot = buffer.read(cx).snapshot();
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
let text_with_actual_range = generate_marked_text(&text, &[range], false);
@@ -918,10 +930,10 @@ mod tests {
old_text: String,
edits: Vec<AssistantEditKind>,
new_text: String,
cx: &mut App,
cx: &mut AppContext,
) {
let buffer =
cx.new(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx));
cx.new_model(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.read(cx).snapshot();
let resolved_edits = edits
.into_iter()

View File

@@ -4,8 +4,8 @@ pub use assistant_slash_command::SlashCommand;
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{App, Context, Entity, Task, WeakEntity, Window};
use language::{Anchor, Buffer, CompletionDocumentation, LanguageServerId, ToPoint};
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
use parking_lot::Mutex;
use project::CompletionIntent;
use rope::Point;
@@ -23,15 +23,15 @@ use workspace::Workspace;
pub struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>,
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakEntity<ContextEditor>>,
workspace: Option<WeakEntity<Workspace>>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
}
impl SlashCommandCompletionProvider {
pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakEntity<ContextEditor>>,
workspace: Option<WeakEntity<Workspace>>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
@@ -46,8 +46,7 @@ impl SlashCommandCompletionProvider {
command_name: &str,
command_range: Range<Anchor>,
name_range: Range<Anchor>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<Result<Vec<project::Completion>>> {
let slash_commands = self.slash_commands.clone();
let candidates = slash_commands
@@ -59,7 +58,7 @@ impl SlashCommandCompletionProvider {
let command_name = command_name.to_string();
let editor = self.editor.clone();
let workspace = self.workspace.clone();
window.spawn(cx, |mut cx| async move {
cx.spawn(|mut cx| async move {
let matches = match_strings(
&candidates,
&command_name,
@@ -70,7 +69,7 @@ impl SlashCommandCompletionProvider {
)
.await;
cx.update(|_, cx| {
cx.update(|cx| {
matches
.into_iter()
.filter_map(|mat| {
@@ -92,37 +91,32 @@ impl SlashCommandCompletionProvider {
let editor = editor.clone();
let workspace = workspace.clone();
Arc::new(
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
if !requires_argument
&& (!accepts_arguments || intent.is_complete())
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&[],
true,
workspace.clone(),
window,
cx,
);
})
.ok();
false
} else {
requires_argument || accepts_arguments
}
},
) as Arc<_>
move |intent: CompletionIntent, cx: &mut WindowContext| {
if !requires_argument
&& (!accepts_arguments || intent.is_complete())
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&[],
true,
workspace.clone(),
cx,
);
})
.ok();
false
} else {
requires_argument || accepts_arguments
}
},
) as Arc<_>
});
Some(project::Completion {
old_range: name_range.clone(),
documentation: Some(CompletionDocumentation::SingleLine(
command.description(),
)),
documentation: Some(Documentation::SingleLine(command.description())),
new_text,
label: command.label(cx),
server_id: LanguageServerId(0),
@@ -136,7 +130,6 @@ impl SlashCommandCompletionProvider {
})
}
#[allow(clippy::too_many_arguments)]
fn complete_command_argument(
&self,
command_name: &str,
@@ -144,8 +137,7 @@ impl SlashCommandCompletionProvider {
command_range: Range<Anchor>,
argument_range: Range<Anchor>,
last_argument_range: Range<Anchor>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<Result<Vec<project::Completion>>> {
let new_cancel_flag = Arc::new(AtomicBool::new(false));
let mut flag = self.cancel_flag.lock();
@@ -156,7 +148,6 @@ impl SlashCommandCompletionProvider {
arguments,
new_cancel_flag.clone(),
self.workspace.clone(),
window,
cx,
);
let command_name: Arc<str> = command_name.into();
@@ -184,9 +175,7 @@ impl SlashCommandCompletionProvider {
let command_range = command_range.clone();
let command_name = command_name.clone();
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
move |intent: CompletionIntent, cx: &mut WindowContext| {
if new_argument.after_completion.run()
|| intent.is_complete()
{
@@ -198,7 +187,6 @@ impl SlashCommandCompletionProvider {
&completed_arguments,
true,
workspace.clone(),
window,
cx,
);
})
@@ -242,11 +230,10 @@ impl SlashCommandCompletionProvider {
impl CompletionProvider for SlashCommandCompletionProvider {
fn completions(
&self,
buffer: &Entity<Buffer>,
buffer: &Model<Buffer>,
buffer_position: Anchor,
_: editor::CompletionContext,
window: &mut Window,
cx: &mut Context<Editor>,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Vec<project::Completion>>> {
let Some((name, arguments, command_range, last_argument_range)) =
buffer.update(cx, |buffer, _cx| {
@@ -301,31 +288,30 @@ impl CompletionProvider for SlashCommandCompletionProvider {
command_range,
argument_range,
last_argument_range,
window,
cx,
)
} else {
self.complete_command_name(&name, command_range, last_argument_range, window, cx)
self.complete_command_name(&name, command_range, last_argument_range, cx)
}
}
fn resolve_completions(
&self,
_: Entity<Buffer>,
_: Model<Buffer>,
_: Vec<usize>,
_: Rc<RefCell<Box<[project::Completion]>>>,
_: &mut Context<Editor>,
_: &mut ViewContext<Editor>,
) -> Task<Result<bool>> {
Task::ready(Ok(true))
}
fn is_completion_trigger(
&self,
buffer: &Entity<Buffer>,
buffer: &Model<Buffer>,
position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
cx: &mut Context<Editor>,
cx: &mut ViewContext<Editor>,
) -> bool {
let buffer = buffer.read(cx);
let position = position.to_point(buffer);

View File

@@ -1,22 +1,17 @@
use std::sync::Arc;
use assistant_slash_command::SlashCommandWorkingSet;
use gpui::{AnyElement, AnyView, DismissEvent, SharedString, Task, WeakEntity};
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
use crate::context_editor::ContextEditor;
#[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakEntity<ContextEditor>,
active_context_editor: WeakView<ContextEditor>,
trigger: T,
tooltip: TT,
}
#[derive(Clone)]
@@ -32,8 +27,8 @@ enum SlashCommandEntry {
Info(SlashCommandInfo),
Advert {
name: SharedString,
renderer: fn(&mut Window, &mut App) -> AnyElement,
on_confirm: fn(&mut Window, &mut App),
renderer: fn(&mut WindowContext) -> AnyElement,
on_confirm: fn(&mut WindowContext),
},
}
@@ -49,26 +44,20 @@ impl AsRef<str> for SlashCommandEntry {
pub(crate) struct SlashCommandDelegate {
all_commands: Vec<SlashCommandEntry>,
filtered_commands: Vec<SlashCommandEntry>,
active_context_editor: WeakEntity<ContextEditor>,
active_context_editor: WeakView<ContextEditor>,
selected_index: usize,
}
impl<T, TT> SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
impl<T: PopoverTrigger> SlashCommandSelector<T> {
pub(crate) fn new(
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakEntity<ContextEditor>,
active_context_editor: WeakView<ContextEditor>,
trigger: T,
tooltip: TT,
) -> Self {
SlashCommandSelector {
working_set,
active_context_editor,
trigger,
tooltip,
}
}
}
@@ -84,23 +73,18 @@ impl PickerDelegate for SlashCommandDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
cx.notify();
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Select a command...".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let all_commands = self.all_commands.clone();
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let filtered_commands = cx
.background_executor()
.spawn(async move {
@@ -120,9 +104,9 @@ impl PickerDelegate for SlashCommandDelegate {
})
.await;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate.filtered_commands = filtered_commands;
this.delegate.set_selected_index(0, window, cx);
this.delegate.set_selected_index(0, cx);
cx.notify();
})
.ok();
@@ -155,25 +139,25 @@ impl PickerDelegate for SlashCommandDelegate {
ret
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(command) = self.filtered_commands.get(self.selected_index) {
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, window, cx)
context_editor.insert_command(&info.name, cx)
})
.ok();
}
SlashCommandEntry::Advert { on_confirm, .. } => {
on_confirm(window, cx);
on_confirm(cx);
}
}
cx.emit(DismissEvent);
}
}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn editor_position(&self) -> PickerEditorPosition {
PickerEditorPosition::End
@@ -183,8 +167,7 @@ impl PickerDelegate for SlashCommandDelegate {
&self,
ix: usize,
selected: bool,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let command_info = self.filtered_commands.get(ix)?;
@@ -196,7 +179,7 @@ impl PickerDelegate for SlashCommandDelegate {
.toggle_state(selected)
.tooltip({
let description = info.description.clone();
move |_, cx| cx.new(|_| Tooltip::new(description.clone())).into()
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
})
.child(
v_flex()
@@ -246,18 +229,14 @@ impl PickerDelegate for SlashCommandDelegate {
.inset(true)
.spacing(ListItemSpacing::Dense)
.toggle_state(selected)
.child(renderer(window, cx)),
.child(renderer(cx)),
),
}
}
}
impl<T, TT> RenderOnce for SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let all_models = self
.working_set
.featured_command_names(cx)
@@ -280,7 +259,7 @@ where
})
.chain([SlashCommandEntry::Advert {
name: "create-your-command".into(),
renderer: |_, cx| {
renderer: |cx| {
v_flex()
.w_full()
.child(
@@ -314,7 +293,7 @@ where
)
.into_any_element()
},
on_confirm: |_, cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
}])
.collect::<Vec<_>>();
@@ -325,9 +304,8 @@ where
selected_index: 0,
};
let picker_view = cx.new(|cx| {
let picker =
Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()));
let picker_view = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
picker
});
@@ -336,8 +314,8 @@ where
.update(cx, |this, _| this.slash_menu_handle.clone())
.ok();
PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(picker_view.clone()))
.trigger_with_tooltip(self.trigger, self.tooltip)
.menu(move |_cx| Some(picker_view.clone()))
.trigger(self.trigger)
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {

View File

@@ -21,7 +21,6 @@ lmstudio = { workspace = true, features = ["schemars"] }
log.workspace = true
ollama = { workspace = true, features = ["schemars"] }
open_ai = { workspace = true, features = ["schemars"] }
deepseek = { workspace = true, features = ["schemars"] }
schemars.workspace = true
serde.workspace = true
settings.workspace = true

View File

@@ -2,9 +2,8 @@ use std::sync::Arc;
use ::open_ai::Model as OpenAiModel;
use anthropic::Model as AnthropicModel;
use deepseek::Model as DeepseekModel;
use feature_flags::FeatureFlagAppExt;
use gpui::{App, Pixels};
use gpui::{AppContext, Pixels};
use language_model::{CloudModel, LanguageModel};
use lmstudio::Model as LmStudioModel;
use ollama::Model as OllamaModel;
@@ -47,11 +46,6 @@ pub enum AssistantProviderContentV1 {
default_model: Option<LmStudioModel>,
api_url: Option<String>,
},
#[serde(rename = "deepseek")]
DeepSeek {
default_model: Option<DeepseekModel>,
api_url: Option<String>,
},
}
#[derive(Debug, Default)]
@@ -68,7 +62,7 @@ pub struct AssistantSettings {
}
impl AssistantSettings {
pub fn are_live_diffs_enabled(&self, cx: &App) -> bool {
pub fn are_live_diffs_enabled(&self, cx: &AppContext) -> bool {
cx.is_staff() || self.enable_experimental_live_diffs
}
}
@@ -155,12 +149,6 @@ impl AssistantSettingsContent {
model: model.id().to_string(),
})
}
AssistantProviderContentV1::DeepSeek { default_model, .. } => {
default_model.map(|model| LanguageModelSelection {
provider: "deepseek".to_string(),
model: model.id().to_string(),
})
}
}),
inline_alternatives: None,
enable_experimental_live_diffs: None,
@@ -265,18 +253,6 @@ impl AssistantSettingsContent {
available_models,
});
}
"deepseek" => {
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::DeepSeek { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::DeepSeek {
default_model: DeepseekModel::from_id(&model).ok(),
api_url,
});
}
_ => {}
},
VersionedAssistantSettingsContent::V2(settings) => {
@@ -365,7 +341,6 @@ fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema:
"openai".into(),
"zed.dev".into(),
"copilot_chat".into(),
"deepseek".into(),
]),
..Default::default()
}
@@ -405,7 +380,7 @@ pub struct AssistantSettingsContentV1 {
default_height: Option<f32>,
/// The provider of the assistant service.
///
/// This can be "openai", "anthropic", "ollama", "lmstudio", "deepseek", "zed.dev"
/// This can be "openai", "anthropic", "ollama", "lmstudio", "zed.dev"
/// each with their respective default models and configurations.
provider: Option<AssistantProviderContentV1>,
}
@@ -434,7 +409,7 @@ pub struct LegacyAssistantSettingsContent {
pub default_open_ai_model: Option<OpenAiModel>,
/// OpenAI API base URL to use when creating new chats.
///
/// Default: <https://api.openai.com/v1>
/// Default: https://api.openai.com/v1
pub openai_api_url: Option<String>,
}
@@ -447,7 +422,7 @@ impl Settings for AssistantSettings {
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::App,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
let mut settings = AssistantSettings::default();

View File

@@ -8,7 +8,7 @@ pub use crate::slash_command_working_set::*;
use anyhow::Result;
use futures::stream::{self, BoxStream};
use futures::StreamExt;
use gpui::{App, SharedString, Task, WeakEntity, Window};
use gpui::{AppContext, SharedString, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role;
use serde::{Deserialize, Serialize};
@@ -18,7 +18,7 @@ use std::{
};
use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut App) {
pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
extension_slash_command::init(cx);
}
@@ -71,7 +71,7 @@ pub trait SlashCommand: 'static + Send + Sync {
fn icon(&self) -> IconName {
IconName::Slash
}
fn label(&self, _cx: &App) -> CodeLabel {
fn label(&self, _cx: &AppContext) -> CodeLabel {
CodeLabel::plain(self.name(), None)
}
fn description(&self) -> String;
@@ -80,29 +80,26 @@ pub trait SlashCommand: 'static + Send + Sync {
self: Arc<Self>,
arguments: &[String],
cancel: Arc<AtomicBool>,
workspace: Option<WeakEntity<Workspace>>,
window: &mut Window,
cx: &mut App,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>>;
fn requires_argument(&self) -> bool;
fn accepts_arguments(&self) -> bool {
self.requires_argument()
}
#[allow(clippy::too_many_arguments)]
fn run(
self: Arc<Self>,
arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
// TODO: We're just using the `LspAdapterDelegate` here because that is
// what the extension API is already expecting.
//
// It may be that `LspAdapterDelegate` needs a more general name, or
// perhaps another kind of delegate is needed here.
delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult>;
}

View File

@@ -4,7 +4,7 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result;
use async_trait::async_trait;
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
use gpui::{App, Task, WeakEntity, Window};
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -14,7 +14,7 @@ use crate::{
SlashCommandRegistry, SlashCommandResult,
};
pub fn init(cx: &mut App) {
pub fn init(cx: &mut AppContext) {
let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
slash_command_registry: SlashCommandRegistry::global(cx),
@@ -97,9 +97,8 @@ impl SlashCommand for ExtensionSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let command = self.command.clone();
let arguments = arguments.to_owned();
@@ -128,10 +127,9 @@ impl SlashCommand for ExtensionSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakEntity<Workspace>,
_workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let command = self.command.clone();
let arguments = arguments.to_owned();

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use collections::{BTreeSet, HashMap};
use derive_more::{Deref, DerefMut};
use gpui::Global;
use gpui::{App, ReadGlobal};
use gpui::{AppContext, ReadGlobal};
use parking_lot::RwLock;
use crate::SlashCommand;
@@ -26,14 +26,14 @@ pub struct SlashCommandRegistry {
impl SlashCommandRegistry {
/// Returns the global [`SlashCommandRegistry`].
pub fn global(cx: &App) -> Arc<Self> {
pub fn global(cx: &AppContext) -> Arc<Self> {
GlobalSlashCommandRegistry::global(cx).0.clone()
}
/// Returns the global [`SlashCommandRegistry`].
///
/// Inserts a default [`SlashCommandRegistry`] if one does not yet exist.
pub fn default_global(cx: &mut App) -> Arc<Self> {
pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
cx.default_global::<GlobalSlashCommandRegistry>().0.clone()
}

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::App;
use gpui::AppContext;
use parking_lot::Mutex;
use crate::{SlashCommand, SlashCommandRegistry};
@@ -23,7 +23,7 @@ struct WorkingSetState {
}
impl SlashCommandWorkingSet {
pub fn command(&self, name: &str, cx: &App) -> Option<Arc<dyn SlashCommand>> {
pub fn command(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn SlashCommand>> {
self.state
.lock()
.context_server_commands_by_name
@@ -32,7 +32,7 @@ impl SlashCommandWorkingSet {
.or_else(|| SlashCommandRegistry::global(cx).command(name))
}
pub fn command_names(&self, cx: &App) -> Vec<Arc<str>> {
pub fn command_names(&self, cx: &AppContext) -> Vec<Arc<str>> {
let mut command_names = SlashCommandRegistry::global(cx).command_names();
command_names.extend(
self.state
@@ -45,7 +45,7 @@ impl SlashCommandWorkingSet {
command_names
}
pub fn featured_command_names(&self, cx: &App) -> Vec<Arc<str>> {
pub fn featured_command_names(&self, cx: &AppContext) -> Vec<Arc<str>> {
SlashCommandRegistry::global(cx).featured_command_names()
}

View File

@@ -45,7 +45,6 @@ toml.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
worktree.workspace = true
[dev-dependencies]
env_logger.workspace = true

View File

@@ -17,7 +17,7 @@ mod symbols_command;
mod tab_command;
mod terminal_command;
use gpui::App;
use gpui::AppContext;
use language::{CodeLabel, HighlightId};
use ui::ActiveTheme as _;
@@ -40,7 +40,11 @@ pub use crate::symbols_command::*;
pub use crate::tab_command::*;
pub use crate::terminal_command::*;
pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel {
pub fn create_label_for_command(
command_name: &str,
arguments: &[&str],
cx: &AppContext,
) -> CodeLabel {
let mut label = CodeLabel::default();
label.push_str(command_name, None);
label.push_str(" ", None);

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{App, AsyncApp, Task, WeakEntity, Window};
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
@@ -45,7 +45,7 @@ impl SlashCommand for AutoCommand {
self.description()
}
fn label(&self, cx: &App) -> CodeLabel {
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("auto", &["--prompt"], cx)
}
@@ -53,9 +53,8 @@ impl SlashCommand for AutoCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
cx: &mut App,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
// There's no autocomplete for a prompt, since it's arbitrary text.
// However, we can use this opportunity to kick off a drain of the backlog.
@@ -75,9 +74,9 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("No project indexer, cannot use /auto")));
};
let cx: &mut App = cx;
let cx: &mut AppContext = cx;
cx.spawn(|cx: gpui::AsyncApp| async move {
cx.spawn(|cx: gpui::AsyncAppContext| async move {
let task = project_index.read_with(&cx, |project_index, cx| {
project_index.flush_summary_backlogs(cx)
})?;
@@ -97,10 +96,9 @@ impl SlashCommand for AutoCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -117,7 +115,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("no project indexer")));
};
let task = window.spawn(cx, |cx| async move {
let task = cx.spawn(|cx: AsyncWindowContext| async move {
let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?;
@@ -189,7 +187,7 @@ struct CommandToRun {
async fn commands_for_summaries(
summaries: &[FileSummary],
original_prompt: &str,
cx: &AsyncApp,
cx: &AsyncAppContext,
) -> Result<Vec<CommandToRun>> {
if summaries.is_empty() {
log::warn!("Inferring no context because there were no summaries available.");

View File

@@ -1,10 +1,10 @@
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use fs::Fs;
use gpui::{App, Entity, Task, WeakEntity};
use gpui::{AppContext, Model, Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use std::{
@@ -76,7 +76,7 @@ impl CargoWorkspaceSlashCommand {
Ok(message)
}
fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
let worktree = project.read(cx).worktrees(cx).next()?;
let worktree = worktree.read(cx);
let entry = worktree.entry_for_path("Cargo.toml")?;
@@ -107,9 +107,8 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -123,10 +122,9 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let project = workspace.project().clone();

View File

@@ -8,7 +8,7 @@ use context_server::{
manager::{ContextServer, ContextServerManager},
types::Prompt,
};
use gpui::{App, Entity, Task, WeakEntity, Window};
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -19,14 +19,14 @@ use workspace::Workspace;
use crate::create_label_for_command;
pub struct ContextServerSlashCommand {
server_manager: Entity<ContextServerManager>,
server_manager: Model<ContextServerManager>,
server_id: Arc<str>,
prompt: Prompt,
}
impl ContextServerSlashCommand {
pub fn new(
server_manager: Entity<ContextServerManager>,
server_manager: Model<ContextServerManager>,
server: &Arc<ContextServer>,
prompt: Prompt,
) -> Self {
@@ -43,7 +43,7 @@ impl SlashCommand for ContextServerSlashCommand {
self.prompt.name.clone()
}
fn label(&self, cx: &App) -> language::CodeLabel {
fn label(&self, cx: &AppContext) -> language::CodeLabel {
let mut parts = vec![self.prompt.name.as_str()];
if let Some(args) = &self.prompt.arguments {
if let Some(arg) = args.first() {
@@ -77,9 +77,8 @@ impl SlashCommand for ContextServerSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
return Task::ready(Err(anyhow!("Failed to complete argument")));
@@ -129,10 +128,9 @@ impl SlashCommand for ContextServerSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakEntity<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone();

View File

@@ -3,7 +3,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakEntity};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_library::PromptStore;
use std::{
@@ -36,9 +36,8 @@ impl SlashCommand for DefaultSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -48,10 +47,9 @@ impl SlashCommand for DefaultSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakEntity<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let store = PromptStore::global(cx);
cx.background_executor().spawn(async move {

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
};
use collections::HashSet;
use futures::future;
use gpui::{App, Task, WeakEntity, Window};
use gpui::{Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use text::OffsetRangeExt;
@@ -40,9 +40,8 @@ impl SlashCommand for DeltaSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -52,10 +51,9 @@ impl SlashCommand for DeltaSlashCommand {
_arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new();
@@ -79,7 +77,6 @@ impl SlashCommand for DeltaSlashCommand {
context_buffer.clone(),
workspace.clone(),
delegate.clone(),
window,
cx,
));
}

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
OffsetRangeExt, ToOffset,
@@ -30,8 +30,8 @@ impl DiagnosticsSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Entity<Workspace>,
cx: &mut App,
workspace: &View<Workspace>,
cx: &mut AppContext,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -90,7 +90,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
"diagnostics".into()
}
fn label(&self, cx: &App) -> language::CodeLabel {
fn label(&self, cx: &AppContext) -> language::CodeLabel {
create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx)
}
@@ -118,9 +118,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakEntity<Workspace>>,
_: &mut Window,
cx: &mut App,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -173,10 +172,9 @@ impl SlashCommand for DiagnosticsSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -186,7 +184,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
window.spawn(cx, move |_| async move {
cx.spawn(move |_| async move {
task.await?
.map(|output| output.to_event_stream())
.ok_or_else(|| anyhow!("No diagnostics found"))
@@ -225,9 +223,9 @@ impl Options {
}
fn collect_diagnostics(
project: Entity<Project>,
project: Model<Project>,
options: Options,
cx: &mut App,
cx: &mut AppContext,
) -> Task<Result<Option<SlashCommandOutput>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1);
@@ -303,7 +301,7 @@ fn collect_diagnostics(
.await
.log_err()
{
let snapshot = cx.read_entity(&buffer, |buffer, _| buffer.snapshot())?;
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
}

View File

@@ -8,7 +8,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{App, BackgroundExecutor, Entity, Task, WeakEntity};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
@@ -24,7 +24,7 @@ pub struct DocsSlashCommand;
impl DocsSlashCommand {
pub const NAME: &'static str = "docs";
fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
let worktree = project.read(cx).worktrees(cx).next()?;
let worktree = worktree.read(cx);
let entry = worktree.entry_for_path("Cargo.toml")?;
@@ -43,8 +43,8 @@ impl DocsSlashCommand {
/// access the workspace so we can read the project.
fn ensure_rust_doc_providers_are_registered(
&self,
workspace: Option<WeakEntity<Workspace>>,
cx: &mut App,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
if indexed_docs_registry
@@ -164,9 +164,8 @@ impl SlashCommand for DocsSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakEntity<Workspace>>,
_: &mut Window,
cx: &mut App,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
self.ensure_rust_doc_providers_are_registered(workspace, cx);
@@ -273,10 +272,9 @@ impl SlashCommand for DocsSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakEntity<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
if arguments.is_empty() {
return Task::ready(Err(anyhow!("missing an argument")));

View File

@@ -9,7 +9,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use futures::AsyncReadExt;
use gpui::{Task, WeakEntity};
use gpui::{Task, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -124,9 +124,8 @@ impl SlashCommand for FetchSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -136,10 +135,9 @@ impl SlashCommand for FetchSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakEntity<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut App,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(argument) = arguments.first() else {
return Task::ready(Err(anyhow!("missing URL")));

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