Compare commits
1 Commits
missing-gi
...
vim-norm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc996a87c |
34
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
Normal 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
|
||||||
60
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -1,34 +1,54 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: |
|
description: |
|
||||||
Something is broken in Zed (exclude crashing).
|
Use this template for **non-crash-related** bug reports.
|
||||||
type: "Bug"
|
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||||
|
labels: ["admin read", "triage", "bug"]
|
||||||
body:
|
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
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Summary
|
label: Describe the bug / provide steps to reproduce it
|
||||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
description: A clear and concise description of what the bug is.
|
||||||
value: |
|
|
||||||
<!-- Please insert a one line summary of the issue below -->
|
|
||||||
|
|
||||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
|
||||||
Steps to trigger the problem:
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
Actual Behavior:
|
|
||||||
|
|
||||||
Expected Behavior:
|
|
||||||
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: environment
|
id: environment
|
||||||
attributes:
|
attributes:
|
||||||
label: Zed Version and System Specs
|
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: |
|
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:
|
validations:
|
||||||
required: true
|
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
|
||||||
|
|||||||
35
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -1,33 +1,26 @@
|
|||||||
name: Crash Report
|
name: Crash Report
|
||||||
description: Zed is Crashing or Hanging
|
description: |
|
||||||
type: "Crash"
|
Use this template for crash reports.
|
||||||
|
labels: ["admin read", "triage", "bug", "panic / crash"]
|
||||||
body:
|
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
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Summary
|
label: Describe the bug / provide steps to reproduce it
|
||||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
description: A clear and concise description of what the bug is.
|
||||||
value: |
|
|
||||||
<!-- Please insert a one line summary of the issue below -->
|
|
||||||
|
|
||||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
|
||||||
Steps to trigger the problem:
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
Actual Behavior:
|
|
||||||
|
|
||||||
Expected Behavior:
|
|
||||||
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: environment
|
id: environment
|
||||||
attributes:
|
attributes:
|
||||||
label: Zed Version and System Specs
|
label: Environment
|
||||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
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.
|
||||||
placeholder: |
|
|
||||||
Output of "zed: Copy System Specs Into Clipboard"
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
24
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,12 +1,18 @@
|
|||||||
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
|
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Feature Request
|
- name: Language Request
|
||||||
url: https://github.com/zed-industries/zed/discussions/new/choose
|
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
|
||||||
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
|
about: Request a language in the extensions repository
|
||||||
- name: Zed Discussion Forum
|
- name: Theme Request
|
||||||
url: https://github.com/zed-industries/zed/discussions
|
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: A community discussion forum
|
about: Request a theme in the extensions repository
|
||||||
- name: "Zed Discord: #Support Channel"
|
- name: Top-Ranking Issues
|
||||||
url: https://zed.dev/community-links
|
url: https://github.com/zed-industries/zed/issues/5393
|
||||||
about: Real-time discussion and user support
|
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
|
||||||
|
|||||||
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
|||||||
cargo install cargo-nextest --locked
|
cargo install cargo-nextest --locked
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
26
.github/actions/run_tests_windows/action.yml
vendored
@@ -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
|
|
||||||
1
.github/workflows/bump_patch_version.yml
vendored
@@ -14,7 +14,6 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump_patch_version:
|
bump_patch_version:
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
33
.github/workflows/ci.yml
vendored
@@ -135,7 +135,6 @@ jobs:
|
|||||||
cargo check -p gpui --features "macos-blade"
|
cargo check -p gpui --features "macos-blade"
|
||||||
cargo check -p workspace
|
cargo check -p workspace
|
||||||
cargo build -p remote_server
|
cargo build -p remote_server
|
||||||
cargo check -p gpui --examples
|
|
||||||
script/check-rust-livekit-macos
|
script/check-rust-livekit-macos
|
||||||
|
|
||||||
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
|
# Since the macOS runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||||
@@ -182,7 +181,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo build -p zed
|
cargo build -p zed
|
||||||
cargo check -p workspace
|
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.
|
# 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
|
# 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()
|
if: always()
|
||||||
run: rm -rf ./../.cargo
|
run: rm -rf ./../.cargo
|
||||||
|
|
||||||
|
# todo(windows): Actually run the tests
|
||||||
windows_tests:
|
windows_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Windows) Run Clippy and tests
|
name: (Windows) Run Clippy and tests
|
||||||
@@ -237,55 +236,33 @@ jobs:
|
|||||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||||
- name: Enable longer pathnames for git
|
- name: Enable longer pathnames for git
|
||||||
run: git config --system core.longpaths true
|
run: git config --system core.longpaths true
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
clean: false
|
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
|
- name: Cache dependencies
|
||||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
workspaces: ${{ env.ZED_WORKSPACE }}
|
|
||||||
cache-provider: "github"
|
cache-provider: "github"
|
||||||
|
|
||||||
- name: Configure CI
|
- name: Configure CI
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ${{ env.CARGO_HOME }} -ErrorAction Ignore
|
mkdir -p ./../.cargo
|
||||||
cp ./.cargo/ci-config.toml ${{ env.CARGO_HOME }}/config.toml
|
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
|
||||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||||
run: cargo xtask clippy
|
run: cargo xtask clippy
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
uses: ./.github/actions/run_tests_windows
|
|
||||||
with:
|
|
||||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
|
||||||
|
|
||||||
- name: Build Zed
|
- name: Build Zed
|
||||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
|
||||||
run: cargo build
|
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.
|
# Since the Windows runners are stateful, so we need to remove the config file to prevent potential bug.
|
||||||
- name: Clean CI config file
|
- name: Clean CI config file
|
||||||
if: always()
|
if: always()
|
||||||
run: Remove-Item -Path "${{ env.CARGO_HOME }}/config.toml" -Force
|
run: Remove-Item -Path "./../.cargo" -Recurse -Force
|
||||||
|
|
||||||
bundle-mac:
|
bundle-mac:
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
@@ -306,7 +283,7 @@ jobs:
|
|||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'zed-industries'
|
if: github.repository_owner == 'zed-industries'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
@@ -19,7 +19,11 @@ jobs:
|
|||||||
|
|
||||||
Thanks for your help!
|
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."
|
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
|
days-before-close: 7
|
||||||
any-of-issue-labels: "bug,panic / crash"
|
any-of-issue-labels: "bug,panic / crash"
|
||||||
operations-per-run: 1000
|
operations-per-run: 1000
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
delete_comment:
|
delete_comment:
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check for specific strings in comment
|
- name: Check for specific strings in comment
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
discord_release:
|
discord_release:
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Get release URL
|
- name: Get release URL
|
||||||
|
|||||||
25
.github/workflows/community_update_all_top_ranking_issues.yml
vendored
Normal 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
|
||||||
25
.github/workflows/community_update_weekly_top_ranking_issues.yml
vendored
Normal 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
|
||||||
3
.github/workflows/danger.yml
vendored
@@ -11,7 +11,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
danger:
|
danger:
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -22,7 +21,7 @@ jobs:
|
|||||||
version: 9
|
version: 9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
15
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,36 +37,29 @@ jobs:
|
|||||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||||
|
|
||||||
- name: Deploy Docs
|
- name: Deploy Docs
|
||||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: pages deploy target/deploy --project-name=docs
|
command: pages deploy target/deploy --project-name=docs
|
||||||
|
|
||||||
- name: Deploy Install
|
- name: Deploy Install
|
||||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||||
|
|
||||||
- name: Deploy Docs Workers
|
- name: Deploy Docs Workers
|
||||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||||
|
|
||||||
- name: Deploy Install Workers
|
- name: Deploy Install Workers
|
||||||
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
|
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||||
|
|
||||||
- name: Preserve Wrangler logs
|
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: wrangler_logs
|
|
||||||
path: /home/runner/.config/.wrangler/logs/
|
|
||||||
|
|||||||
1
.github/workflows/deploy_collab.yml
vendored
@@ -12,7 +12,6 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
style:
|
style:
|
||||||
name: Check formatting and Clippy lints
|
name: Check formatting and Clippy lints
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
|
|||||||
1
.github/workflows/publish_extension_cli.yml
vendored
@@ -12,7 +12,6 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
name: Publish zed-extension CLI
|
name: Publish zed-extension CLI
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on:
|
runs-on:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
3
.github/workflows/randomized_tests.yml
vendored
@@ -18,12 +18,11 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Run randomized tests
|
name: Run randomized tests
|
||||||
if: github.repository_owner == 'zed-industries'
|
|
||||||
runs-on:
|
runs-on:
|
||||||
- buildjet-16vcpu-ubuntu-2204
|
- buildjet-16vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/release_nightly.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
|||||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -30,7 +30,6 @@ DerivedData/
|
|||||||
.vscode
|
.vscode
|
||||||
.wrangler
|
.wrangler
|
||||||
.flatpak-builder
|
.flatpak-builder
|
||||||
.envrc
|
|
||||||
|
|
||||||
# Don't commit any secrets to the repo.
|
# Don't commit any secrets to the repo.
|
||||||
.env.secret.toml
|
.env.secret.toml
|
||||||
|
|||||||
@@ -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.
|
- [`rpc`](/crates/rpc) defines messages to be exchanged with collaboration server.
|
||||||
- [`theme`](/crates/theme) defines the theme system and provides a default theme.
|
- [`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.
|
- [`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).
|
|
||||||
|
|||||||
960
Cargo.lock
generated
62
Cargo.toml
@@ -2,6 +2,7 @@
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/activity_indicator",
|
"crates/activity_indicator",
|
||||||
|
"crates/zed_predict_tos",
|
||||||
"crates/anthropic",
|
"crates/anthropic",
|
||||||
"crates/assets",
|
"crates/assets",
|
||||||
"crates/assistant",
|
"crates/assistant",
|
||||||
@@ -30,9 +31,7 @@ members = [
|
|||||||
"crates/context_server_settings",
|
"crates/context_server_settings",
|
||||||
"crates/copilot",
|
"crates/copilot",
|
||||||
"crates/db",
|
"crates/db",
|
||||||
"crates/deepseek",
|
|
||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
"crates/diff",
|
|
||||||
"crates/docs_preprocessor",
|
"crates/docs_preprocessor",
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
"crates/evals",
|
"crates/evals",
|
||||||
@@ -45,17 +44,16 @@ members = [
|
|||||||
"crates/feedback",
|
"crates/feedback",
|
||||||
"crates/file_finder",
|
"crates/file_finder",
|
||||||
"crates/file_icons",
|
"crates/file_icons",
|
||||||
|
"crates/fireworks",
|
||||||
"crates/fs",
|
"crates/fs",
|
||||||
"crates/fsevent",
|
"crates/fsevent",
|
||||||
"crates/fuzzy",
|
"crates/fuzzy",
|
||||||
"crates/git",
|
"crates/git",
|
||||||
"crates/git_hosting_providers",
|
"crates/git_hosting_providers",
|
||||||
"crates/git_ui",
|
|
||||||
"crates/go_to_line",
|
"crates/go_to_line",
|
||||||
"crates/google_ai",
|
"crates/google_ai",
|
||||||
"crates/gpui",
|
"crates/gpui",
|
||||||
"crates/gpui_macros",
|
"crates/gpui_macros",
|
||||||
"crates/gpui_tokio",
|
|
||||||
"crates/html_to_markdown",
|
"crates/html_to_markdown",
|
||||||
"crates/http_client",
|
"crates/http_client",
|
||||||
"crates/image_viewer",
|
"crates/image_viewer",
|
||||||
@@ -88,7 +86,6 @@ members = [
|
|||||||
"crates/open_ai",
|
"crates/open_ai",
|
||||||
"crates/outline",
|
"crates/outline",
|
||||||
"crates/outline_panel",
|
"crates/outline_panel",
|
||||||
"crates/panel",
|
|
||||||
"crates/paths",
|
"crates/paths",
|
||||||
"crates/picker",
|
"crates/picker",
|
||||||
"crates/prettier",
|
"crates/prettier",
|
||||||
@@ -108,7 +105,6 @@ members = [
|
|||||||
"crates/rich_text",
|
"crates/rich_text",
|
||||||
"crates/rope",
|
"crates/rope",
|
||||||
"crates/rpc",
|
"crates/rpc",
|
||||||
"crates/schema_generator",
|
|
||||||
"crates/search",
|
"crates/search",
|
||||||
"crates/semantic_index",
|
"crates/semantic_index",
|
||||||
"crates/semantic_version",
|
"crates/semantic_version",
|
||||||
@@ -144,8 +140,8 @@ members = [
|
|||||||
"crates/ui",
|
"crates/ui",
|
||||||
"crates/ui_input",
|
"crates/ui_input",
|
||||||
"crates/ui_macros",
|
"crates/ui_macros",
|
||||||
|
"crates/reqwest_client",
|
||||||
"crates/util",
|
"crates/util",
|
||||||
"crates/util_macros",
|
|
||||||
"crates/vcs_menu",
|
"crates/vcs_menu",
|
||||||
"crates/vim",
|
"crates/vim",
|
||||||
"crates/vim_mode_setting",
|
"crates/vim_mode_setting",
|
||||||
@@ -155,6 +151,7 @@ members = [
|
|||||||
"crates/zed",
|
"crates/zed",
|
||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
"crates/zeta",
|
"crates/zeta",
|
||||||
|
"crates/git_ui",
|
||||||
|
|
||||||
#
|
#
|
||||||
# Extensions
|
# Extensions
|
||||||
@@ -171,6 +168,7 @@ members = [
|
|||||||
"extensions/lua",
|
"extensions/lua",
|
||||||
"extensions/php",
|
"extensions/php",
|
||||||
"extensions/perplexity",
|
"extensions/perplexity",
|
||||||
|
"extensions/prisma",
|
||||||
"extensions/proto",
|
"extensions/proto",
|
||||||
"extensions/purescript",
|
"extensions/purescript",
|
||||||
"extensions/ruff",
|
"extensions/ruff",
|
||||||
@@ -202,6 +200,7 @@ edition = "2021"
|
|||||||
|
|
||||||
activity_indicator = { path = "crates/activity_indicator" }
|
activity_indicator = { path = "crates/activity_indicator" }
|
||||||
ai = { path = "crates/ai" }
|
ai = { path = "crates/ai" }
|
||||||
|
zed_predict_tos = { path = "crates/zed_predict_tos" }
|
||||||
anthropic = { path = "crates/anthropic" }
|
anthropic = { path = "crates/anthropic" }
|
||||||
assets = { path = "crates/assets" }
|
assets = { path = "crates/assets" }
|
||||||
assistant = { path = "crates/assistant" }
|
assistant = { path = "crates/assistant" }
|
||||||
@@ -230,9 +229,7 @@ context_server = { path = "crates/context_server" }
|
|||||||
context_server_settings = { path = "crates/context_server_settings" }
|
context_server_settings = { path = "crates/context_server_settings" }
|
||||||
copilot = { path = "crates/copilot" }
|
copilot = { path = "crates/copilot" }
|
||||||
db = { path = "crates/db" }
|
db = { path = "crates/db" }
|
||||||
deepseek = { path = "crates/deepseek" }
|
|
||||||
diagnostics = { path = "crates/diagnostics" }
|
diagnostics = { path = "crates/diagnostics" }
|
||||||
diff = { path = "crates/diff" }
|
|
||||||
editor = { path = "crates/editor" }
|
editor = { path = "crates/editor" }
|
||||||
extension = { path = "crates/extension" }
|
extension = { path = "crates/extension" }
|
||||||
extension_host = { path = "crates/extension_host" }
|
extension_host = { path = "crates/extension_host" }
|
||||||
@@ -241,19 +238,19 @@ feature_flags = { path = "crates/feature_flags" }
|
|||||||
feedback = { path = "crates/feedback" }
|
feedback = { path = "crates/feedback" }
|
||||||
file_finder = { path = "crates/file_finder" }
|
file_finder = { path = "crates/file_finder" }
|
||||||
file_icons = { path = "crates/file_icons" }
|
file_icons = { path = "crates/file_icons" }
|
||||||
|
fireworks = { path = "crates/fireworks" }
|
||||||
fs = { path = "crates/fs" }
|
fs = { path = "crates/fs" }
|
||||||
fsevent = { path = "crates/fsevent" }
|
fsevent = { path = "crates/fsevent" }
|
||||||
fuzzy = { path = "crates/fuzzy" }
|
fuzzy = { path = "crates/fuzzy" }
|
||||||
git = { path = "crates/git" }
|
git = { path = "crates/git" }
|
||||||
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
|
||||||
git_ui = { path = "crates/git_ui" }
|
git_ui = { path = "crates/git_ui" }
|
||||||
|
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||||
go_to_line = { path = "crates/go_to_line" }
|
go_to_line = { path = "crates/go_to_line" }
|
||||||
google_ai = { path = "crates/google_ai" }
|
google_ai = { path = "crates/google_ai" }
|
||||||
gpui = { path = "crates/gpui", default-features = false, features = [
|
gpui = { path = "crates/gpui", default-features = false, features = [
|
||||||
"http_client",
|
"http_client",
|
||||||
] }
|
] }
|
||||||
gpui_macros = { path = "crates/gpui_macros" }
|
gpui_macros = { path = "crates/gpui_macros" }
|
||||||
gpui_tokio = { path = "crates/gpui_tokio" }
|
|
||||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||||
http_client = { path = "crates/http_client" }
|
http_client = { path = "crates/http_client" }
|
||||||
image_viewer = { path = "crates/image_viewer" }
|
image_viewer = { path = "crates/image_viewer" }
|
||||||
@@ -287,7 +284,6 @@ open_ai = { path = "crates/open_ai" }
|
|||||||
outline = { path = "crates/outline" }
|
outline = { path = "crates/outline" }
|
||||||
outline_panel = { path = "crates/outline_panel" }
|
outline_panel = { path = "crates/outline_panel" }
|
||||||
paths = { path = "crates/paths" }
|
paths = { path = "crates/paths" }
|
||||||
panel = { path = "crates/panel" }
|
|
||||||
picker = { path = "crates/picker" }
|
picker = { path = "crates/picker" }
|
||||||
plugin = { path = "crates/plugin" }
|
plugin = { path = "crates/plugin" }
|
||||||
plugin_macros = { path = "crates/plugin_macros" }
|
plugin_macros = { path = "crates/plugin_macros" }
|
||||||
@@ -343,7 +339,6 @@ ui = { path = "crates/ui" }
|
|||||||
ui_input = { path = "crates/ui_input" }
|
ui_input = { path = "crates/ui_input" }
|
||||||
ui_macros = { path = "crates/ui_macros" }
|
ui_macros = { path = "crates/ui_macros" }
|
||||||
util = { path = "crates/util" }
|
util = { path = "crates/util" }
|
||||||
util_macros = { path = "crates/util_macros" }
|
|
||||||
vcs_menu = { path = "crates/vcs_menu" }
|
vcs_menu = { path = "crates/vcs_menu" }
|
||||||
vim = { path = "crates/vim" }
|
vim = { path = "crates/vim" }
|
||||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||||
@@ -364,7 +359,7 @@ alacritty_terminal = { git = "https://github.com/alacritty/alacritty.git", rev =
|
|||||||
any_vec = "0.14"
|
any_vec = "0.14"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||||
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-compat = "0.2.1"
|
||||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||||
async-dispatcher = "0.1"
|
async-dispatcher = "0.1"
|
||||||
@@ -377,11 +372,10 @@ async-tungstenite = "0.28"
|
|||||||
async-watch = "0.3.1"
|
async-watch = "0.3.1"
|
||||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.8.0"
|
||||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||||
blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||||
naga = { version = "23.1.0", features = ["wgsl-in"] }
|
|
||||||
blake3 = "1.5.3"
|
blake3 = "1.5.3"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
cargo_metadata = "0.19"
|
cargo_metadata = "0.19"
|
||||||
@@ -390,7 +384,7 @@ chrono = { version = "0.4", features = ["serde"] }
|
|||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
cocoa = "0.26"
|
cocoa = "0.26"
|
||||||
cocoa-foundation = "0.2.0"
|
cocoa-foundation = "0.2.0"
|
||||||
convert_case = "0.7.0"
|
convert_case = "0.6.0"
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.9.3"
|
||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
ctor = "0.2.6"
|
ctor = "0.2.6"
|
||||||
@@ -406,8 +400,7 @@ fork = "0.2.0"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-batch = "0.6.1"
|
futures-batch = "0.6.1"
|
||||||
futures-lite = "1.13"
|
futures-lite = "1.13"
|
||||||
# TODO: get back to regular versions when https://github.com/rust-lang/git2-rs/pull/1120 is released
|
git2 = { version = "0.19", default-features = false }
|
||||||
git2 = { git = "https://github.com/rust-lang/git2-rs", rev = "a3b90cb3756c1bb63e2317bf9cfa57838178de5c", default-features = false }
|
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
handlebars = "4.3"
|
handlebars = "4.3"
|
||||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||||
@@ -426,17 +419,14 @@ jupyter-websocket-client = { version = "0.9.0" }
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
|
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
|
||||||
"dispatcher",
|
log = { version = "0.4.25", features = ["kv_unstable_serde", "serde"] }
|
||||||
"services-dispatcher",
|
|
||||||
"rustls-tls-native-roots",
|
|
||||||
], default-features = false }
|
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
|
||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
nbformat = { version = "0.10.0" }
|
nbformat = { version = "0.10.0" }
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
|
once_cell = "1.20"
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
@@ -450,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" }
|
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
|
||||||
postage = { version = "0.5", features = ["futures-traits"] }
|
postage = { version = "0.5", features = ["futures-traits"] }
|
||||||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||||
proc-macro2 = "1.0.93"
|
|
||||||
profiling = "1"
|
profiling = "1"
|
||||||
prost = "0.9"
|
prost = "0.9"
|
||||||
prost-build = "0.9"
|
prost-build = "0.9"
|
||||||
prost-types = "0.9"
|
prost-types = "0.9"
|
||||||
pulldown-cmark = { version = "0.12.0", default-features = false }
|
pulldown-cmark = { version = "0.12.0", default-features = false }
|
||||||
quote = "1.0.9"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rayon = "1.8"
|
rayon = "1.8"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
@@ -476,7 +464,7 @@ runtimelib = { version = "0.25.0", default-features = false, features = [
|
|||||||
rustc-demangle = "0.1.23"
|
rustc-demangle = "0.1.23"
|
||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||||
rustc-hash = "2.1.0"
|
rustc-hash = "2.1.0"
|
||||||
rustls = { version = "0.23.22" }
|
rustls = "0.21.12"
|
||||||
rustls-native-certs = "0.8.0"
|
rustls-native-certs = "0.8.0"
|
||||||
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
@@ -500,7 +488,6 @@ sqlformat = "0.2"
|
|||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum = { version = "0.26.0", features = ["derive"] }
|
strum = { version = "0.26.0", features = ["derive"] }
|
||||||
subtle = "2.5.0"
|
subtle = "2.5.0"
|
||||||
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
|
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
sysinfo = "0.31.0"
|
sysinfo = "0.31.0"
|
||||||
take-until = "0.2.0"
|
take-until = "0.2.0"
|
||||||
@@ -522,10 +509,9 @@ tree-sitter = { version = "0.23", features = ["wasm"] }
|
|||||||
tree-sitter-bash = "0.23"
|
tree-sitter-bash = "0.23"
|
||||||
tree-sitter-c = "0.23"
|
tree-sitter-c = "0.23"
|
||||||
tree-sitter-cpp = "0.23"
|
tree-sitter-cpp = "0.23"
|
||||||
tree-sitter-css = "0.23"
|
tree-sitter-css = "0.23.2"
|
||||||
tree-sitter-elixir = "0.3"
|
tree-sitter-elixir = "0.3"
|
||||||
tree-sitter-embedded-template = "0.23.0"
|
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 = "0.23"
|
||||||
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
|
tree-sitter-go-mod = { git = "https://github.com/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" }
|
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
|
||||||
@@ -536,13 +522,13 @@ tree-sitter-jsdoc = "0.23"
|
|||||||
tree-sitter-json = "0.24"
|
tree-sitter-json = "0.24"
|
||||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||||
tree-sitter-python = "0.23"
|
tree-sitter-python = "0.23"
|
||||||
tree-sitter-regex = "0.24"
|
tree-sitter-regex = "0.23"
|
||||||
tree-sitter-ruby = "0.23"
|
tree-sitter-ruby = "0.23"
|
||||||
tree-sitter-rust = "0.23"
|
tree-sitter-rust = "0.23"
|
||||||
tree-sitter-typescript = "0.23"
|
tree-sitter-typescript = "0.23"
|
||||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
unindent = "0.2.0"
|
unindent = "0.1.7"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
unicode-script = "0.5.7"
|
unicode-script = "0.5.7"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
@@ -559,9 +545,8 @@ wasmtime = { version = "24", default-features = false, features = [
|
|||||||
wasmtime-wasi = "24"
|
wasmtime-wasi = "24"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
wit-component = "0.201"
|
wit-component = "0.201"
|
||||||
zed_llm_client = "0.2"
|
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
metal = "0.31"
|
metal = "0.30"
|
||||||
|
|
||||||
[workspace.dependencies.async-stripe]
|
[workspace.dependencies.async-stripe]
|
||||||
git = "https://github.com/zed-industries/async-stripe"
|
git = "https://github.com/zed-industries/async-stripe"
|
||||||
@@ -620,7 +605,6 @@ features = [
|
|||||||
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
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]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2022 - 2025 Zed Industries, Inc.
|
Copyright 2022 - 2024 Zed Industries, Inc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -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 |
@@ -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 |
@@ -1,4 +1,8 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10.0001 1.33334H4.00008C3.64646 1.33334 3.30732 1.47382 3.05727 1.72387C2.80722 1.97392 2.66675 2.31305 2.66675 2.66668V13.3333C2.66675 13.687 2.80722 14.0261 3.05727 14.2762C3.30732 14.5262 3.64646 14.6667 4.00008 14.6667H12.0001C12.3537 14.6667 12.6928 14.5262 12.9429 14.2762C13.1929 14.0261 13.3334 13.687 13.3334 13.3333V4.66668L10.0001 1.33334Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path
|
||||||
<path d="M9.33325 1.33334V4.00001C9.33325 4.35363 9.47373 4.69277 9.72378 4.94282C9.97383 5.19287 10.313 5.33334 10.6666 5.33334H13.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M3.5 2C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V6H8.5C8.22386 6 8 5.77614 8 5.5V2H3.5ZM9 2.70711L11.2929 5H9V2.70711ZM2 2.5C2 1.67157 2.67157 1 3.5 1H8.5C8.63261 1 8.75979 1.05268 8.85355 1.14645L12.8536 5.14645C12.9473 5.24021 13 5.36739 13 5.5V12.5C13 13.3284 12.3284 14 11.5 14H3.5C2.67157 14 2 13.3284 2 12.5V2.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 769 B After Width: | Height: | Size: 567 B |
@@ -42,12 +42,6 @@
|
|||||||
"elm": "elm",
|
"elm": "elm",
|
||||||
"erl": "erlang",
|
"erl": "erlang",
|
||||||
"escript": "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": "eslint",
|
||||||
"eslintrc.js": "eslint",
|
"eslintrc.js": "eslint",
|
||||||
"eslintrc.json": "eslint",
|
"eslintrc.json": "eslint",
|
||||||
@@ -86,8 +80,8 @@
|
|||||||
"hpp": "cpp",
|
"hpp": "cpp",
|
||||||
"hrl": "erlang",
|
"hrl": "erlang",
|
||||||
"hs": "haskell",
|
"hs": "haskell",
|
||||||
"htm": "html",
|
"htm": "template",
|
||||||
"html": "html",
|
"html": "template",
|
||||||
"hxx": "cpp",
|
"hxx": "cpp",
|
||||||
"ib": "storage",
|
"ib": "storage",
|
||||||
"ico": "image",
|
"ico": "image",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M13.3333 13.3333C13.6869 13.3333 14.026 13.1929 14.2761 12.9428C14.5261 12.6928 14.6666 12.3536 14.6666 12V5.33333C14.6666 4.97971 14.5261 4.64057 14.2761 4.39052C14.026 4.14048 13.6869 4 13.3333 4H8.06659C7.84359 4.00219 7.62362 3.94841 7.42679 3.84359C7.22996 3.73877 7.06256 3.58625 6.93992 3.4L6.39992 2.6C6.27851 2.41565 6.11324 2.26432 5.91892 2.1596C5.7246 2.05488 5.50732 2.00004 5.28659 2H2.66659C2.31296 2 1.97382 2.14048 1.72378 2.39052C1.47373 2.64057 1.33325 2.97971 1.33325 3.33333V12C1.33325 12.3536 1.47373 12.6928 1.72378 12.9428C1.97382 13.1929 2.31296 13.3333 2.66659 13.3333H13.3333Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8.26046 3.97337C8.3527 4.17617 8.4795 4.47151 8.57375 4.69341C8.65258 4.87898 8.83437 4.99999 9.03599 4.99999H12.5C12.7761 4.99999 13 5.22385 13 5.49999V12.125C13 12.4011 12.7761 12.625 12.5 12.625H3.5C3.22386 12.625 3 12.4011 3 12.125V3.86932C3 3.59318 3.22386 3.36932 3.5 3.36932H7.34219C7.74141 3.36932 8.09483 3.60924 8.26046 3.97337Z" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 800 B After Width: | Height: | Size: 512 B |
@@ -1,12 +1 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
|
||||||
<g clip-path="url(#clip0_2226_61)">
|
|
||||||
<path d="M7.99992 14.6667C11.6818 14.6667 14.6666 11.6819 14.6666 8C14.6666 4.3181 11.6818 1.33333 7.99992 1.33333C4.31802 1.33333 1.33325 4.3181 1.33325 8C1.33325 11.6819 4.31802 14.6667 7.99992 14.6667Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M7.99992 1.33333C6.28807 3.13076 5.33325 5.51782 5.33325 8C5.33325 10.4822 6.28807 12.8692 7.99992 14.6667C9.71176 12.8692 10.6666 10.4822 10.6666 8C10.6666 5.51782 9.71176 3.13076 7.99992 1.33333Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M1.33325 8H14.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_2226_61">
|
|
||||||
<rect width="16" height="16" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 327 B |
@@ -1,6 +1,4 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M5.26659 13.3333C6.53897 13.986 8.00264 14.1628 9.39384 13.8319C10.785 13.5009 12.0123 12.6839 12.8544 11.5281C13.6966 10.3724 14.0982 8.95381 13.987 7.52811C13.8758 6.10241 13.259 4.76332 12.2478 3.75213C11.2366 2.74095 9.89751 2.12417 8.47181 2.01295C7.04611 1.90173 5.62757 2.30337 4.4718 3.1455C3.31603 3.98764 2.49905 5.21488 2.16807 6.60608C1.83709 7.99728 2.01388 9.46095 2.66659 10.7333L1.33325 14.6667L5.26659 13.3333Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M5.46115 8.43419C7.30678 8.43419 8.92229 7.43411 8.92229 5.21171C8.92229 2.98933 7.30678 1.98926 5.46115 1.98926C3.61553 1.98926 2 2.98933 2 5.21171C2 6.028 2.21794 6.67935 2.58519 7.17685C2.7184 7.35732 2.69033 7.77795 2.58387 7.97539C2.32908 8.44793 2.81048 8.9657 3.33372 8.84571C3.72539 8.75597 4.13621 8.63447 4.49574 8.4715C4.62736 8.41181 4.7727 8.38777 4.91631 8.40402C5.09471 8.42416 5.27678 8.43419 5.46115 8.43419Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M5.33325 8H5.33992" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M11.3385 6.24835C11.7049 6.74561 11.9224 7.39641 11.9224 8.2117C11.9224 9.02799 11.7044 9.67934 11.3372 10.1768C11.204 10.3573 11.232 10.7779 11.3385 10.9754C11.5933 11.4479 11.1119 11.9657 10.5886 11.8457C10.197 11.756 9.78615 11.6345 9.42662 11.4715C9.295 11.4118 9.14966 11.3878 9.00605 11.404C8.82765 11.4242 8.64558 11.4342 8.46121 11.4342C7.61469 11.4342 6.81658 11.2238 6.20055 10.7816" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M8 8H8.00667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M10.6667 8H10.6734" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 954 B After Width: | Height: | Size: 1.1 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="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="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="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>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 334 B |
@@ -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 |
@@ -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 |
@@ -117,7 +117,7 @@
|
|||||||
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
"ctrl-alt-space": "editor::ShowCharacterPalette",
|
||||||
"ctrl-;": "editor::ToggleLineNumbers",
|
"ctrl-;": "editor::ToggleLineNumbers",
|
||||||
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
|
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
|
||||||
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
"ctrl-'": "editor::ToggleHunkDiff",
|
||||||
"ctrl-\"": "editor::ExpandAllHunkDiffs",
|
"ctrl-\"": "editor::ExpandAllHunkDiffs",
|
||||||
"ctrl-i": "editor::ShowSignatureHelp",
|
"ctrl-i": "editor::ShowSignatureHelp",
|
||||||
"alt-g b": "editor::ToggleGitBlame",
|
"alt-g b": "editor::ToggleGitBlame",
|
||||||
@@ -136,12 +136,11 @@
|
|||||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||||
"find": "buffer_search::Deploy",
|
"find": "buffer_search::Deploy",
|
||||||
"ctrl-f": "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 }],
|
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||||
"ctrl->": "assistant::QuoteSelection",
|
"ctrl->": "assistant::QuoteSelection",
|
||||||
"ctrl-<": "assistant::InsertIntoEditor",
|
"ctrl-<": "assistant::InsertIntoEditor",
|
||||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
|
||||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -203,8 +202,8 @@
|
|||||||
"enter": "search::SelectNextMatch",
|
"enter": "search::SelectNextMatch",
|
||||||
"shift-enter": "search::SelectPrevMatch",
|
"shift-enter": "search::SelectPrevMatch",
|
||||||
"alt-enter": "search::SelectAllMatches",
|
"alt-enter": "search::SelectAllMatches",
|
||||||
"find": "search::FocusSearch",
|
|
||||||
"ctrl-f": "search::FocusSearch",
|
"ctrl-f": "search::FocusSearch",
|
||||||
|
"find": "search::FocusSearch",
|
||||||
"ctrl-h": "search::ToggleReplace",
|
"ctrl-h": "search::ToggleReplace",
|
||||||
"ctrl-l": "search::ToggleSelection"
|
"ctrl-l": "search::ToggleSelection"
|
||||||
}
|
}
|
||||||
@@ -290,15 +289,15 @@
|
|||||||
"f3": "search::SelectNextMatch",
|
"f3": "search::SelectNextMatch",
|
||||||
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
||||||
"shift-f3": "search::SelectPrevMatch",
|
"shift-f3": "search::SelectPrevMatch",
|
||||||
"shift-find": "project_search::ToggleFocus",
|
|
||||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||||
|
"shift-find": "project_search::ToggleFocus",
|
||||||
"ctrl-alt-shift-h": "search::ToggleReplace",
|
"ctrl-alt-shift-h": "search::ToggleReplace",
|
||||||
"ctrl-alt-shift-l": "search::ToggleSelection",
|
"ctrl-alt-shift-l": "search::ToggleSelection",
|
||||||
"alt-enter": "search::SelectAllMatches",
|
"alt-enter": "search::SelectAllMatches",
|
||||||
"alt-c": "search::ToggleCaseSensitive",
|
"alt-c": "search::ToggleCaseSensitive",
|
||||||
"alt-w": "search::ToggleWholeWord",
|
"alt-w": "search::ToggleWholeWord",
|
||||||
"alt-find": "project_search::ToggleFilters",
|
|
||||||
"alt-ctrl-f": "project_search::ToggleFilters",
|
"alt-ctrl-f": "project_search::ToggleFilters",
|
||||||
|
"alt-find": "project_search::ToggleFilters",
|
||||||
"ctrl-alt-shift-r": "search::ToggleRegex",
|
"ctrl-alt-shift-r": "search::ToggleRegex",
|
||||||
"ctrl-alt-shift-x": "search::ToggleRegex",
|
"ctrl-alt-shift-x": "search::ToggleRegex",
|
||||||
"alt-r": "search::ToggleRegex",
|
"alt-r": "search::ToggleRegex",
|
||||||
@@ -426,7 +425,6 @@
|
|||||||
"ctrl-shift-m": "diagnostics::Deploy",
|
"ctrl-shift-m": "diagnostics::Deploy",
|
||||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||||
"ctrl-shift-g": "git_panel::ToggleFocus",
|
|
||||||
"ctrl-?": "assistant::ToggleFocus",
|
"ctrl-?": "assistant::ToggleFocus",
|
||||||
"alt-save": "workspace::SaveAll",
|
"alt-save": "workspace::SaveAll",
|
||||||
"ctrl-alt-s": "workspace::SaveAll",
|
"ctrl-alt-s": "workspace::SaveAll",
|
||||||
@@ -503,14 +501,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && inline_completion",
|
"context": "Editor && inline_completion && !showing_completions",
|
||||||
"bindings": {
|
|
||||||
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
|
|
||||||
"alt-enter": "editor::AcceptInlineCompletion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "editor::AcceptInlineCompletion"
|
"tab": "editor::AcceptInlineCompletion"
|
||||||
@@ -552,7 +543,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
"shift-enter": "editor::ExpandExcerpts",
|
"shift-enter": "editor::ExpandExcerpts",
|
||||||
"ctrl-alt-enter": "editor::OpenExcerptsSplit",
|
"ctrl-k enter": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||||
"ctrl-f8": "editor::GoToHunk",
|
"ctrl-f8": "editor::GoToHunk",
|
||||||
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
||||||
@@ -654,7 +645,7 @@
|
|||||||
"shift-down": "menu::SelectNext",
|
"shift-down": "menu::SelectNext",
|
||||||
"shift-up": "menu::SelectPrev",
|
"shift-up": "menu::SelectPrev",
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
"ctrl-alt-enter": "editor::OpenExcerptsSplit"
|
"ctrl-k enter": "editor::OpenExcerptsSplit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -687,8 +678,8 @@
|
|||||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||||
"shift-find": "project_panel::NewSearchInDirectory",
|
|
||||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||||
|
"shift-find": "project_panel::NewSearchInDirectory",
|
||||||
"shift-down": "menu::SelectNext",
|
"shift-down": "menu::SelectNext",
|
||||||
"shift-up": "menu::SelectPrev",
|
"shift-up": "menu::SelectPrev",
|
||||||
"escape": "menu::Cancel"
|
"escape": "menu::Cancel"
|
||||||
@@ -700,34 +691,6 @@
|
|||||||
"space": "project_panel::Open"
|
"space": "project_panel::Open"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "GitPanel && !CommitEditor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"escape": "git_panel::Close"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "GitPanel && ChangesList",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"up": "menu::SelectPrev",
|
|
||||||
"down": "menu::SelectNext",
|
|
||||||
"enter": "menu::Confirm",
|
|
||||||
"space": "git::ToggleStaged",
|
|
||||||
"ctrl-space": "git::StageAll",
|
|
||||||
"ctrl-shift-space": "git::UnstageAll"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "GitPanel && CommitEditor > Editor",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"escape": "git_panel::FocusChanges",
|
|
||||||
"ctrl-enter": "git::CommitChanges",
|
|
||||||
"ctrl-shift-enter": "git::CommitAllChanges"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -805,8 +768,6 @@
|
|||||||
"shift-insert": "terminal::Paste",
|
"shift-insert": "terminal::Paste",
|
||||||
"ctrl-shift-v": "terminal::Paste",
|
"ctrl-shift-v": "terminal::Paste",
|
||||||
"ctrl-enter": "assistant::InlineAssist",
|
"ctrl-enter": "assistant::InlineAssist",
|
||||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
|
||||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
|
||||||
// Overrides for conflicting keybindings
|
// Overrides for conflicting keybindings
|
||||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||||
"ctrl-shift-a": "editor::SelectAll",
|
"ctrl-shift-a": "editor::SelectAll",
|
||||||
@@ -830,12 +791,5 @@
|
|||||||
"shift-end": "terminal::ScrollToBottom",
|
"shift-end": "terminal::ScrollToBottom",
|
||||||
"ctrl-shift-space": "terminal::ToggleViMode"
|
"ctrl-shift-space": "terminal::ToggleViMode"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "ZedPredictModal",
|
|
||||||
"use_key_equivalents": true,
|
|
||||||
"bindings": {
|
|
||||||
"escape": "menu::Cancel"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
"ctrl-cmd-space": "editor::ShowCharacterPalette",
|
"ctrl-cmd-space": "editor::ShowCharacterPalette",
|
||||||
"cmd-;": "editor::ToggleLineNumbers",
|
"cmd-;": "editor::ToggleLineNumbers",
|
||||||
"cmd-alt-z": "editor::RevertSelectedHunks",
|
"cmd-alt-z": "editor::RevertSelectedHunks",
|
||||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
"cmd-'": "editor::ToggleHunkDiff",
|
||||||
"cmd-\"": "editor::ExpandAllHunkDiffs",
|
"cmd-\"": "editor::ExpandAllHunkDiffs",
|
||||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||||
"cmd-i": "editor::ShowSignatureHelp",
|
"cmd-i": "editor::ShowSignatureHelp",
|
||||||
@@ -145,13 +145,12 @@
|
|||||||
"cmd-shift-enter": "editor::NewlineAbove",
|
"cmd-shift-enter": "editor::NewlineAbove",
|
||||||
"cmd-k z": "editor::ToggleSoftWrap",
|
"cmd-k z": "editor::ToggleSoftWrap",
|
||||||
"cmd-f": "buffer_search::Deploy",
|
"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-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
|
||||||
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||||
"cmd->": "assistant::QuoteSelection",
|
"cmd->": "assistant::QuoteSelection",
|
||||||
"cmd-<": "assistant::InsertIntoEditor",
|
"cmd-<": "assistant::InsertIntoEditor",
|
||||||
"cmd-alt-e": "editor::SelectEnclosingSymbol",
|
"cmd-alt-e": "editor::SelectEnclosingSymbol"
|
||||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -504,7 +503,6 @@
|
|||||||
"cmd-shift-m": "diagnostics::Deploy",
|
"cmd-shift-m": "diagnostics::Deploy",
|
||||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
"cmd-shift-b": "outline_panel::ToggleFocus",
|
||||||
"ctrl-shift-g": "git_panel::ToggleFocus",
|
|
||||||
"cmd-?": "assistant::ToggleFocus",
|
"cmd-?": "assistant::ToggleFocus",
|
||||||
"cmd-alt-s": "workspace::SaveAll",
|
"cmd-alt-s": "workspace::SaveAll",
|
||||||
"cmd-k m": "language_selector::Toggle",
|
"cmd-k m": "language_selector::Toggle",
|
||||||
@@ -580,14 +578,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && inline_completion",
|
"context": "Editor && inline_completion && !showing_completions",
|
||||||
"bindings": {
|
|
||||||
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
|
|
||||||
"alt-tab": "editor::AcceptInlineCompletion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "editor::AcceptInlineCompletion"
|
"tab": "editor::AcceptInlineCompletion"
|
||||||
@@ -629,7 +620,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
"shift-enter": "editor::ExpandExcerpts",
|
"shift-enter": "editor::ExpandExcerpts",
|
||||||
"cmd-alt-enter": "editor::OpenExcerptsSplit",
|
"cmd-k enter": "editor::OpenExcerptsSplit",
|
||||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||||
"cmd-f8": "editor::GoToHunk",
|
"cmd-f8": "editor::GoToHunk",
|
||||||
"cmd-shift-f8": "editor::GoToPrevHunk",
|
"cmd-shift-f8": "editor::GoToPrevHunk",
|
||||||
@@ -676,7 +667,7 @@
|
|||||||
"shift-down": "menu::SelectNext",
|
"shift-down": "menu::SelectNext",
|
||||||
"shift-up": "menu::SelectPrev",
|
"shift-up": "menu::SelectPrev",
|
||||||
"alt-enter": "editor::OpenExcerpts",
|
"alt-enter": "editor::OpenExcerpts",
|
||||||
"cmd-alt-enter": "editor::OpenExcerptsSplit"
|
"cmd-k enter": "editor::OpenExcerptsSplit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -841,8 +832,6 @@
|
|||||||
// Terminal.app compatibility
|
// Terminal.app compatibility
|
||||||
"alt-left": ["terminal::SendText", "\u001bb"],
|
"alt-left": ["terminal::SendText", "\u001bb"],
|
||||||
"alt-right": ["terminal::SendText", "\u001bf"],
|
"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.
|
// There are conflicting bindings for these keys in the global context.
|
||||||
// these bindings override them, remove at your own risk:
|
// these bindings override them, remove at your own risk:
|
||||||
"up": ["terminal::SendKeystroke", "up"],
|
"up": ["terminal::SendKeystroke", "up"],
|
||||||
@@ -890,7 +879,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ZedPredictModal",
|
"context": "ZedPredictTos",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "menu::Cancel"
|
"escape": "menu::Cancel"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"cmd-b": "editor::GoToDefinition",
|
"cmd-b": "editor::GoToDefinition",
|
||||||
"cmd-j": "editor::ScrollCursorCenter",
|
"cmd-j": "editor::ScrollCursorCenter",
|
||||||
"cmd-enter": "editor::NewlineBelow",
|
"cmd-enter": "editor::NewlineBelow",
|
||||||
"cmd-alt-enter": "editor::NewlineAbove",
|
"cmd-alt-enter": "editor::NewLineAbove",
|
||||||
"cmd-shift-l": "editor::SelectLine",
|
"cmd-shift-l": "editor::SelectLine",
|
||||||
"cmd-shift-t": "outline::Toggle",
|
"cmd-shift-t": "outline::Toggle",
|
||||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||||
"cmd-d": "project_panel::Duplicate",
|
"cmd-d": "project_panel::Duplicate",
|
||||||
"cmd-n": "project_panel::NewDirectory",
|
"cmd-n": "project_panel::NewFolder",
|
||||||
"return": "project_panel::Rename",
|
"return": "project_panel::Rename",
|
||||||
"cmd-c": "project_panel::Copy",
|
"cmd-c": "project_panel::Copy",
|
||||||
"cmd-v": "project_panel::Paste",
|
"cmd-v": "project_panel::Paste",
|
||||||
|
|||||||
@@ -86,7 +86,6 @@
|
|||||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||||
"v": "vim::ToggleVisual",
|
"v": "vim::ToggleVisual",
|
||||||
"shift-v": "vim::ToggleVisualLine",
|
"shift-v": "vim::ToggleVisualLine",
|
||||||
"ctrl-g": "vim::ShowLocation",
|
|
||||||
"ctrl-v": "vim::ToggleVisualBlock",
|
"ctrl-v": "vim::ToggleVisualBlock",
|
||||||
"ctrl-q": "vim::ToggleVisualBlock",
|
"ctrl-q": "vim::ToggleVisualBlock",
|
||||||
"shift-k": "editor::Hover",
|
"shift-k": "editor::Hover",
|
||||||
@@ -408,7 +407,6 @@
|
|||||||
"(": "vim::Parentheses",
|
"(": "vim::Parentheses",
|
||||||
")": "vim::Parentheses",
|
")": "vim::Parentheses",
|
||||||
"b": "vim::Parentheses",
|
"b": "vim::Parentheses",
|
||||||
// "b": "vim::AnyBrackets",
|
|
||||||
"[": "vim::SquareBrackets",
|
"[": "vim::SquareBrackets",
|
||||||
"]": "vim::SquareBrackets",
|
"]": "vim::SquareBrackets",
|
||||||
"r": "vim::SquareBrackets",
|
"r": "vim::SquareBrackets",
|
||||||
@@ -421,8 +419,7 @@
|
|||||||
"i": "vim::IndentObj",
|
"i": "vim::IndentObj",
|
||||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
|
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
|
||||||
"f": "vim::Method",
|
"f": "vim::Method",
|
||||||
"c": "vim::Class",
|
"c": "vim::Class"
|
||||||
"e": "vim::EntireFile"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -438,7 +435,7 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"d": "vim::CurrentLine",
|
"d": "vim::CurrentLine",
|
||||||
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
"s": ["vim::PushOperator", "DeleteSurrounds"],
|
||||||
"o": "editor::ToggleSelectedDiffHunks", // "d o"
|
"o": "editor::ToggleHunkDiff", // "d o"
|
||||||
"p": "editor::RevertSelectedHunks" // "d p"
|
"p": "editor::RevertSelectedHunks" // "d p"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -611,12 +608,10 @@
|
|||||||
"ctrl-w shift-s": "pane::SplitHorizontal",
|
"ctrl-w shift-s": "pane::SplitHorizontal",
|
||||||
"ctrl-w ctrl-s": "pane::SplitHorizontal",
|
"ctrl-w ctrl-s": "pane::SplitHorizontal",
|
||||||
"ctrl-w s": "pane::SplitHorizontal",
|
"ctrl-w s": "pane::SplitHorizontal",
|
||||||
"ctrl-w ctrl-c": "pane::CloseActiveItem",
|
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
||||||
"ctrl-w c": "pane::CloseActiveItem",
|
"ctrl-w c": "pane::CloseAllItems",
|
||||||
"ctrl-w ctrl-q": "pane::CloseActiveItem",
|
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||||
"ctrl-w q": "pane::CloseActiveItem",
|
"ctrl-w q": "pane::CloseAllItems",
|
||||||
"ctrl-w ctrl-a": "pane::CloseAllItems",
|
|
||||||
"ctrl-w a": "pane::CloseAllItems",
|
|
||||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||||
@@ -672,20 +667,5 @@
|
|||||||
"shift-g": "menu::SelectLast",
|
"shift-g": "menu::SelectLast",
|
||||||
"g g": "menu::SelectFirst"
|
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,9 +10,8 @@
|
|||||||
"light": "One Light",
|
"light": "One Light",
|
||||||
"dark": "One Dark"
|
"dark": "One Dark"
|
||||||
},
|
},
|
||||||
"icon_theme": "Zed (Default)",
|
|
||||||
// The name of a base set of key bindings to use.
|
// 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:
|
// text editor:
|
||||||
//
|
//
|
||||||
// 1. "VSCode"
|
// 1. "VSCode"
|
||||||
@@ -24,7 +23,7 @@
|
|||||||
"base_keymap": "VSCode",
|
"base_keymap": "VSCode",
|
||||||
// Features that can be globally enabled or disabled
|
// Features that can be globally enabled or disabled
|
||||||
"features": {
|
"features": {
|
||||||
// Which edit prediction provider to use.
|
// Which inline completion provider to use.
|
||||||
"inline_completion_provider": "copilot"
|
"inline_completion_provider": "copilot"
|
||||||
},
|
},
|
||||||
// The name of a font to use for rendering text in the editor
|
// The name of a font to use for rendering text in the editor
|
||||||
@@ -161,8 +160,8 @@
|
|||||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||||
"show_signature_help_after_edits": false,
|
"show_signature_help_after_edits": false,
|
||||||
/// Whether to show the edit predictions next to the completions provided by a language server.
|
/// Whether to show the inline completions next to the completions provided by a language server.
|
||||||
/// Only has an effect if edit prediction provider supports it.
|
/// Only has an effect if inline completion provider supports it.
|
||||||
"show_inline_completions_in_menu": true,
|
"show_inline_completions_in_menu": true,
|
||||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||||
@@ -196,14 +195,14 @@
|
|||||||
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
||||||
// no matter how they were inserted.
|
// no matter how they were inserted.
|
||||||
"always_treat_brackets_as_autoclosed": false,
|
"always_treat_brackets_as_autoclosed": false,
|
||||||
// Controls whether edit predictions are shown immediately (true)
|
// Controls whether inline completions are shown immediately (true)
|
||||||
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||||
"show_inline_completions": true,
|
"show_inline_completions": true,
|
||||||
// Controls whether edit predictions are shown in a given language scope.
|
// Controls whether inline completions are shown in a given language scope.
|
||||||
// Example: ["string", "comment"]
|
// Example: ["string", "comment"]
|
||||||
"inline_completions_disabled_in": [],
|
"inline_completions_disabled_in": [],
|
||||||
// Whether to show tabs and spaces in the editor.
|
// 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):
|
// 1. Draw tabs and spaces only for the selected text (default):
|
||||||
// "selection"
|
// "selection"
|
||||||
@@ -393,7 +392,7 @@
|
|||||||
/// Scrollbar-related settings
|
/// Scrollbar-related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
/// When to show the scrollbar in the project panel.
|
/// 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
|
/// 1. null (default): Inherit editor settings
|
||||||
/// 2. Show the scrollbar if there's important information or
|
/// 2. Show the scrollbar if there's important information or
|
||||||
@@ -465,7 +464,7 @@
|
|||||||
/// Scrollbar-related settings
|
/// Scrollbar-related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
/// When to show the scrollbar in the project panel.
|
/// 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
|
/// 1. null (default): Inherit editor settings
|
||||||
/// 2. Show the scrollbar if there's important information or
|
/// 2. Show the scrollbar if there's important information or
|
||||||
@@ -593,9 +592,7 @@
|
|||||||
// Whether or not to show the tab bar in the editor
|
// Whether or not to show the tab bar in the editor
|
||||||
"show": true,
|
"show": true,
|
||||||
// Whether or not to show the navigation history buttons.
|
// Whether or not to show the navigation history buttons.
|
||||||
"show_nav_history_buttons": true,
|
"show_nav_history_buttons": true
|
||||||
/// Whether or not to show the tab bar buttons.
|
|
||||||
"show_tab_bar_buttons": true
|
|
||||||
},
|
},
|
||||||
// Settings related to the editor's tabs
|
// Settings related to the editor's tabs
|
||||||
"tabs": {
|
"tabs": {
|
||||||
@@ -775,22 +772,8 @@
|
|||||||
// "load_direnv": "shell_hook"
|
// "load_direnv": "shell_hook"
|
||||||
"load_direnv": "direct",
|
"load_direnv": "direct",
|
||||||
"inline_completions": {
|
"inline_completions": {
|
||||||
// A list of globs representing files that edit predictions should be disabled for.
|
// A list of globs representing files that inline completions should be disabled for.
|
||||||
"disabled_globs": [
|
"disabled_globs": [".env"]
|
||||||
"**/.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"
|
|
||||||
},
|
},
|
||||||
// Settings specific to journaling
|
// Settings specific to journaling
|
||||||
"journal": {
|
"journal": {
|
||||||
@@ -929,7 +912,7 @@
|
|||||||
/// Scrollbar-related settings
|
/// Scrollbar-related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
/// When to show the scrollbar in the terminal.
|
/// 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
|
/// 1. null (default): Inherit editor settings
|
||||||
/// 2. Show the scrollbar if there's important information or
|
/// 2. Show the scrollbar if there's important information or
|
||||||
@@ -1183,9 +1166,6 @@
|
|||||||
},
|
},
|
||||||
"lmstudio": {
|
"lmstudio": {
|
||||||
"api_url": "http://localhost:1234/api/v0"
|
"api_url": "http://localhost:1234/api/v0"
|
||||||
},
|
|
||||||
"deepseek": {
|
|
||||||
"api_url": "https://api.deepseek.com"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Zed's Prettier integration settings.
|
// Zed's Prettier integration settings.
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use editor::Editor;
|
|||||||
use extension_host::ExtensionStore;
|
use extension_host::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, percentage, Animation, AnimationExt as _, App, Context, CursorStyle, Entity,
|
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||||
EventEmitter, InteractiveElement as _, ParentElement as _, Render, SharedString,
|
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||||
StatefulInteractiveElement, Styled, Transformation, Window,
|
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||||
};
|
};
|
||||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
|
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
|
||||||
use lsp::LanguageServerName;
|
use lsp::LanguageServerName;
|
||||||
@@ -27,8 +27,8 @@ pub enum Event {
|
|||||||
|
|
||||||
pub struct ActivityIndicator {
|
pub struct ActivityIndicator {
|
||||||
statuses: Vec<LspStatus>,
|
statuses: Vec<LspStatus>,
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
auto_updater: Option<Entity<AutoUpdater>>,
|
auto_updater: Option<Model<AutoUpdater>>,
|
||||||
context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,24 +46,22 @@ struct PendingWork<'a> {
|
|||||||
struct Content {
|
struct Content {
|
||||||
icon: Option<gpui::AnyElement>,
|
icon: Option<gpui::AnyElement>,
|
||||||
message: String,
|
message: String,
|
||||||
on_click:
|
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||||
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivityIndicator {
|
impl ActivityIndicator {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Workspace>,
|
||||||
cx: &mut Context<Workspace>,
|
) -> View<ActivityIndicator> {
|
||||||
) -> Entity<ActivityIndicator> {
|
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let auto_updater = AutoUpdater::get(cx);
|
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();
|
let mut status_events = languages.language_server_binary_statuses();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
while let Some((name, status)) = status_events.next().await {
|
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.retain(|s| s.name != name);
|
||||||
this.statuses.push(LspStatus { name, status });
|
this.statuses.push(LspStatus { name, status });
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -72,7 +70,6 @@ impl ActivityIndicator {
|
|||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
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 } => {
|
Event::ShowError { lsp_name, error } => {
|
||||||
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
let error = error.clone();
|
let error = error.clone();
|
||||||
let lsp_name = lsp_name.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?;
|
let buffer = create_buffer.await?;
|
||||||
buffer.update(&mut cx, |buffer, cx| {
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
buffer.edit(
|
buffer.edit(
|
||||||
@@ -106,14 +103,13 @@ impl ActivityIndicator {
|
|||||||
);
|
);
|
||||||
buffer.set_capability(language::Capability::ReadOnly, cx);
|
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(
|
workspace.add_item_to_active_pane(
|
||||||
Box::new(cx.new(|cx| {
|
Box::new(cx.new_view(|cx| {
|
||||||
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
|
Editor::for_buffer(buffer, Some(project.clone()), cx)
|
||||||
})),
|
})),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})?;
|
})?;
|
||||||
@@ -127,7 +123,7 @@ impl ActivityIndicator {
|
|||||||
this
|
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| {
|
self.statuses.retain(|status| {
|
||||||
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
|
||||||
cx.emit(Event::ShowError {
|
cx.emit(Event::ShowError {
|
||||||
@@ -143,12 +139,7 @@ impl ActivityIndicator {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss_error_message(
|
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &DismissErrorMessage,
|
|
||||||
_: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
if let Some(updater) = &self.auto_updater {
|
if let Some(updater) = &self.auto_updater {
|
||||||
updater.update(cx, |updater, cx| {
|
updater.update(cx, |updater, cx| {
|
||||||
updater.dismiss_error(cx);
|
updater.dismiss_error(cx);
|
||||||
@@ -159,7 +150,7 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
fn pending_language_server_work<'a>(
|
fn pending_language_server_work<'a>(
|
||||||
&self,
|
&self,
|
||||||
cx: &'a App,
|
cx: &'a AppContext,
|
||||||
) -> impl Iterator<Item = PendingWork<'a>> {
|
) -> impl Iterator<Item = PendingWork<'a>> {
|
||||||
self.project
|
self.project
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -187,12 +178,12 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
fn pending_environment_errors<'a>(
|
fn pending_environment_errors<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
cx: &'a App,
|
cx: &'a AppContext,
|
||||||
) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> {
|
) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> {
|
||||||
self.project.read(cx).shell_environment_errors(cx)
|
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
|
// Show if any direnv calls failed
|
||||||
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
|
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
|
||||||
return Some(Content {
|
return Some(Content {
|
||||||
@@ -202,11 +193,11 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: error.0.clone(),
|
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| {
|
this.project.update(cx, |project, cx| {
|
||||||
project.remove_environment_error(cx, worktree_id);
|
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
|
this.statuses
|
||||||
.retain(|status| !downloading.contains(&status.name));
|
.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
|
this.statuses
|
||||||
.retain(|status| !checking_for_update.contains(&status.name));
|
.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
|
acc
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.show_error_message(&Default::default(), window, cx)
|
this.show_error_message(&Default::default(), cx)
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -360,11 +351,11 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
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| {
|
indicator.project.update(cx, |project, cx| {
|
||||||
project.reset_last_formatting_failure(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(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Checking for Zed updates…".to_string(),
|
message: "Checking for Zed updates…".to_string(),
|
||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Downloading => Some(Content {
|
AutoUpdateStatus::Downloading => Some(Content {
|
||||||
@@ -390,8 +381,8 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Downloading Zed update…".to_string(),
|
message: "Downloading Zed update…".to_string(),
|
||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Installing => Some(Content {
|
AutoUpdateStatus::Installing => Some(Content {
|
||||||
@@ -401,8 +392,8 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Installing Zed update…".to_string(),
|
message: "Installing Zed update…".to_string(),
|
||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Updated { binary_path } => Some(Content {
|
AutoUpdateStatus::Updated { binary_path } => Some(Content {
|
||||||
@@ -412,7 +403,7 @@ impl ActivityIndicator {
|
|||||||
let reload = workspace::Reload {
|
let reload = workspace::Reload {
|
||||||
binary_path: Some(binary_path.clone()),
|
binary_path: Some(binary_path.clone()),
|
||||||
};
|
};
|
||||||
move |_, _, cx| workspace::reload(&reload, cx)
|
move |_, cx| workspace::reload(&reload, cx)
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Errored => Some(Content {
|
AutoUpdateStatus::Errored => Some(Content {
|
||||||
@@ -422,8 +413,8 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: "Auto update failed".to_string(),
|
message: "Auto update failed".to_string(),
|
||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
AutoUpdateStatus::Idle => None,
|
AutoUpdateStatus::Idle => None,
|
||||||
@@ -441,8 +432,8 @@ impl ActivityIndicator {
|
|||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!("Updating {extension_id} extension…"),
|
message: format!("Updating {extension_id} extension…"),
|
||||||
on_click: Some(Arc::new(|this, window, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
this.dismiss_error_message(&DismissErrorMessage, cx)
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -451,12 +442,8 @@ impl ActivityIndicator {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_language_server_work_context_menu(
|
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
self.context_menu_handle.toggle(cx);
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.context_menu_handle.toggle(window, cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +452,7 @@ impl EventEmitter<Event> for ActivityIndicator {}
|
|||||||
const MAX_MESSAGE_LEN: usize = 50;
|
const MAX_MESSAGE_LEN: usize = 50;
|
||||||
|
|
||||||
impl Render for ActivityIndicator {
|
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()
|
let result = h_flex()
|
||||||
.id("activity-indicator")
|
.id("activity-indicator")
|
||||||
.on_action(cx.listener(Self::show_error_message))
|
.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 {
|
let Some(content) = self.content_to_render(cx) else {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
let this = cx.entity().downgrade();
|
let this = cx.view().downgrade();
|
||||||
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
|
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
|
||||||
result.gap_2().child(
|
result.gap_2().child(
|
||||||
PopoverMenu::new("activity-indicator-popover")
|
PopoverMenu::new("activity-indicator-popover")
|
||||||
@@ -493,24 +480,24 @@ impl Render for ActivityIndicator {
|
|||||||
))
|
))
|
||||||
.size(LabelSize::Small),
|
.size(LabelSize::Small),
|
||||||
)
|
)
|
||||||
.tooltip(Tooltip::text(content.message))
|
.tooltip(move |cx| Tooltip::text(&content.message, cx))
|
||||||
} else {
|
} else {
|
||||||
button.child(Label::new(content.message).size(LabelSize::Small))
|
button.child(Label::new(content.message).size(LabelSize::Small))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when_some(content.on_click, |this, handler| {
|
.when_some(content.on_click, |this, handler| {
|
||||||
this.on_click(cx.listener(move |this, _, window, cx| {
|
this.on_click(cx.listener(move |this, _, cx| {
|
||||||
handler(this, window, cx);
|
handler(this, cx);
|
||||||
}))
|
}))
|
||||||
.cursor(CursorStyle::PointingHand)
|
.cursor(CursorStyle::PointingHand)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.anchor(gpui::Corner::BottomLeft)
|
.anchor(gpui::Corner::BottomLeft)
|
||||||
.menu(move |window, cx| {
|
.menu(move |cx| {
|
||||||
let strong_this = this.upgrade()?;
|
let strong_this = this.upgrade()?;
|
||||||
let mut has_work = false;
|
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) {
|
for work in strong_this.read(cx).pending_language_server_work(cx) {
|
||||||
has_work = true;
|
has_work = true;
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
@@ -526,7 +513,7 @@ impl Render for ActivityIndicator {
|
|||||||
let token = work.progress_token.to_string();
|
let token = work.progress_token.to_string();
|
||||||
let title = SharedString::from(title);
|
let title = SharedString::from(title);
|
||||||
menu = menu.custom_entry(
|
menu = menu.custom_entry(
|
||||||
move |_, _| {
|
move |_| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
@@ -534,7 +521,7 @@ impl Render for ActivityIndicator {
|
|||||||
.child(Icon::new(IconName::XCircle))
|
.child(Icon::new(IconName::XCircle))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
},
|
},
|
||||||
move |_, cx| {
|
move |cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.project.update(cx, |project, cx| {
|
this.project.update(cx, |project, cx| {
|
||||||
project.cancel_language_server_work(
|
project.cancel_language_server_work(
|
||||||
@@ -567,11 +554,5 @@ impl Render for ActivityIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StatusItemView for ActivityIndicator {
|
impl StatusItemView for ActivityIndicator {
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
|
||||||
&mut self,
|
|
||||||
_: Option<&dyn ItemHandle>,
|
|
||||||
_window: &mut Window,
|
|
||||||
_: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "anthropic"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
license = "GPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
1
crates/anthropic/LICENSE-AGPL
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-AGPL
|
||||||
@@ -2,7 +2,7 @@ mod supported_countries;
|
|||||||
|
|
||||||
use std::{pin::Pin, str::FromStr};
|
use std::{pin::Pin, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
|
||||||
use http_client::http::{HeaderMap, HeaderValue};
|
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 {
|
pub fn beta_headers(&self) -> String {
|
||||||
let mut headers = Self::DEFAULT_BETA_HEADERS
|
let mut headers = vec!["prompt-caching-2024-07-31".to_string()];
|
||||||
.into_iter()
|
|
||||||
.map(|header| header.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if let Self::Custom {
|
if let Self::Custom {
|
||||||
extra_beta_headers, ..
|
extra_beta_headers, ..
|
||||||
@@ -191,14 +186,12 @@ pub async fn complete(
|
|||||||
request: Request,
|
request: Request,
|
||||||
) -> Result<Response, AnthropicError> {
|
) -> Result<Response, AnthropicError> {
|
||||||
let uri = format!("{api_url}/v1/messages");
|
let uri = format!("{api_url}/v1/messages");
|
||||||
let beta_headers = Model::from_id(&request.model)
|
let model = Model::from_id(&request.model)?;
|
||||||
.map(|model| model.beta_headers())
|
|
||||||
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
|
|
||||||
let request_builder = HttpRequest::builder()
|
let request_builder = HttpRequest::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
.header("Anthropic-Version", "2023-06-01")
|
.header("Anthropic-Version", "2023-06-01")
|
||||||
.header("Anthropic-Beta", beta_headers)
|
.header("Anthropic-Beta", model.beta_headers())
|
||||||
.header("X-Api-Key", api_key)
|
.header("X-Api-Key", api_key)
|
||||||
.header("Content-Type", "application/json");
|
.header("Content-Type", "application/json");
|
||||||
|
|
||||||
@@ -250,7 +243,7 @@ pub async fn stream_completion(
|
|||||||
.map(|output| output.0)
|
.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)]
|
#[derive(Debug)]
|
||||||
pub struct RateLimitInfo {
|
pub struct RateLimitInfo {
|
||||||
pub requests_limit: usize,
|
pub requests_limit: usize,
|
||||||
@@ -309,14 +302,12 @@ pub async fn stream_completion_with_rate_limit_info(
|
|||||||
stream: true,
|
stream: true,
|
||||||
};
|
};
|
||||||
let uri = format!("{api_url}/v1/messages");
|
let uri = format!("{api_url}/v1/messages");
|
||||||
let beta_headers = Model::from_id(&request.base.model)
|
let model = Model::from_id(&request.base.model)?;
|
||||||
.map(|model| model.beta_headers())
|
|
||||||
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
|
|
||||||
let request_builder = HttpRequest::builder()
|
let request_builder = HttpRequest::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
.header("Anthropic-Version", "2023-06-01")
|
.header("Anthropic-Version", "2023-06-01")
|
||||||
.header("Anthropic-Beta", beta_headers)
|
.header("Anthropic-Beta", model.beta_headers())
|
||||||
.header("X-Api-Key", api_key)
|
.header("X-Api-Key", api_key)
|
||||||
.header("Content-Type", "application/json");
|
.header("Content-Type", "application/json");
|
||||||
let serialized_request =
|
let serialized_request =
|
||||||
@@ -626,7 +617,7 @@ pub struct ApiError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An Anthropic API error code.
|
/// 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)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum ApiErrorCode {
|
pub enum ApiErrorCode {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::LazyLock;
|
|||||||
|
|
||||||
/// Returns whether the given country code is supported by Anthropic.
|
/// 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 {
|
pub fn is_supported_country(country_code: &str) -> bool {
|
||||||
SUPPORTED_COUNTRIES.contains(&country_code)
|
SUPPORTED_COUNTRIES.contains(&country_code)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
// 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 anyhow::anyhow;
|
||||||
|
|
||||||
use gpui::{App, AssetSource, Result, SharedString};
|
use gpui::{AppContext, AssetSource, Result, SharedString};
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
@@ -39,7 +39,7 @@ impl AssetSource for Assets {
|
|||||||
|
|
||||||
impl Assets {
|
impl Assets {
|
||||||
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
|
/// 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 font_paths = self.list("fonts")?;
|
||||||
let mut embedded_fonts = Vec::new();
|
let mut embedded_fonts = Vec::new();
|
||||||
for font_path in font_paths {
|
for font_path in font_paths {
|
||||||
@@ -55,7 +55,7 @@ impl Assets {
|
|||||||
cx.text_system().add_fonts(embedded_fonts)
|
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()
|
cx.text_system()
|
||||||
.add_fonts(vec![self
|
.add_fonts(vec![self
|
||||||
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
|
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use client::Client;
|
|||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{actions, App, Global, UpdateGlobal};
|
use gpui::{actions, AppContext, Global, UpdateGlobal};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
||||||
};
|
};
|
||||||
@@ -67,7 +67,7 @@ impl Global for Assistant {}
|
|||||||
impl Assistant {
|
impl Assistant {
|
||||||
const NAMESPACE: &'static str = "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 {
|
if self.enabled == enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ pub fn init(
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
cx.set_global(Assistant::default());
|
cx.set_global(Assistant::default());
|
||||||
AssistantSettings::register(cx);
|
AssistantSettings::register(cx);
|
||||||
@@ -165,7 +165,7 @@ pub fn init(
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_language_model_settings(cx: &mut App) {
|
fn init_language_model_settings(cx: &mut AppContext) {
|
||||||
update_active_language_model_from_settings(cx);
|
update_active_language_model_from_settings(cx);
|
||||||
|
|
||||||
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
|
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
|
||||||
@@ -184,7 +184,7 @@ fn init_language_model_settings(cx: &mut App) {
|
|||||||
.detach();
|
.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 settings = AssistantSettings::get_global(cx);
|
||||||
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
|
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
|
||||||
let model_id = LanguageModelId::from(settings.default_model.model.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);
|
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||||
|
|
||||||
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
|
||||||
@@ -278,7 +278,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
|||||||
.detach();
|
.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 slash_command_registry = SlashCommandRegistry::global(cx);
|
||||||
let settings = SlashCommandSettings::get_global(cx);
|
let settings = SlashCommandSettings::get_global(cx);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
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 language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||||
use ui::{prelude::*, ElevationIndex};
|
use ui::{prelude::*, ElevationIndex};
|
||||||
use workspace::Item;
|
use workspace::Item;
|
||||||
@@ -13,17 +13,16 @@ pub struct ConfigurationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
let registry_subscription = cx.subscribe_in(
|
let registry_subscription = cx.subscribe(
|
||||||
&LanguageModelRegistry::global(cx),
|
&LanguageModelRegistry::global(cx),
|
||||||
window,
|
|this, _, event: &language_model::Event, cx| match event {
|
||||||
|this, _, event: &language_model::Event, window, cx| match event {
|
|
||||||
language_model::Event::AddedProvider(provider_id) => {
|
language_model::Event::AddedProvider(provider_id) => {
|
||||||
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
||||||
if let Some(provider) = provider {
|
if let Some(provider) = provider {
|
||||||
this.add_configuration_view(&provider, window, cx);
|
this.add_configuration_view(&provider, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
language_model::Event::RemovedProvider(provider_id) => {
|
language_model::Event::RemovedProvider(provider_id) => {
|
||||||
@@ -38,14 +37,14 @@ impl ConfigurationView {
|
|||||||
configuration_views: HashMap::default(),
|
configuration_views: HashMap::default(),
|
||||||
_registry_subscription: registry_subscription,
|
_registry_subscription: registry_subscription,
|
||||||
};
|
};
|
||||||
this.build_configuration_views(window, cx);
|
this.build_configuration_views(cx);
|
||||||
this
|
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();
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||||
for provider in 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(
|
fn add_configuration_view(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: &Arc<dyn LanguageModelProvider>,
|
provider: &Arc<dyn LanguageModelProvider>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let configuration_view = provider.configuration_view(window, cx);
|
let configuration_view = provider.configuration_view(cx);
|
||||||
self.configuration_views
|
self.configuration_views
|
||||||
.insert(provider.id(), configuration_view);
|
.insert(provider.id(), configuration_view);
|
||||||
}
|
}
|
||||||
@@ -67,7 +65,7 @@ impl ConfigurationView {
|
|||||||
fn render_provider_view(
|
fn render_provider_view(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: &Arc<dyn LanguageModelProvider>,
|
provider: &Arc<dyn LanguageModelProvider>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Div {
|
) -> Div {
|
||||||
let provider_id = provider.id().0.clone();
|
let provider_id = provider.id().0.clone();
|
||||||
let provider_name = provider.name().0.clone();
|
let provider_name = provider.name().0.clone();
|
||||||
@@ -75,7 +73,7 @@ impl ConfigurationView {
|
|||||||
|
|
||||||
let open_new_context = cx.listener({
|
let open_new_context = cx.listener({
|
||||||
let provider = provider.clone();
|
let provider = provider.clone();
|
||||||
move |_, _, _window, cx| {
|
move |_, _, cx| {
|
||||||
cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
|
cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
|
||||||
provider.clone(),
|
provider.clone(),
|
||||||
))
|
))
|
||||||
@@ -125,7 +123,7 @@ impl ConfigurationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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 providers = LanguageModelRegistry::read_global(cx).providers();
|
||||||
let provider_views = providers
|
let provider_views = providers
|
||||||
.into_iter()
|
.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
|
// 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.
|
// because we couldn't the element to take up the size of the parent.
|
||||||
canvas(
|
canvas(
|
||||||
move |bounds, window, cx| {
|
move |bounds, cx| {
|
||||||
element.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
|
element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
|
||||||
element
|
element
|
||||||
},
|
},
|
||||||
|_, mut element, window, cx| {
|
|_, mut element, cx| {
|
||||||
element.paint(window, cx);
|
element.paint(cx);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -184,8 +182,8 @@ pub enum ConfigurationViewEvent {
|
|||||||
|
|
||||||
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
|
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
|
||||||
|
|
||||||
impl Focusable for ConfigurationView {
|
impl FocusableView for ConfigurationView {
|
||||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +191,7 @@ impl Focusable for ConfigurationView {
|
|||||||
impl Item for ConfigurationView {
|
impl Item for ConfigurationView {
|
||||||
type Event = ConfigurationViewEvent;
|
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())
|
Some("Configuration".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::App;
|
use gpui::AppContext;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsSources};
|
use settings::{Settings, SettingsSources};
|
||||||
@@ -36,7 +36,7 @@ impl Settings for SlashCommandSettings {
|
|||||||
|
|
||||||
type FileContent = Self;
|
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(
|
SettingsSources::<Self::FileContent>::json_merge_with(
|
||||||
[sources.default]
|
[sources.default]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use editor::{
|
|||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription, Task,
|
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
|
||||||
TextStyle, UpdateGlobal, WeakEntity,
|
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,
|
||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
@@ -39,7 +39,7 @@ pub fn init(
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
|
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
|
||||||
}
|
}
|
||||||
@@ -86,20 +86,20 @@ impl TerminalInlineAssistant {
|
|||||||
|
|
||||||
pub fn assist(
|
pub fn assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
terminal_view: &Entity<TerminalView>,
|
terminal_view: &View<TerminalView>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
assistant_panel: Option<&Entity<AssistantPanel>>,
|
assistant_panel: Option<&View<AssistantPanel>>,
|
||||||
initial_prompt: Option<String>,
|
initial_prompt: Option<String>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
let terminal = terminal_view.read(cx).terminal().clone();
|
let terminal = terminal_view.read(cx).terminal().clone();
|
||||||
let assist_id = self.next_assist_id.post_inc();
|
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 =
|
||||||
let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
|
cx.new_model(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
|
||||||
let codegen = cx.new(|_| Codegen::new(terminal, self.telemetry.clone()));
|
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(
|
PromptEditor::new(
|
||||||
assist_id,
|
assist_id,
|
||||||
self.prompt_history.clone(),
|
self.prompt_history.clone(),
|
||||||
@@ -108,7 +108,6 @@ impl TerminalInlineAssistant {
|
|||||||
assistant_panel,
|
assistant_panel,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
self.fs.clone(),
|
self.fs.clone(),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -118,7 +117,7 @@ impl TerminalInlineAssistant {
|
|||||||
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
||||||
};
|
};
|
||||||
terminal_view.update(cx, |terminal_view, cx| {
|
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(
|
let terminal_assistant = TerminalInlineAssist::new(
|
||||||
@@ -127,27 +126,21 @@ impl TerminalInlineAssistant {
|
|||||||
assistant_panel.is_some(),
|
assistant_panel.is_some(),
|
||||||
prompt_editor,
|
prompt_editor,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.assists.insert(assist_id, terminal_assistant);
|
self.assists.insert(assist_id, terminal_assistant);
|
||||||
|
|
||||||
self.focus_assist(assist_id, window, cx);
|
self.focus_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_assist(
|
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
|
||||||
&mut self,
|
|
||||||
assist_id: TerminalInlineAssistId,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
let assist = &self.assists[&assist_id];
|
let assist = &self.assists[&assist_id];
|
||||||
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
|
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
|
||||||
prompt_editor.update(cx, |this, cx| {
|
prompt_editor.update(cx, |this, cx| {
|
||||||
this.editor.update(cx, |editor, cx| {
|
this.editor.update(cx, |editor, cx| {
|
||||||
window.focus(&editor.focus_handle(cx));
|
editor.focus(cx);
|
||||||
editor.select_all(&SelectAll, window, cx);
|
editor.select_all(&SelectAll, cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -155,10 +148,9 @@ impl TerminalInlineAssistant {
|
|||||||
|
|
||||||
fn handle_prompt_editor_event(
|
fn handle_prompt_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt_editor: Entity<PromptEditor>,
|
prompt_editor: View<PromptEditor>,
|
||||||
event: &PromptEditorEvent,
|
event: &PromptEditorEvent,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
let assist_id = prompt_editor.read(cx).id;
|
let assist_id = prompt_editor.read(cx).id;
|
||||||
match event {
|
match event {
|
||||||
@@ -169,21 +161,21 @@ impl TerminalInlineAssistant {
|
|||||||
self.stop_assist(assist_id, cx);
|
self.stop_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::ConfirmRequested { execute } => {
|
PromptEditorEvent::ConfirmRequested { execute } => {
|
||||||
self.finish_assist(assist_id, false, *execute, window, cx);
|
self.finish_assist(assist_id, false, *execute, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::CancelRequested => {
|
PromptEditorEvent::CancelRequested => {
|
||||||
self.finish_assist(assist_id, true, false, window, cx);
|
self.finish_assist(assist_id, true, false, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::DismissRequested => {
|
PromptEditorEvent::DismissRequested => {
|
||||||
self.dismiss_assist(assist_id, window, cx);
|
self.dismiss_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::Resized { height_in_lines } => {
|
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) {
|
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
assist
|
assist
|
||||||
} else {
|
} else {
|
||||||
@@ -221,7 +213,7 @@ impl TerminalInlineAssistant {
|
|||||||
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
|
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) {
|
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
assist
|
assist
|
||||||
} else {
|
} else {
|
||||||
@@ -234,7 +226,7 @@ impl TerminalInlineAssistant {
|
|||||||
fn request_for_inline_assist(
|
fn request_for_inline_assist(
|
||||||
&self,
|
&self,
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
cx: &mut App,
|
cx: &mut WindowContext,
|
||||||
) -> Result<LanguageModelRequest> {
|
) -> Result<LanguageModelRequest> {
|
||||||
let assist = self.assists.get(&assist_id).context("invalid assist")?;
|
let assist = self.assists.get(&assist_id).context("invalid assist")?;
|
||||||
|
|
||||||
@@ -242,7 +234,7 @@ impl TerminalInlineAssistant {
|
|||||||
let (latest_output, working_directory) = assist
|
let (latest_output, working_directory) = assist
|
||||||
.terminal
|
.terminal
|
||||||
.update(cx, |terminal, cx| {
|
.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 latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES);
|
||||||
let working_directory = terminal
|
let working_directory = terminal
|
||||||
.working_directory()
|
.working_directory()
|
||||||
@@ -304,17 +296,16 @@ impl TerminalInlineAssistant {
|
|||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
undo: bool,
|
undo: bool,
|
||||||
execute: bool,
|
execute: bool,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
self.dismiss_assist(assist_id, window, cx);
|
self.dismiss_assist(assist_id, cx);
|
||||||
|
|
||||||
if let Some(assist) = self.assists.remove(&assist_id) {
|
if let Some(assist) = self.assists.remove(&assist_id) {
|
||||||
assist
|
assist
|
||||||
.terminal
|
.terminal
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.clear_block_below_cursor(cx);
|
this.clear_block_below_cursor(cx);
|
||||||
this.focus_handle(cx).focus(window);
|
this.focus_handle(cx).focus(cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
@@ -357,8 +348,7 @@ impl TerminalInlineAssistant {
|
|||||||
fn dismiss_assist(
|
fn dismiss_assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(assist) = self.assists.get_mut(&assist_id) else {
|
let Some(assist) = self.assists.get_mut(&assist_id) else {
|
||||||
return false;
|
return false;
|
||||||
@@ -371,7 +361,7 @@ impl TerminalInlineAssistant {
|
|||||||
.terminal
|
.terminal
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.clear_block_below_cursor(cx);
|
this.clear_block_below_cursor(cx);
|
||||||
this.focus_handle(cx).focus(window);
|
this.focus_handle(cx).focus(cx);
|
||||||
})
|
})
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
@@ -380,8 +370,7 @@ impl TerminalInlineAssistant {
|
|||||||
&mut self,
|
&mut self,
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
height: u8,
|
height: u8,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
if let Some(assist) = self.assists.get_mut(&assist_id) {
|
if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
|
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
|
||||||
@@ -393,7 +382,7 @@ impl TerminalInlineAssistant {
|
|||||||
height,
|
height,
|
||||||
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
|
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();
|
.log_err();
|
||||||
}
|
}
|
||||||
@@ -402,10 +391,10 @@ impl TerminalInlineAssistant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TerminalInlineAssist {
|
struct TerminalInlineAssist {
|
||||||
terminal: WeakEntity<TerminalView>,
|
terminal: WeakView<TerminalView>,
|
||||||
prompt_editor: Option<Entity<PromptEditor>>,
|
prompt_editor: Option<View<PromptEditor>>,
|
||||||
codegen: Entity<Codegen>,
|
codegen: Model<Codegen>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
include_context: bool,
|
include_context: bool,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
@@ -413,12 +402,11 @@ struct TerminalInlineAssist {
|
|||||||
impl TerminalInlineAssist {
|
impl TerminalInlineAssist {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
terminal: &Entity<TerminalView>,
|
terminal: &View<TerminalView>,
|
||||||
include_context: bool,
|
include_context: bool,
|
||||||
prompt_editor: Entity<PromptEditor>,
|
prompt_editor: View<PromptEditor>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let codegen = prompt_editor.read(cx).codegen.clone();
|
let codegen = prompt_editor.read(cx).codegen.clone();
|
||||||
Self {
|
Self {
|
||||||
@@ -428,12 +416,12 @@ impl TerminalInlineAssist {
|
|||||||
workspace: workspace.clone(),
|
workspace: workspace.clone(),
|
||||||
include_context,
|
include_context,
|
||||||
_subscriptions: vec![
|
_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| {
|
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 {
|
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
|
||||||
CodegenEvent::Finished => {
|
CodegenEvent::Finished => {
|
||||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||||
@@ -466,7 +454,7 @@ impl TerminalInlineAssist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if assist.prompt_editor.is_none() {
|
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 {
|
struct PromptEditor {
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
height_in_lines: u8,
|
height_in_lines: u8,
|
||||||
editor: Entity<Editor>,
|
editor: View<Editor>,
|
||||||
language_model_selector: Entity<LanguageModelSelector>,
|
language_model_selector: View<LanguageModelSelector>,
|
||||||
edited_since_done: bool,
|
edited_since_done: bool,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
prompt_history_ix: Option<usize>,
|
prompt_history_ix: Option<usize>,
|
||||||
pending_prompt: String,
|
pending_prompt: String,
|
||||||
codegen: Entity<Codegen>,
|
codegen: Model<Codegen>,
|
||||||
_codegen_subscription: Subscription,
|
_codegen_subscription: Subscription,
|
||||||
editor_subscriptions: Vec<Subscription>,
|
editor_subscriptions: Vec<Subscription>,
|
||||||
pending_token_count: Task<Result<()>>,
|
pending_token_count: Task<Result<()>>,
|
||||||
token_count: Option<usize>,
|
token_count: Option<usize>,
|
||||||
_token_count_subscriptions: Vec<Subscription>,
|
_token_count_subscriptions: Vec<Subscription>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||||
|
|
||||||
impl Render 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 status = &self.codegen.read(cx).status;
|
||||||
let buttons = match status {
|
let buttons = match status {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
@@ -514,20 +502,16 @@ impl Render for PromptEditor {
|
|||||||
IconButton::new("cancel", IconName::Close)
|
IconButton::new("cancel", IconName::Close)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||||
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
|
|
||||||
})
|
|
||||||
.on_click(
|
.on_click(
|
||||||
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
|
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
|
||||||
),
|
),
|
||||||
IconButton::new("start", IconName::SparkleAlt)
|
IconButton::new("start", IconName::SparkleAlt)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
|
||||||
Tooltip::for_action("Generate", &menu::Confirm, window, cx)
|
|
||||||
})
|
|
||||||
.on_click(
|
.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)
|
IconButton::new("cancel", IconName::Close)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(Tooltip::text("Cancel Assist"))
|
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
|
||||||
.on_click(
|
.on_click(
|
||||||
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
|
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
|
||||||
),
|
),
|
||||||
IconButton::new("stop", IconName::Stop)
|
IconButton::new("stop", IconName::Stop)
|
||||||
.icon_color(Color::Error)
|
.icon_color(Color::Error)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
"Interrupt Generation",
|
"Interrupt Generation",
|
||||||
Some(&menu::Cancel),
|
Some(&menu::Cancel),
|
||||||
"Changes won't be discarded",
|
"Changes won't be discarded",
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(
|
.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)
|
let cancel = IconButton::new("cancel", IconName::Close)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||||
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
|
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
|
||||||
})
|
|
||||||
.on_click(
|
|
||||||
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let has_error = matches!(status, CodegenStatus::Error(_));
|
let has_error = matches!(status, CodegenStatus::Error(_));
|
||||||
if has_error || self.edited_since_done {
|
if has_error || self.edited_since_done {
|
||||||
@@ -575,16 +554,15 @@ impl Render for PromptEditor {
|
|||||||
IconButton::new("restart", IconName::RotateCw)
|
IconButton::new("restart", IconName::RotateCw)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
"Restart Generation",
|
"Restart Generation",
|
||||||
Some(&menu::Confirm),
|
Some(&menu::Confirm),
|
||||||
"Changes will be discarded",
|
"Changes will be discarded",
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
.on_click(cx.listener(|_, _, cx| {
|
||||||
cx.emit(PromptEditorEvent::StartRequested);
|
cx.emit(PromptEditorEvent::StartRequested);
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
@@ -594,29 +572,23 @@ impl Render for PromptEditor {
|
|||||||
IconButton::new("accept", IconName::Check)
|
IconButton::new("accept", IconName::Check)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::for_action(
|
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
|
||||||
"Accept Generated Command",
|
|
||||||
&menu::Confirm,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
.on_click(cx.listener(|_, _, cx| {
|
||||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||||
})),
|
})),
|
||||||
IconButton::new("confirm", IconName::Play)
|
IconButton::new("confirm", IconName::Play)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::for_action(
|
Tooltip::for_action(
|
||||||
"Execute Generated Command",
|
"Execute Generated Command",
|
||||||
&menu::SecondaryConfirm,
|
&menu::SecondaryConfirm,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
.on_click(cx.listener(|_, _, cx| {
|
||||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
@@ -647,7 +619,7 @@ impl Render for PromptEditor {
|
|||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
format!(
|
format!(
|
||||||
"Using {}",
|
"Using {}",
|
||||||
@@ -658,7 +630,6 @@ impl Render for PromptEditor {
|
|||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
"Change Model",
|
"Change Model",
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -669,7 +640,7 @@ impl Render for PromptEditor {
|
|||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.id("error")
|
.id("error")
|
||||||
.tooltip(Tooltip::text(error_message))
|
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::XCircle)
|
Icon::new(IconName::XCircle)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -692,8 +663,8 @@ impl Render for PromptEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for PromptEditor {
|
impl FocusableView for PromptEditor {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.editor.focus_handle(cx)
|
self.editor.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,15 +676,14 @@ impl PromptEditor {
|
|||||||
fn new(
|
fn new(
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
prompt_buffer: Entity<MultiBuffer>,
|
prompt_buffer: Model<MultiBuffer>,
|
||||||
codegen: Entity<Codegen>,
|
codegen: Model<Codegen>,
|
||||||
assistant_panel: Option<&Entity<AssistantPanel>>,
|
assistant_panel: Option<&View<AssistantPanel>>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let prompt_editor = cx.new(|cx| {
|
let prompt_editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::new(
|
let mut editor = Editor::new(
|
||||||
EditorMode::AutoHeight {
|
EditorMode::AutoHeight {
|
||||||
max_lines: Self::MAX_LINES as usize,
|
max_lines: Self::MAX_LINES as usize,
|
||||||
@@ -721,28 +691,24 @@ impl PromptEditor {
|
|||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, 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
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut token_count_subscriptions = Vec::new();
|
let mut token_count_subscriptions = Vec::new();
|
||||||
if let Some(assistant_panel) = assistant_panel {
|
if let Some(assistant_panel) = assistant_panel {
|
||||||
token_count_subscriptions.push(cx.subscribe_in(
|
token_count_subscriptions
|
||||||
assistant_panel,
|
.push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
|
||||||
window,
|
|
||||||
Self::handle_assistant_panel_event,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
id,
|
id,
|
||||||
height_in_lines: 1,
|
height_in_lines: 1,
|
||||||
editor: prompt_editor,
|
editor: prompt_editor,
|
||||||
language_model_selector: cx.new(|cx| {
|
language_model_selector: cx.new_view(|cx| {
|
||||||
let fs = fs.clone();
|
let fs = fs.clone();
|
||||||
LanguageModelSelector::new(
|
LanguageModelSelector::new(
|
||||||
move |model, cx| {
|
move |model, cx| {
|
||||||
@@ -752,7 +718,6 @@ impl PromptEditor {
|
|||||||
move |settings, _| settings.set_model(model.clone()),
|
move |settings, _| settings.set_model(model.clone()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -760,7 +725,7 @@ impl PromptEditor {
|
|||||||
prompt_history,
|
prompt_history,
|
||||||
prompt_history_ix: None,
|
prompt_history_ix: None,
|
||||||
pending_prompt: String::new(),
|
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(),
|
editor_subscriptions: Vec::new(),
|
||||||
codegen,
|
codegen,
|
||||||
pending_token_count: Task::ready(Ok(())),
|
pending_token_count: Task::ready(Ok(())),
|
||||||
@@ -774,15 +739,15 @@ impl PromptEditor {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder_text(window: &Window) -> String {
|
fn placeholder_text(cx: &WindowContext) -> String {
|
||||||
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, window)
|
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, cx)
|
||||||
.map(|keybinding| format!(" • {keybinding} for context"))
|
.map(|keybinding| format!(" • {keybinding} for context"))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
format!("Generate…{context_keybinding} • ↓↑ for history")
|
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.clear();
|
||||||
self.editor_subscriptions
|
self.editor_subscriptions
|
||||||
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
|
.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));
|
.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)
|
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(
|
let height_in_lines = cmp::max(
|
||||||
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
||||||
cmp::min(
|
cmp::min(
|
||||||
@@ -812,16 +777,15 @@ impl PromptEditor {
|
|||||||
|
|
||||||
fn handle_assistant_panel_event(
|
fn handle_assistant_panel_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &Entity<AssistantPanel>,
|
_: View<AssistantPanel>,
|
||||||
event: &AssistantPanelEvent,
|
event: &AssistantPanelEvent,
|
||||||
_: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let AssistantPanelEvent::ContextEdited { .. } = event;
|
let AssistantPanelEvent::ContextEdited { .. } = event;
|
||||||
self.count_tokens(cx);
|
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 assist_id = self.id;
|
||||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||||
return;
|
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);
|
self.count_lines(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_prompt_editor_events(
|
fn handle_prompt_editor_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Entity<Editor>,
|
_: View<Editor>,
|
||||||
event: &EditorEvent,
|
event: &EditorEvent,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited { .. } => {
|
EditorEvent::Edited { .. } => {
|
||||||
@@ -872,12 +836,7 @@ impl PromptEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_codegen_changed(
|
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: Entity<Codegen>,
|
|
||||||
_: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
match &self.codegen.read(cx).status {
|
match &self.codegen.read(cx).status {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
self.editor
|
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 {
|
match &self.codegen.read(cx).status {
|
||||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||||
cx.emit(PromptEditorEvent::CancelRequested);
|
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 {
|
match &self.codegen.read(cx).status {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
if !self.editor.read(cx).text(cx).trim().is_empty() {
|
if !self.editor.read(cx).text(cx).trim().is_empty() {
|
||||||
@@ -929,58 +888,53 @@ impl PromptEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn secondary_confirm(
|
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &menu::SecondaryConfirm,
|
|
||||||
_: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
|
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
|
||||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
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 let Some(ix) = self.prompt_history_ix {
|
||||||
if ix > 0 {
|
if ix > 0 {
|
||||||
self.prompt_history_ix = Some(ix - 1);
|
self.prompt_history_ix = Some(ix - 1);
|
||||||
let prompt = self.prompt_history[ix - 1].as_str();
|
let prompt = self.prompt_history[ix - 1].as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_beginning(&Default::default(), window, cx);
|
editor.move_to_beginning(&Default::default(), cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if !self.prompt_history.is_empty() {
|
} else if !self.prompt_history.is_empty() {
|
||||||
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
||||||
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_beginning(&Default::default(), window, 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 let Some(ix) = self.prompt_history_ix {
|
||||||
if ix < self.prompt_history.len() - 1 {
|
if ix < self.prompt_history.len() - 1 {
|
||||||
self.prompt_history_ix = Some(ix + 1);
|
self.prompt_history_ix = Some(ix + 1);
|
||||||
let prompt = self.prompt_history[ix + 1].as_str();
|
let prompt = self.prompt_history[ix + 1].as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_end(&Default::default(), window, cx)
|
editor.move_to_end(&Default::default(), cx)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.prompt_history_ix = None;
|
self.prompt_history_ix = None;
|
||||||
let prompt = self.pending_prompt.as_str();
|
let prompt = self.pending_prompt.as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_end(&Default::default(), window, 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 model = LanguageModelRegistry::read_global(cx).active_model()?;
|
||||||
let token_count = self.token_count?;
|
let token_count = self.token_count?;
|
||||||
let max_token_count = model.max_token_count();
|
let max_token_count = model.max_token_count();
|
||||||
@@ -1010,35 +964,34 @@ impl PromptEditor {
|
|||||||
);
|
);
|
||||||
if let Some(workspace) = self.workspace.clone() {
|
if let Some(workspace) = self.workspace.clone() {
|
||||||
token_count = token_count
|
token_count = token_count
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
"Tokens Used by Inline Assistant",
|
"Tokens Used by Inline Assistant",
|
||||||
None,
|
None,
|
||||||
"Click to Open Assistant Panel",
|
"Click to Open Assistant Panel",
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
.on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx)
|
workspace.focus_panel::<AssistantPanel>(cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
token_count = token_count
|
token_count = token_count
|
||||||
.cursor_default()
|
.cursor_default()
|
||||||
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
|
.tooltip(|cx| Tooltip::text("Tokens Used by Inline Assistant", cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(token_count)
|
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 settings = ThemeSettings::get_global(cx);
|
||||||
let text_style = TextStyle {
|
let text_style = TextStyle {
|
||||||
color: if self.editor.read(cx).read_only(cx) {
|
color: if self.editor.read(cx).read_only(cx) {
|
||||||
@@ -1076,27 +1029,27 @@ const CLEAR_INPUT: &str = "\x15";
|
|||||||
const CARRIAGE_RETURN: &str = "\x0d";
|
const CARRIAGE_RETURN: &str = "\x0d";
|
||||||
|
|
||||||
struct TerminalTransaction {
|
struct TerminalTransaction {
|
||||||
terminal: Entity<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalTransaction {
|
impl TerminalTransaction {
|
||||||
pub fn start(terminal: Entity<Terminal>) -> Self {
|
pub fn start(terminal: Model<Terminal>) -> Self {
|
||||||
Self { terminal }
|
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
|
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||||
let input = Self::sanitize_input(hunk);
|
let input = Self::sanitize_input(hunk);
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(input));
|
.update(cx, |terminal, _| terminal.input(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&self, cx: &mut App) {
|
pub fn undo(&self, cx: &mut AppContext) {
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
.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, _| {
|
self.terminal.update(cx, |terminal, _| {
|
||||||
terminal.input(CARRIAGE_RETURN.to_string())
|
terminal.input(CARRIAGE_RETURN.to_string())
|
||||||
});
|
});
|
||||||
@@ -1110,14 +1063,14 @@ impl TerminalTransaction {
|
|||||||
pub struct Codegen {
|
pub struct Codegen {
|
||||||
status: CodegenStatus,
|
status: CodegenStatus,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
terminal: Entity<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
generation: Task<()>,
|
generation: Task<()>,
|
||||||
message_id: Option<String>,
|
message_id: Option<String>,
|
||||||
transaction: Option<TerminalTransaction>,
|
transaction: Option<TerminalTransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Codegen {
|
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 {
|
Self {
|
||||||
terminal,
|
terminal,
|
||||||
telemetry,
|
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 {
|
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -1228,20 +1181,20 @@ impl Codegen {
|
|||||||
cx.notify();
|
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.status = CodegenStatus::Done;
|
||||||
self.generation = Task::ready(());
|
self.generation = Task::ready(());
|
||||||
cx.emit(CodegenEvent::Finished);
|
cx.emit(CodegenEvent::Finished);
|
||||||
cx.notify();
|
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() {
|
if let Some(transaction) = self.transaction.take() {
|
||||||
transaction.complete(cx);
|
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() {
|
if let Some(transaction) = self.transaction.take() {
|
||||||
transaction.undo(cx);
|
transaction.undo(cx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ fs.workspace = true
|
|||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
heed.workspace = true
|
|
||||||
html_to_markdown.workspace = true
|
html_to_markdown.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
@@ -53,7 +52,6 @@ markdown.workspace = true
|
|||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
prompt_library.workspace = true
|
prompt_library.workspace = true
|
||||||
@@ -73,6 +71,7 @@ theme.workspace = true
|
|||||||
time.workspace = true
|
time.workspace = true
|
||||||
time_format.workspace = true
|
time_format.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
unindent.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use std::sync::Arc;
|
|||||||
use assistant_tool::ToolWorkingSet;
|
use assistant_tool::ToolWorkingSet;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, Length,
|
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
|
||||||
ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, TextStyleRefinement,
|
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
|
||||||
UnderlineStyle, WeakEntity,
|
TextStyleRefinement, UnderlineStyle, View, WeakView,
|
||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use language_model::Role;
|
use language_model::Role;
|
||||||
@@ -16,48 +16,43 @@ use ui::prelude::*;
|
|||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||||
use crate::thread_store::ThreadStore;
|
|
||||||
use crate::ui::ContextPill;
|
use crate::ui::ContextPill;
|
||||||
|
|
||||||
pub struct ActiveThread {
|
pub struct ActiveThread {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
thread_store: Entity<ThreadStore>,
|
pub(crate) thread: Model<Thread>,
|
||||||
thread: Entity<Thread>,
|
|
||||||
messages: Vec<MessageId>,
|
messages: Vec<MessageId>,
|
||||||
list_state: ListState,
|
list_state: ListState,
|
||||||
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
|
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
|
||||||
last_error: Option<ThreadError>,
|
last_error: Option<ThreadError>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveThread {
|
impl ActiveThread {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread: Entity<Thread>,
|
thread: Model<Thread>,
|
||||||
thread_store: Entity<ThreadStore>,
|
workspace: WeakView<Workspace>,
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.observe(&thread, |_, _, cx| cx.notify()),
|
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 {
|
let mut this = Self {
|
||||||
workspace,
|
workspace,
|
||||||
language_registry,
|
language_registry,
|
||||||
tools,
|
tools,
|
||||||
thread_store,
|
|
||||||
thread: thread.clone(),
|
thread: thread.clone(),
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
rendered_messages_by_id: HashMap::default(),
|
rendered_messages_by_id: HashMap::default(),
|
||||||
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||||
let this = cx.entity().downgrade();
|
let this = cx.view().downgrade();
|
||||||
move |ix, _: &mut Window, cx: &mut App| {
|
move |ix, cx: &mut WindowContext| {
|
||||||
this.update(cx, |this, cx| this.render_message(ix, cx))
|
this.update(cx, |this, cx| this.render_message(ix, cx))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
@@ -67,29 +62,25 @@ impl ActiveThread {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
||||||
this.push_message(&message.id, message.text.clone(), window, cx);
|
this.push_message(&message.id, message.text.clone(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread(&self) -> &Entity<Thread> {
|
|
||||||
&self.thread
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.messages.is_empty()
|
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()
|
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()
|
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.last_error.take();
|
||||||
self.thread
|
self.thread
|
||||||
.update(cx, |thread, _cx| thread.cancel_last_completion())
|
.update(cx, |thread, _cx| thread.cancel_last_completion())
|
||||||
@@ -103,13 +94,7 @@ impl ActiveThread {
|
|||||||
self.last_error.take();
|
self.last_error.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_message(
|
fn push_message(&mut self, id: &MessageId, text: String, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
id: &MessageId,
|
|
||||||
text: String,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let old_len = self.messages.len();
|
let old_len = self.messages.len();
|
||||||
self.messages.push(*id);
|
self.messages.push(*id);
|
||||||
self.list_state.splice(old_len..old_len, 1);
|
self.list_state.splice(old_len..old_len, 1);
|
||||||
@@ -118,7 +103,7 @@ impl ActiveThread {
|
|||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
let ui_font_size = TextSize::Default.rems(cx);
|
let ui_font_size = TextSize::Default.rems(cx);
|
||||||
let buffer_font_size = TextSize::Small.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 {
|
text_style.refine(&TextStyleRefinement {
|
||||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||||
@@ -177,13 +162,12 @@ impl ActiveThread {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let markdown = cx.new(|cx| {
|
let markdown = cx.new_view(|cx| {
|
||||||
Markdown::new(
|
Markdown::new(
|
||||||
text,
|
text,
|
||||||
markdown_style,
|
markdown_style,
|
||||||
Some(self.language_registry.clone()),
|
Some(self.language_registry.clone()),
|
||||||
None,
|
None,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -196,26 +180,20 @@ impl ActiveThread {
|
|||||||
|
|
||||||
fn handle_thread_event(
|
fn handle_thread_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &Entity<Thread>,
|
_: Model<Thread>,
|
||||||
event: &ThreadEvent,
|
event: &ThreadEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
ThreadEvent::ShowError(error) => {
|
ThreadEvent::ShowError(error) => {
|
||||||
self.last_error = Some(error.clone());
|
self.last_error = Some(error.clone());
|
||||||
}
|
}
|
||||||
ThreadEvent::StreamedCompletion | ThreadEvent::SummaryChanged => {
|
ThreadEvent::StreamedCompletion => {}
|
||||||
self.thread_store
|
ThreadEvent::SummaryChanged => {}
|
||||||
.update(cx, |thread_store, cx| {
|
|
||||||
thread_store.save_thread(&self.thread, cx)
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
||||||
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
|
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
|
||||||
markdown.update(cx, |markdown, cx| {
|
markdown.update(cx, |markdown, cx| {
|
||||||
markdown.append(text, window, cx);
|
markdown.append(text, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,15 +204,9 @@ impl ActiveThread {
|
|||||||
.message(*message_id)
|
.message(*message_id)
|
||||||
.map(|message| message.text.clone())
|
.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
|
|
||||||
.update(cx, |thread_store, cx| {
|
|
||||||
thread_store.save_thread(&self.thread, cx)
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
ThreadEvent::UsePendingTools => {
|
ThreadEvent::UsePendingTools => {
|
||||||
@@ -249,7 +221,7 @@ impl ActiveThread {
|
|||||||
|
|
||||||
for tool_use in pending_tool_uses {
|
for tool_use in pending_tool_uses {
|
||||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
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| {
|
self.thread.update(cx, |thread, cx| {
|
||||||
thread.insert_tool_output(
|
thread.insert_tool_output(
|
||||||
@@ -266,7 +238,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 message_id = self.messages[ix];
|
||||||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||||
return Empty.into_any();
|
return Empty.into_any();
|
||||||
@@ -298,7 +270,7 @@ impl ActiveThread {
|
|||||||
let styled_message = match message.role {
|
let styled_message = match message.role {
|
||||||
Role::User => v_flex()
|
Role::User => v_flex()
|
||||||
.id(("message-container", ix))
|
.id(("message-container", ix))
|
||||||
.pt_2p5()
|
.py_1()
|
||||||
.px_2p5()
|
.px_2p5()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -347,9 +319,10 @@ impl ActiveThread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.pt_1p5()
|
||||||
.child(list(self.list_state.clone()).flex_grow())
|
.child(list(self.list_state.clone()).flex_grow())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use client::Client;
|
|||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{actions, App};
|
use gpui::{actions, AppContext};
|
||||||
use prompt_library::PromptBuilder;
|
use prompt_library::PromptBuilder;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ pub fn init(
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
AssistantSettings::register(cx);
|
AssistantSettings::register(cx);
|
||||||
assistant_panel::init(cx);
|
assistant_panel::init(cx);
|
||||||
@@ -84,7 +84,7 @@ pub fn init(
|
|||||||
feature_gate_assistant2_actions(cx);
|
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| {
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
filter.hide_namespace(NAMESPACE);
|
filter.hide_namespace(NAMESPACE);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
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 language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||||
use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
|
use ui::{prelude::*, ElevationIndex};
|
||||||
use zed_actions::assistant::DeployPromptLibrary;
|
use zed_actions::assistant::DeployPromptLibrary;
|
||||||
|
|
||||||
pub struct AssistantConfiguration {
|
pub struct AssistantConfiguration {
|
||||||
@@ -13,17 +13,16 @@ pub struct AssistantConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
let registry_subscription = cx.subscribe_in(
|
let registry_subscription = cx.subscribe(
|
||||||
&LanguageModelRegistry::global(cx),
|
&LanguageModelRegistry::global(cx),
|
||||||
window,
|
|this, _, event: &language_model::Event, cx| match event {
|
||||||
|this, _, event: &language_model::Event, window, cx| match event {
|
|
||||||
language_model::Event::AddedProvider(provider_id) => {
|
language_model::Event::AddedProvider(provider_id) => {
|
||||||
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
||||||
if let Some(provider) = provider {
|
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) => {
|
language_model::Event::RemovedProvider(provider_id) => {
|
||||||
@@ -38,14 +37,14 @@ impl AssistantConfiguration {
|
|||||||
configuration_views_by_provider: HashMap::default(),
|
configuration_views_by_provider: HashMap::default(),
|
||||||
_registry_subscription: registry_subscription,
|
_registry_subscription: registry_subscription,
|
||||||
};
|
};
|
||||||
this.build_provider_configuration_views(window, cx);
|
this.build_provider_configuration_views(cx);
|
||||||
this
|
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();
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||||
for provider in 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(
|
fn add_provider_configuration_view(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: &Arc<dyn LanguageModelProvider>,
|
provider: &Arc<dyn LanguageModelProvider>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let configuration_view = provider.configuration_view(window, cx);
|
let configuration_view = provider.configuration_view(cx);
|
||||||
self.configuration_views_by_provider
|
self.configuration_views_by_provider
|
||||||
.insert(provider.id(), configuration_view);
|
.insert(provider.id(), configuration_view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for AssistantConfiguration {
|
impl FocusableView for AssistantConfiguration {
|
||||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +79,7 @@ impl AssistantConfiguration {
|
|||||||
fn render_provider_configuration(
|
fn render_provider_configuration(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: &Arc<dyn LanguageModelProvider>,
|
provider: &Arc<dyn LanguageModelProvider>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let provider_id = provider.id().0.clone();
|
let provider_id = provider.id().0.clone();
|
||||||
let provider_name = provider.name().0.clone();
|
let provider_name = provider.name().0.clone();
|
||||||
@@ -91,47 +89,38 @@ impl AssistantConfiguration {
|
|||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1p5()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Icon::new(provider.icon())
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(Label::new(provider_name.clone())),
|
|
||||||
)
|
|
||||||
.when(provider.is_authenticated(cx), |parent| {
|
.when(provider.is_authenticated(cx), |parent| {
|
||||||
parent.child(
|
parent.child(
|
||||||
Button::new(
|
h_flex().justify_end().child(
|
||||||
SharedString::from(format!("new-thread-{provider_id}")),
|
Button::new(
|
||||||
"Start New Thread",
|
SharedString::from(format!("new-thread-{provider_id}")),
|
||||||
)
|
"Open New Thread",
|
||||||
.icon_position(IconPosition::Start)
|
)
|
||||||
.icon(IconName::Plus)
|
.icon_position(IconPosition::Start)
|
||||||
.icon_size(IconSize::Small)
|
.icon(IconName::Plus)
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.layer(ElevationIndex::ModalSurface)
|
||||||
.label_size(LabelSize::Small)
|
.on_click(cx.listener({
|
||||||
.on_click(cx.listener({
|
let provider = provider.clone();
|
||||||
let provider = provider.clone();
|
move |_this, _event, cx| {
|
||||||
move |_this, _event, _window, cx| {
|
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
provider.clone(),
|
||||||
provider.clone(),
|
))
|
||||||
))
|
}
|
||||||
}
|
})),
|
||||||
})),
|
),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.p(DynamicSpacing::Base08.rems(cx))
|
.p(DynamicSpacing::Base08.rems(cx))
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().surface_background)
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
@@ -146,49 +135,34 @@ impl AssistantConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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();
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("assistant-configuration")
|
.id("assistant-configuration")
|
||||||
.track_focus(&self.focus_handle(cx))
|
.track_focus(&self.focus_handle(cx))
|
||||||
.bg(cx.theme().colors().panel_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
h_flex().p(DynamicSpacing::Base16.rems(cx)).child(
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
Button::new("open-prompt-library", "Open Prompt Library")
|
||||||
.gap_1()
|
.style(ButtonStyle::Filled)
|
||||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
.full_width()
|
||||||
.child(
|
.icon(IconName::Book)
|
||||||
Button::new("open-prompt-library", "Open Prompt Library")
|
.icon_size(IconSize::Small)
|
||||||
.style(ButtonStyle::Filled)
|
.icon_position(IconPosition::Start)
|
||||||
.layer(ElevationIndex::ModalSurface)
|
.on_click(|_event, cx| {
|
||||||
.full_width()
|
cx.dispatch_action(DeployPromptLibrary.boxed_clone())
|
||||||
.icon(IconName::Book)
|
}),
|
||||||
.icon_size(IconSize::Small)
|
),
|
||||||
.icon_position(IconPosition::Start)
|
|
||||||
.on_click(|_event, _window, cx| {
|
|
||||||
cx.dispatch_action(&DeployPromptLibrary)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(Divider::horizontal().color(DividerColor::Border))
|
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p(DynamicSpacing::Base16.rems(cx))
|
.p(DynamicSpacing::Base16.rems(cx))
|
||||||
.mt_1()
|
.mt_1()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.flex_1()
|
.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(
|
.children(
|
||||||
providers
|
providers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Entity, FocusHandle, SharedString};
|
use gpui::{FocusHandle, View};
|
||||||
use language_model::LanguageModelRegistry;
|
use language_model::LanguageModelRegistry;
|
||||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
@@ -10,7 +10,7 @@ use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
|||||||
use crate::ToggleModelSelector;
|
use crate::ToggleModelSelector;
|
||||||
|
|
||||||
pub struct AssistantModelSelector {
|
pub struct AssistantModelSelector {
|
||||||
selector: Entity<LanguageModelSelector>,
|
selector: View<LanguageModelSelector>,
|
||||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
@@ -20,11 +20,10 @@ impl AssistantModelSelector {
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
selector: cx.new(|cx| {
|
selector: cx.new_view(|cx| {
|
||||||
let fs = fs.clone();
|
let fs = fs.clone();
|
||||||
LanguageModelSelector::new(
|
LanguageModelSelector::new(
|
||||||
move |model, cx| {
|
move |model, cx| {
|
||||||
@@ -34,7 +33,6 @@ impl AssistantModelSelector {
|
|||||||
move |settings, _cx| settings.set_model(model.clone()),
|
move |settings, _cx| settings.set_model(model.clone()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -45,13 +43,9 @@ impl AssistantModelSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AssistantModelSelector {
|
impl Render for AssistantModelSelector {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||||
let focus_handle = self.focus_handle.clone();
|
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(
|
LanguageModelSelectorPopoverMenu::new(
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
@@ -61,13 +55,23 @@ impl Render for AssistantModelSelector {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.child(
|
.child(
|
||||||
div().max_w_32().child(
|
div()
|
||||||
Label::new(model_name)
|
.overflow_x_hidden()
|
||||||
.size(LabelSize::Small)
|
.flex_grow()
|
||||||
.color(Color::Muted)
|
.whitespace_nowrap()
|
||||||
.text_ellipsis()
|
.child(match active_model {
|
||||||
.into_any_element(),
|
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(
|
.child(
|
||||||
Icon::new(IconName::ChevronDown)
|
Icon::new(IconName::ChevronDown)
|
||||||
@@ -75,14 +79,8 @@ impl Render for AssistantModelSelector {
|
|||||||
.size(IconSize::XSmall),
|
.size(IconSize::XSmall),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::for_action_in(
|
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||||
"Change Model",
|
|
||||||
&ToggleModelSelector,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with_handle(self.menu_handle.clone())
|
.with_handle(self.menu_handle.clone())
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use client::telemetry::Telemetry;
|
|||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||||
use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, StreamExt};
|
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::{Buffer, IndentKind, Point, TransactionId};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||||
@@ -32,14 +32,14 @@ use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
|||||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||||
|
|
||||||
pub struct BufferCodegen {
|
pub struct BufferCodegen {
|
||||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
alternatives: Vec<Model<CodegenAlternative>>,
|
||||||
pub active_alternative: usize,
|
pub active_alternative: usize,
|
||||||
seen_alternatives: HashSet<usize>,
|
seen_alternatives: HashSet<usize>,
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
buffer: Entity<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
initial_transaction_id: Option<TransactionId>,
|
initial_transaction_id: Option<TransactionId>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
pub is_insertion: bool,
|
pub is_insertion: bool,
|
||||||
@@ -47,15 +47,15 @@ pub struct BufferCodegen {
|
|||||||
|
|
||||||
impl BufferCodegen {
|
impl BufferCodegen {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
buffer: Entity<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
initial_transaction_id: Option<TransactionId>,
|
initial_transaction_id: Option<TransactionId>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -83,7 +83,7 @@ impl BufferCodegen {
|
|||||||
this
|
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();
|
let codegen = self.active_alternative().clone();
|
||||||
self.subscriptions.clear();
|
self.subscriptions.clear();
|
||||||
self.subscriptions
|
self.subscriptions
|
||||||
@@ -92,22 +92,22 @@ impl BufferCodegen {
|
|||||||
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
|
.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]
|
&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
|
&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)
|
LanguageModelRegistry::read_global(cx)
|
||||||
.inline_alternative_models()
|
.inline_alternative_models()
|
||||||
.len()
|
.len()
|
||||||
+ 1
|
+ 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 {
|
let next_active_ix = if self.active_alternative == 0 {
|
||||||
self.alternatives.len() - 1
|
self.alternatives.len() - 1
|
||||||
} else {
|
} else {
|
||||||
@@ -116,12 +116,12 @@ impl BufferCodegen {
|
|||||||
self.activate(next_active_ix, cx);
|
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();
|
let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
|
||||||
self.activate(next_active_ix, cx);
|
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()
|
self.active_alternative()
|
||||||
.update(cx, |codegen, cx| codegen.set_active(false, cx));
|
.update(cx, |codegen, cx| codegen.set_active(false, cx));
|
||||||
self.seen_alternatives.insert(index);
|
self.seen_alternatives.insert(index);
|
||||||
@@ -132,7 +132,7 @@ impl BufferCodegen {
|
|||||||
cx.notify();
|
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)
|
let alternative_models = LanguageModelRegistry::read_global(cx)
|
||||||
.inline_alternative_models()
|
.inline_alternative_models()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
@@ -143,7 +143,7 @@ impl BufferCodegen {
|
|||||||
self.alternatives.truncate(1);
|
self.alternatives.truncate(1);
|
||||||
|
|
||||||
for _ in 0..alternative_models.len() {
|
for _ in 0..alternative_models.len() {
|
||||||
self.alternatives.push(cx.new(|cx| {
|
self.alternatives.push(cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
self.buffer.clone(),
|
self.buffer.clone(),
|
||||||
self.range.clone(),
|
self.range.clone(),
|
||||||
@@ -172,13 +172,13 @@ impl BufferCodegen {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
for codegen in &self.alternatives {
|
for codegen in &self.alternatives {
|
||||||
codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
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()
|
self.active_alternative()
|
||||||
.update(cx, |codegen, cx| codegen.undo(cx));
|
.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()
|
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()
|
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()
|
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
|
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
|
&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()
|
self.active_alternative().read(cx).last_equal_ranges()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,8 +218,8 @@ impl BufferCodegen {
|
|||||||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||||
|
|
||||||
pub struct CodegenAlternative {
|
pub struct CodegenAlternative {
|
||||||
buffer: Entity<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
old_buffer: Entity<Buffer>,
|
old_buffer: Model<Buffer>,
|
||||||
snapshot: MultiBufferSnapshot,
|
snapshot: MultiBufferSnapshot,
|
||||||
edit_position: Option<Anchor>,
|
edit_position: Option<Anchor>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
@@ -228,7 +228,7 @@ pub struct CodegenAlternative {
|
|||||||
status: CodegenStatus,
|
status: CodegenStatus,
|
||||||
generation: Task<()>,
|
generation: Task<()>,
|
||||||
diff: Diff,
|
diff: Diff,
|
||||||
context_store: Option<Entity<ContextStore>>,
|
context_store: Option<Model<ContextStore>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
_subscription: gpui::Subscription,
|
_subscription: gpui::Subscription,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
@@ -245,27 +245,27 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
|||||||
|
|
||||||
impl CodegenAlternative {
|
impl CodegenAlternative {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
buffer: Entity<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
active: bool,
|
active: bool,
|
||||||
context_store: Option<Entity<ContextStore>>,
|
context_store: Option<Model<ContextStore>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let snapshot = buffer.read(cx).snapshot(cx);
|
let snapshot = buffer.read(cx).snapshot(cx);
|
||||||
|
|
||||||
let (old_buffer, _, _) = snapshot
|
let (old_excerpt, _) = snapshot
|
||||||
.range_to_buffer_ranges(range.clone())
|
.range_to_buffer_ranges(range.clone())
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let old_buffer = cx.new(|cx| {
|
let old_buffer = cx.new_model(|cx| {
|
||||||
let text = old_buffer.as_rope().clone();
|
let text = old_excerpt.buffer().as_rope().clone();
|
||||||
let line_ending = old_buffer.line_ending();
|
let line_ending = old_excerpt.buffer().line_ending();
|
||||||
let language = old_buffer.language().cloned();
|
let language = old_excerpt.buffer().language().cloned();
|
||||||
let language_registry = buffer
|
let language_registry = buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.buffer(old_buffer.remote_id())
|
.buffer(old_excerpt.buffer_id())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.language_registry();
|
.language_registry();
|
||||||
@@ -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 {
|
if active != self.active {
|
||||||
self.active = active;
|
self.active = active;
|
||||||
|
|
||||||
@@ -327,9 +327,9 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
fn handle_buffer_event(
|
fn handle_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_buffer: Entity<MultiBuffer>,
|
_buffer: Model<MultiBuffer>,
|
||||||
event: &multi_buffer::Event,
|
event: &multi_buffer::Event,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
|
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
|
||||||
if self.transformation_transaction_id == Some(*transaction_id) {
|
if self.transformation_transaction_id == Some(*transaction_id) {
|
||||||
@@ -348,7 +348,7 @@ impl CodegenAlternative {
|
|||||||
&mut self,
|
&mut self,
|
||||||
user_prompt: String,
|
user_prompt: String,
|
||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
@@ -375,7 +375,11 @@ impl CodegenAlternative {
|
|||||||
Ok(())
|
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 buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
let language = buffer.language_at(self.range.start);
|
let language = buffer.language_at(self.range.start);
|
||||||
let language_name = if let Some(language) = language.as_ref() {
|
let language_name = if let Some(language) = language.as_ref() {
|
||||||
@@ -434,7 +438,7 @@ impl CodegenAlternative {
|
|||||||
model_provider_id: String,
|
model_provider_id: String,
|
||||||
model_api_key: Option<String>,
|
model_api_key: Option<String>,
|
||||||
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
|
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let snapshot = self.snapshot.clone();
|
let snapshot = self.snapshot.clone();
|
||||||
@@ -471,7 +475,7 @@ impl CodegenAlternative {
|
|||||||
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||||
ranges
|
ranges
|
||||||
.first()
|
.first()
|
||||||
.and_then(|(buffer, _, _)| buffer.language())
|
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||||
.map(|language| language.name())
|
.map(|language| language.name())
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -692,7 +696,7 @@ impl CodegenAlternative {
|
|||||||
cx.notify();
|
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();
|
self.last_equal_ranges.clear();
|
||||||
if self.diff.is_empty() {
|
if self.diff.is_empty() {
|
||||||
self.status = CodegenStatus::Idle;
|
self.status = CodegenStatus::Idle;
|
||||||
@@ -704,7 +708,7 @@ impl CodegenAlternative {
|
|||||||
cx.notify();
|
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| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
if let Some(transaction_id) = self.transformation_transaction_id.take() {
|
if let Some(transaction_id) = self.transformation_transaction_id.take() {
|
||||||
buffer.undo_transaction(transaction_id, cx);
|
buffer.undo_transaction(transaction_id, cx);
|
||||||
@@ -716,7 +720,7 @@ impl CodegenAlternative {
|
|||||||
fn apply_edits(
|
fn apply_edits(
|
||||||
&mut self,
|
&mut self,
|
||||||
edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
|
edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
|
||||||
cx: &mut Context<CodegenAlternative>,
|
cx: &mut ModelContext<CodegenAlternative>,
|
||||||
) {
|
) {
|
||||||
let transaction = self.buffer.update(cx, |buffer, cx| {
|
let transaction = self.buffer.update(cx, |buffer, cx| {
|
||||||
// Avoid grouping assistant edits with user edits.
|
// Avoid grouping assistant edits with user edits.
|
||||||
@@ -743,7 +747,7 @@ impl CodegenAlternative {
|
|||||||
fn reapply_line_based_diff(
|
fn reapply_line_based_diff(
|
||||||
&mut self,
|
&mut self,
|
||||||
line_operations: impl IntoIterator<Item = LineOperation>,
|
line_operations: impl IntoIterator<Item = LineOperation>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let old_snapshot = self.snapshot.clone();
|
let old_snapshot = self.snapshot.clone();
|
||||||
let old_range = self.range.to_point(&old_snapshot);
|
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_snapshot = self.snapshot.clone();
|
||||||
let old_range = self.range.to_point(&old_snapshot);
|
let old_range = self.range.to_point(&old_snapshot);
|
||||||
let new_snapshot = self.buffer.read(cx).snapshot(cx);
|
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 =
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
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 range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
|
||||||
});
|
});
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -1141,14 +1146,15 @@ mod tests {
|
|||||||
le
|
le
|
||||||
}
|
}
|
||||||
"};
|
"};
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer =
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
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 range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
|
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
|
||||||
});
|
});
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -1208,14 +1214,15 @@ mod tests {
|
|||||||
" \n",
|
" \n",
|
||||||
"}\n" //
|
"}\n" //
|
||||||
);
|
);
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer =
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
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 range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
|
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
|
||||||
});
|
});
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -1275,14 +1282,14 @@ mod tests {
|
|||||||
\t}
|
\t}
|
||||||
}
|
}
|
||||||
"};
|
"};
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
let range = buffer.read_with(cx, |buffer, cx| {
|
let range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
|
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
|
||||||
});
|
});
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -1330,14 +1337,15 @@ mod tests {
|
|||||||
let x = 0;
|
let x = 0;
|
||||||
}
|
}
|
||||||
"};
|
"};
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer =
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
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 range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
|
||||||
});
|
});
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let codegen = cx.new(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
CodegenAlternative::new(
|
CodegenAlternative::new(
|
||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
@@ -1424,7 +1432,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn simulate_response_stream(
|
fn simulate_response_stream(
|
||||||
codegen: Entity<CodegenAlternative>,
|
codegen: Model<CodegenAlternative>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> mpsc::UnboundedSender<String> {
|
) -> mpsc::UnboundedSender<String> {
|
||||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::path::Path;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::{App, Entity, SharedString};
|
use gpui::{AppContext, Model, SharedString};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -63,14 +63,14 @@ impl ContextKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AssistantContext {
|
pub enum Context {
|
||||||
File(FileContext),
|
File(FileContext),
|
||||||
Directory(DirectoryContext),
|
Directory(DirectoryContext),
|
||||||
FetchedUrl(FetchedUrlContext),
|
FetchedUrl(FetchedUrlContext),
|
||||||
Thread(ThreadContext),
|
Thread(ThreadContext),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantContext {
|
impl Context {
|
||||||
pub fn id(&self) -> ContextId {
|
pub fn id(&self) -> ContextId {
|
||||||
match self {
|
match self {
|
||||||
Self::File(file) => file.id,
|
Self::File(file) => file.id,
|
||||||
@@ -107,7 +107,7 @@ pub struct FetchedUrlContext {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ThreadContext {
|
pub struct ThreadContext {
|
||||||
pub id: ContextId,
|
pub id: ContextId,
|
||||||
pub thread: Entity<Thread>,
|
pub thread: Model<Thread>,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,13 +117,13 @@ pub struct ThreadContext {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ContextBuffer {
|
pub struct ContextBuffer {
|
||||||
pub id: BufferId,
|
pub id: BufferId,
|
||||||
pub buffer: Entity<Buffer>,
|
pub buffer: Model<Buffer>,
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
pub text: SharedString,
|
pub text: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantContext {
|
impl Context {
|
||||||
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
|
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
|
||||||
match &self {
|
match &self {
|
||||||
Self::File(file_context) => file_context.snapshot(cx),
|
Self::File(file_context) => file_context.snapshot(cx),
|
||||||
Self::Directory(directory_context) => Some(directory_context.snapshot()),
|
Self::Directory(directory_context) => Some(directory_context.snapshot()),
|
||||||
@@ -134,7 +134,7 @@ impl AssistantContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FileContext {
|
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 buffer = self.context_buffer.buffer.read(cx);
|
||||||
let path = buffer_path_log_err(buffer)?;
|
let path = buffer_path_log_err(buffer)?;
|
||||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||||
@@ -221,7 +221,7 @@ impl FetchedUrlContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadContext {
|
impl ThreadContext {
|
||||||
pub fn snapshot(&self, cx: &App) -> ContextSnapshot {
|
pub fn snapshot(&self, cx: &AppContext) -> ContextSnapshot {
|
||||||
let thread = self.thread.read(cx);
|
let thread = self.thread.read(cx);
|
||||||
ContextSnapshot {
|
ContextSnapshot {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ mod thread_context_picker;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use file_context_picker::render_file_context_entry;
|
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, View, WeakModel, WeakView,
|
||||||
|
};
|
||||||
use project::ProjectPath;
|
use project::ProjectPath;
|
||||||
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
|
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
|
||||||
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
||||||
@@ -32,38 +33,33 @@ pub enum ConfirmBehavior {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum ContextPickerMode {
|
enum ContextPickerMode {
|
||||||
Default(Entity<ContextMenu>),
|
Default(View<ContextMenu>),
|
||||||
File(Entity<FileContextPicker>),
|
File(View<FileContextPicker>),
|
||||||
Directory(Entity<DirectoryContextPicker>),
|
Directory(View<DirectoryContextPicker>),
|
||||||
Fetch(Entity<FetchContextPicker>),
|
Fetch(View<FetchContextPicker>),
|
||||||
Thread(Entity<ThreadContextPicker>),
|
Thread(View<ThreadContextPicker>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct ContextPicker {
|
pub(super) struct ContextPicker {
|
||||||
mode: ContextPickerMode,
|
mode: ContextPickerMode,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextPicker {
|
impl ContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakView<Editor>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ContextPicker {
|
ContextPicker {
|
||||||
mode: ContextPickerMode::Default(ContextMenu::build(
|
mode: ContextPickerMode::Default(ContextMenu::build(cx, |menu, _cx| menu)),
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
|menu, _window, _cx| menu,
|
|
||||||
)),
|
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
@@ -72,15 +68,15 @@ impl ContextPicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn init(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
|
self.mode = ContextPickerMode::Default(self.build_menu(cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<ContextMenu> {
|
fn build_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
|
||||||
let context_picker = cx.entity().clone();
|
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 recent = self.recent_entries(cx);
|
||||||
let has_recent = !recent.is_empty();
|
let has_recent = !recent.is_empty();
|
||||||
let recent_entries = recent
|
let recent_entries = recent
|
||||||
@@ -99,7 +95,7 @@ impl ContextPicker {
|
|||||||
|
|
||||||
let menu = menu
|
let menu = menu
|
||||||
.when(has_recent, |menu| {
|
.when(has_recent, |menu| {
|
||||||
menu.custom_row(|_, _| {
|
menu.custom_row(|_| {
|
||||||
div()
|
div()
|
||||||
.mb_1()
|
.mb_1()
|
||||||
.child(
|
.child(
|
||||||
@@ -117,10 +113,8 @@ impl ContextPicker {
|
|||||||
|
|
||||||
ContextMenuEntry::new(kind.label())
|
ContextMenuEntry::new(kind.label())
|
||||||
.icon(kind.icon())
|
.icon(kind.icon())
|
||||||
.icon_size(IconSize::XSmall)
|
.handler(move |cx| {
|
||||||
.icon_color(Color::Muted)
|
context_picker.update(cx, |this, cx| this.select_kind(kind, cx))
|
||||||
.handler(move |window, cx| {
|
|
||||||
context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
|
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -143,56 +137,52 @@ impl ContextPicker {
|
|||||||
self.thread_store.is_some()
|
self.thread_store.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_kind(&mut self, kind: ContextKind, cx: &mut ViewContext<Self>) {
|
||||||
let context_picker = cx.entity().downgrade();
|
let context_picker = cx.view().downgrade();
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
ContextKind::File => {
|
ContextKind::File => {
|
||||||
self.mode = ContextPickerMode::File(cx.new(|cx| {
|
self.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||||
FileContextPicker::new(
|
FileContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.editor.clone(),
|
self.editor.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
self.confirm_behavior,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::Directory => {
|
ContextKind::Directory => {
|
||||||
self.mode = ContextPickerMode::Directory(cx.new(|cx| {
|
self.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||||
DirectoryContextPicker::new(
|
DirectoryContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
self.confirm_behavior,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::FetchedUrl => {
|
ContextKind::FetchedUrl => {
|
||||||
self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
|
self.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||||
FetchContextPicker::new(
|
FetchContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
self.confirm_behavior,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextKind::Thread => {
|
ContextKind::Thread => {
|
||||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
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(
|
ThreadContextPicker::new(
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
self.confirm_behavior,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
@@ -201,12 +191,12 @@ impl ContextPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.focus_self(window);
|
cx.focus_self();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recent_menu_item(
|
fn recent_menu_item(
|
||||||
&self,
|
&self,
|
||||||
context_picker: Entity<ContextPicker>,
|
context_picker: View<ContextPicker>,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
entry: RecentEntry,
|
entry: RecentEntry,
|
||||||
) -> ContextMenuItem {
|
) -> ContextMenuItem {
|
||||||
@@ -219,7 +209,7 @@ impl ContextPicker {
|
|||||||
let path = project_path.path.clone();
|
let path = project_path.path.clone();
|
||||||
|
|
||||||
ContextMenuItem::custom_entry(
|
ContextMenuItem::custom_entry(
|
||||||
move |_window, cx| {
|
move |cx| {
|
||||||
render_file_context_entry(
|
render_file_context_entry(
|
||||||
ElementId::NamedInteger("ctx-recent".into(), ix),
|
ElementId::NamedInteger("ctx-recent".into(), ix),
|
||||||
&path,
|
&path,
|
||||||
@@ -229,9 +219,9 @@ impl ContextPicker {
|
|||||||
)
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
},
|
},
|
||||||
move |window, cx| {
|
move |cx| {
|
||||||
context_picker.update(cx, |this, 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,14 +231,13 @@ impl ContextPicker {
|
|||||||
let view_thread = thread.clone();
|
let view_thread = thread.clone();
|
||||||
|
|
||||||
ContextMenuItem::custom_entry(
|
ContextMenuItem::custom_entry(
|
||||||
move |_window, cx| {
|
move |cx| {
|
||||||
render_thread_context_entry(&view_thread, context_store.clone(), cx)
|
render_thread_context_entry(&view_thread, context_store.clone(), cx)
|
||||||
.into_any()
|
.into_any()
|
||||||
},
|
},
|
||||||
move |_window, cx| {
|
move |cx| {
|
||||||
context_picker.update(cx, |this, cx| {
|
context_picker.update(cx, |this, cx| {
|
||||||
this.add_recent_thread(thread.clone(), cx)
|
this.add_recent_thread(thread.clone(), cx);
|
||||||
.detach_and_log_err(cx);
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -256,12 +245,7 @@ impl ContextPicker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_recent_file(
|
fn add_recent_file(&self, project_path: ProjectPath, cx: &mut ViewContext<Self>) {
|
||||||
&self,
|
|
||||||
project_path: ProjectPath,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let Some(context_store) = self.context_store.upgrade() else {
|
let Some(context_store) = self.context_store.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -270,43 +254,34 @@ impl ContextPicker {
|
|||||||
context_store.add_file_from_path(project_path.clone(), cx)
|
context_store.add_file_from_path(project_path.clone(), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, |_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move { task.await.notify_async_err(&mut cx) })
|
||||||
task.await.notify_async_err(&mut cx)
|
.detach();
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_recent_thread(
|
fn add_recent_thread(&self, thread: ThreadContextEntry, cx: &mut ViewContext<Self>) {
|
||||||
&self,
|
|
||||||
thread: ThreadContextEntry,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
let Some(context_store) = self.context_store.upgrade() else {
|
let Some(context_store) = self.context_store.upgrade() else {
|
||||||
return Task::ready(Err(anyhow!("context store not available")));
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(thread_store) = self
|
let Some(thread) = self
|
||||||
.thread_store
|
.thread_store
|
||||||
.as_ref()
|
.clone()
|
||||||
.and_then(|thread_store| thread_store.upgrade())
|
.and_then(|this| this.upgrade())
|
||||||
|
.and_then(|this| this.update(cx, |this, cx| this.open_thread(&thread.id, cx)))
|
||||||
else {
|
else {
|
||||||
return Task::ready(Err(anyhow!("thread store not available")));
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&thread.id, cx));
|
context_store.update(cx, |context_store, cx| {
|
||||||
cx.spawn(|this, mut cx| async move {
|
context_store.add_thread(thread, cx);
|
||||||
let thread = open_thread_task.await?;
|
});
|
||||||
context_store.update(&mut cx, |context_store, cx| {
|
|
||||||
context_store.add_thread(thread, cx);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |_this, cx| cx.notify())
|
cx.notify();
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
@@ -357,17 +332,19 @@ impl ContextPicker {
|
|||||||
return recent;
|
return recent;
|
||||||
};
|
};
|
||||||
|
|
||||||
thread_store.update(cx, |thread_store, _cx| {
|
thread_store.update(cx, |thread_store, cx| {
|
||||||
recent.extend(
|
recent.extend(
|
||||||
thread_store
|
thread_store
|
||||||
.threads()
|
.threads(cx)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|thread| !current_threads.contains(&thread.id))
|
.filter(|thread| !current_threads.contains(thread.read(cx).id()))
|
||||||
.take(2)
|
.take(2)
|
||||||
.map(|thread| {
|
.map(|thread| {
|
||||||
|
let thread = thread.read(cx);
|
||||||
|
|
||||||
RecentEntry::Thread(ThreadContextEntry {
|
RecentEntry::Thread(ThreadContextEntry {
|
||||||
id: thread.id,
|
id: thread.id().clone(),
|
||||||
summary: thread.summary,
|
summary: thread.summary_or_default(),
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -376,7 +353,7 @@ impl ContextPicker {
|
|||||||
recent
|
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 active_item = workspace.active_item(cx)?;
|
||||||
|
|
||||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||||
@@ -389,8 +366,8 @@ impl ContextPicker {
|
|||||||
|
|
||||||
impl EventEmitter<DismissEvent> for ContextPicker {}
|
impl EventEmitter<DismissEvent> for ContextPicker {}
|
||||||
|
|
||||||
impl Focusable for ContextPicker {
|
impl FocusableView for ContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
|
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
|
||||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
||||||
@@ -402,7 +379,7 @@ impl Focusable for ContextPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render 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()
|
v_flex()
|
||||||
.w(px(400.))
|
.w(px(400.))
|
||||||
.min_w(px(400.))
|
.min_w(px(400.))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use fuzzy::PathMatch;
|
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 picker::{Picker, PickerDelegate};
|
||||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||||
use ui::{prelude::*, ListItem};
|
use ui::{prelude::*, ListItem};
|
||||||
@@ -14,17 +14,16 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
|||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
|
|
||||||
pub struct DirectoryContextPicker {
|
pub struct DirectoryContextPicker {
|
||||||
picker: Entity<Picker<DirectoryContextPickerDelegate>>,
|
picker: View<Picker<DirectoryContextPickerDelegate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectoryContextPicker {
|
impl DirectoryContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = DirectoryContextPickerDelegate::new(
|
let delegate = DirectoryContextPickerDelegate::new(
|
||||||
context_picker,
|
context_picker,
|
||||||
@@ -32,28 +31,28 @@ impl DirectoryContextPicker {
|
|||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
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 }
|
Self { picker }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for DirectoryContextPicker {
|
impl FocusableView for DirectoryContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.picker.focus_handle(cx)
|
self.picker.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for DirectoryContextPicker {
|
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()
|
self.picker.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DirectoryContextPickerDelegate {
|
pub struct DirectoryContextPickerDelegate {
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
matches: Vec<PathMatch>,
|
matches: Vec<PathMatch>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
@@ -61,9 +60,9 @@ pub struct DirectoryContextPickerDelegate {
|
|||||||
|
|
||||||
impl DirectoryContextPickerDelegate {
|
impl DirectoryContextPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -80,8 +79,8 @@ impl DirectoryContextPickerDelegate {
|
|||||||
&mut self,
|
&mut self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: &Entity<Workspace>,
|
workspace: &View<Workspace>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Task<Vec<PathMatch>> {
|
) -> Task<Vec<PathMatch>> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
let workspace = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
@@ -147,25 +146,15 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||||||
self.selected_index
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(
|
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||||
&mut self,
|
|
||||||
ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
self.selected_index = ix;
|
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()
|
"Search folders…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return Task::ready(());
|
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 {
|
let Some(mat) = self.matches.get(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -205,19 +194,19 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let confirm_behavior = self.confirm_behavior;
|
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) {
|
match task.await.notify_async_err(&mut cx) {
|
||||||
None => anyhow::Ok(()),
|
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::KeepOpen => {}
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach_and_log_err(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
|
self.context_picker
|
||||||
.update(cx, |_, cx| {
|
.update(cx, |_, cx| {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
@@ -229,8 +218,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_window: &mut Window,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let path_match = &self.matches[ix];
|
let path_match = &self.matches[ix];
|
||||||
let directory_name = path_match.path.to_string_lossy().to_string();
|
let directory_name = path_match.path.to_string_lossy().to_string();
|
||||||
@@ -246,12 +234,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.start_slot(
|
.child(h_flex().gap_2().child(Label::new(directory_name)))
|
||||||
Icon::new(IconName::Folder)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(Label::new(directory_name))
|
|
||||||
.when(added, |el| {
|
.when(added, |el| {
|
||||||
el.end_slot(
|
el.end_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
@@ -4,28 +4,27 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use futures::AsyncReadExt as _;
|
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 html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use ui::{prelude::*, Context, ListItem, Window};
|
use ui::{prelude::*, ListItem, ViewContext};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
|
|
||||||
pub struct FetchContextPicker {
|
pub struct FetchContextPicker {
|
||||||
picker: Entity<Picker<FetchContextPickerDelegate>>,
|
picker: View<Picker<FetchContextPickerDelegate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchContextPicker {
|
impl FetchContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = FetchContextPickerDelegate::new(
|
let delegate = FetchContextPickerDelegate::new(
|
||||||
context_picker,
|
context_picker,
|
||||||
@@ -33,20 +32,20 @@ impl FetchContextPicker {
|
|||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
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 }
|
Self { picker }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for FetchContextPicker {
|
impl FocusableView for FetchContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.picker.focus_handle(cx)
|
self.picker.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for FetchContextPicker {
|
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()
|
self.picker.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,18 +58,18 @@ enum ContentType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FetchContextPickerDelegate {
|
pub struct FetchContextPickerDelegate {
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchContextPickerDelegate {
|
impl FetchContextPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
FetchContextPickerDelegate {
|
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()
|
"Enter the URL that you would like to fetch".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,30 +174,19 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(
|
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||||
&mut self,
|
|
||||||
_ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<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()
|
"Enter a URL…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
self.url = query;
|
self.url = query;
|
||||||
|
|
||||||
Task::ready(())
|
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 {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -206,13 +194,13 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
let http_client = workspace.read(cx).client().http_client().clone();
|
let http_client = workspace.read(cx).client().http_client().clone();
|
||||||
let url = self.url.clone();
|
let url = self.url.clone();
|
||||||
let confirm_behavior = self.confirm_behavior;
|
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
|
let text = cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
.spawn(Self::build_message(http_client, url.clone()))
|
.spawn(Self::build_message(http_client, url.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.delegate
|
this.delegate
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, _cx| {
|
.update(cx, |context_store, _cx| {
|
||||||
@@ -221,7 +209,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
|
|
||||||
match confirm_behavior {
|
match confirm_behavior {
|
||||||
ConfirmBehavior::KeepOpen => {}
|
ConfirmBehavior::KeepOpen => {}
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
@@ -232,7 +220,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
.detach_and_log_err(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
|
self.context_picker
|
||||||
.update(cx, |_, cx| {
|
.update(cx, |_, cx| {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
@@ -244,8 +232,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_window: &mut Window,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
||||||
context_store.read(cx).includes_url(&self.url).is_some()
|
context_store.read(cx).includes_url(&self.url).is_some()
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
|
|||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
|
AnyElement, AppContext, DismissEvent, Empty, FocusHandle, FocusableView, Stateful, Task, View,
|
||||||
WeakEntity,
|
WeakModel, WeakView,
|
||||||
};
|
};
|
||||||
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
@@ -27,18 +27,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
|||||||
use crate::context_store::{ContextStore, FileInclusion};
|
use crate::context_store::{ContextStore, FileInclusion};
|
||||||
|
|
||||||
pub struct FileContextPicker {
|
pub struct FileContextPicker {
|
||||||
picker: Entity<Picker<FileContextPickerDelegate>>,
|
picker: View<Picker<FileContextPickerDelegate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileContextPicker {
|
impl FileContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = FileContextPickerDelegate::new(
|
let delegate = FileContextPickerDelegate::new(
|
||||||
context_picker,
|
context_picker,
|
||||||
@@ -47,29 +46,29 @@ impl FileContextPicker {
|
|||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
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 }
|
Self { picker }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for FileContextPicker {
|
impl FocusableView for FileContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.picker.focus_handle(cx)
|
self.picker.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for FileContextPicker {
|
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()
|
self.picker.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileContextPickerDelegate {
|
pub struct FileContextPickerDelegate {
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
matches: Vec<PathMatch>,
|
matches: Vec<PathMatch>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
@@ -77,10 +76,10 @@ pub struct FileContextPickerDelegate {
|
|||||||
|
|
||||||
impl FileContextPickerDelegate {
|
impl FileContextPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -98,9 +97,8 @@ impl FileContextPickerDelegate {
|
|||||||
&mut self,
|
&mut self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: &Entity<Workspace>,
|
workspace: &View<Workspace>,
|
||||||
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<Vec<PathMatch>> {
|
) -> Task<Vec<PathMatch>> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
let workspace = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
@@ -124,7 +122,7 @@ impl FileContextPickerDelegate {
|
|||||||
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
let file_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
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.,
|
score: 0.,
|
||||||
positions: Vec::new(),
|
positions: Vec::new(),
|
||||||
worktree_id: worktree.id().to_usize(),
|
worktree_id: worktree.id().to_usize(),
|
||||||
@@ -182,32 +180,22 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
self.selected_index
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(
|
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||||
&mut self,
|
|
||||||
ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
self.selected_index = ix;
|
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()
|
"Search files…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
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.
|
// TODO: This should be probably be run in the background.
|
||||||
let paths = search_task.await;
|
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 {
|
let Some(mat) = self.matches.get(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -243,7 +231,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
};
|
};
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
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.
|
// 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);
|
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| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||||
s.select(selections)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_anchors = {
|
let start_anchors = {
|
||||||
@@ -274,7 +260,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
editor.insert(&full_path, window, cx);
|
editor.insert(&full_path, cx);
|
||||||
|
|
||||||
let end_anchors = {
|
let end_anchors = {
|
||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
@@ -286,15 +272,14 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
editor.insert("\n", window, cx); // Needed to end the fold
|
editor.insert("\n", cx); // Needed to end the fold
|
||||||
|
|
||||||
let placeholder = FoldPlaceholder {
|
let placeholder = FoldPlaceholder {
|
||||||
render: render_fold_icon_button(IconName::File, file_name.into()),
|
render: render_fold_icon_button(IconName::File, file_name.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let render_trailer =
|
let render_trailer = move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||||
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
|
||||||
|
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
let mut rows_to_fold = BTreeSet::new();
|
let mut rows_to_fold = BTreeSet::new();
|
||||||
@@ -315,7 +300,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
editor.insert_creases(crease_iter, cx);
|
editor.insert_creases(crease_iter, cx);
|
||||||
|
|
||||||
for buffer_row in rows_to_fold {
|
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;
|
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) {
|
match task.await.notify_async_err(&mut cx) {
|
||||||
None => anyhow::Ok(()),
|
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::KeepOpen => {}
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach_and_log_err(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
|
self.context_picker
|
||||||
.update(cx, |_, cx| {
|
.update(cx, |_, cx| {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
@@ -355,8 +340,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_window: &mut Window,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let path_match = &self.matches[ix];
|
let path_match = &self.matches[ix];
|
||||||
|
|
||||||
@@ -379,8 +363,8 @@ pub fn render_file_context_entry(
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
path_prefix: &Arc<str>,
|
path_prefix: &Arc<str>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
cx: &App,
|
cx: &WindowContext,
|
||||||
) -> Stateful<Div> {
|
) -> Stateful<Div> {
|
||||||
let (file_name, directory) = if path == Path::new("") {
|
let (file_name, directory) = if path == Path::new("") {
|
||||||
(SharedString::from(path_prefix.clone()), None)
|
(SharedString::from(path_prefix.clone()), None)
|
||||||
@@ -412,12 +396,12 @@ pub fn render_file_context_entry(
|
|||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(id)
|
.id(id)
|
||||||
.gap_1p5()
|
.gap_1()
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(file_icon.size(IconSize::Small).color(Color::Muted))
|
.child(file_icon.size(IconSize::Small))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_2()
|
||||||
.child(Label::new(file_name))
|
.child(Label::new(file_name))
|
||||||
.children(directory.map(|directory| {
|
.children(directory.map(|directory| {
|
||||||
Label::new(directory)
|
Label::new(directory)
|
||||||
@@ -425,12 +409,11 @@ pub fn render_file_context_entry(
|
|||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
.child(div().w_full())
|
||||||
.when_some(added, |el, added| match added {
|
.when_some(added, |el, added| match added {
|
||||||
FileInclusion::Direct(_) => el.child(
|
FileInclusion::Direct(_) => el.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.gap_1()
|
||||||
.justify_end()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::Check)
|
Icon::new(IconName::Check)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -443,9 +426,7 @@ pub fn render_file_context_entry(
|
|||||||
|
|
||||||
el.child(
|
el.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.gap_1()
|
||||||
.justify_end()
|
|
||||||
.gap_0p5()
|
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::Check)
|
Icon::new(IconName::Check)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -453,7 +434,7 @@ pub fn render_file_context_entry(
|
|||||||
)
|
)
|
||||||
.child(Label::new("Included").size(LabelSize::Small)),
|
.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 +442,8 @@ pub fn render_file_context_entry(
|
|||||||
fn render_fold_icon_button(
|
fn render_fold_icon_button(
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
|
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
|
||||||
Arc::new(move |fold_id, _fold_range, _window, _cx| {
|
Arc::new(move |fold_id, _fold_range, _cx| {
|
||||||
ButtonLike::new(fold_id)
|
ButtonLike::new(fold_id)
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
@@ -477,14 +458,13 @@ fn fold_toggle(
|
|||||||
) -> impl Fn(
|
) -> impl Fn(
|
||||||
MultiBufferRow,
|
MultiBufferRow,
|
||||||
bool,
|
bool,
|
||||||
Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
|
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||||
&mut Window,
|
&mut WindowContext,
|
||||||
&mut App,
|
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
move |row, is_folded, fold, _window, _cx| {
|
move |row, is_folded, fold, _cx| {
|
||||||
Disclosure::new((name, row.0 as u64), !is_folded)
|
Disclosure::new((name, row.0 as u64), !is_folded)
|
||||||
.toggle_state(is_folded)
|
.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()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use fuzzy::StringMatchCandidate;
|
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 picker::{Picker, PickerDelegate};
|
||||||
use ui::{prelude::*, ListItem};
|
use ui::{prelude::*, ListItem};
|
||||||
|
|
||||||
@@ -11,17 +11,16 @@ use crate::thread::ThreadId;
|
|||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
|
|
||||||
pub struct ThreadContextPicker {
|
pub struct ThreadContextPicker {
|
||||||
picker: Entity<Picker<ThreadContextPickerDelegate>>,
|
picker: View<Picker<ThreadContextPickerDelegate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadContextPicker {
|
impl ThreadContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakModel<ThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakModel<context_store::ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate = ThreadContextPickerDelegate::new(
|
let delegate = ThreadContextPickerDelegate::new(
|
||||||
thread_store,
|
thread_store,
|
||||||
@@ -29,20 +28,20 @@ impl ThreadContextPicker {
|
|||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
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 }
|
ThreadContextPicker { picker }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for ThreadContextPicker {
|
impl FocusableView for ThreadContextPicker {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.picker.focus_handle(cx)
|
self.picker.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ThreadContextPicker {
|
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()
|
self.picker.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,9 +53,9 @@ pub struct ThreadContextEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThreadContextPickerDelegate {
|
pub struct ThreadContextPickerDelegate {
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakModel<ThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakModel<context_store::ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
matches: Vec<ThreadContextEntry>,
|
matches: Vec<ThreadContextEntry>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
@@ -64,9 +63,9 @@ pub struct ThreadContextPickerDelegate {
|
|||||||
|
|
||||||
impl ThreadContextPickerDelegate {
|
impl ThreadContextPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakModel<ThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakModel<context_store::ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ThreadContextPickerDelegate {
|
ThreadContextPickerDelegate {
|
||||||
@@ -91,31 +90,22 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
self.selected_index
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(
|
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||||
&mut self,
|
|
||||||
ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
self.selected_index = ix;
|
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()
|
"Search threads…".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
&mut self,
|
let Ok(threads) = self.thread_store.update(cx, |this, cx| {
|
||||||
query: String,
|
this.threads(cx)
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let Ok(threads) = self.thread_store.update(cx, |this, _cx| {
|
|
||||||
this.threads()
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|thread| ThreadContextEntry {
|
.map(|thread| {
|
||||||
id: thread.id,
|
let id = thread.read(cx).id().clone();
|
||||||
summary: thread.summary,
|
let summary = thread.read(cx).summary_or_default();
|
||||||
|
ThreadContextEntry { id, summary }
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}) else {
|
}) else {
|
||||||
@@ -149,7 +139,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;
|
let matches = search_task.await;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.delegate.matches = matches;
|
this.delegate.matches = matches;
|
||||||
@@ -160,7 +150,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 {
|
let Some(entry) = self.matches.get(self.selected_index) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -169,26 +159,22 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
let Some(thread) = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
cx.spawn_in(window, |this, mut cx| async move {
|
self.context_store
|
||||||
let thread = open_thread_task.await?;
|
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
.ok();
|
||||||
this.delegate
|
|
||||||
.context_store
|
|
||||||
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
match this.delegate.confirm_behavior {
|
match self.confirm_behavior {
|
||||||
ConfirmBehavior::KeepOpen => {}
|
ConfirmBehavior::KeepOpen => {}
|
||||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
ConfirmBehavior::Close => self.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
|
self.context_picker
|
||||||
.update(cx, |_, cx| {
|
.update(cx, |_, cx| {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
@@ -200,8 +186,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_window: &mut Window,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let thread = &self.matches[ix];
|
let thread = &self.matches[ix];
|
||||||
|
|
||||||
@@ -213,21 +198,17 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||||||
|
|
||||||
pub fn render_thread_context_entry(
|
pub fn render_thread_context_entry(
|
||||||
thread: &ThreadContextEntry,
|
thread: &ThreadContextEntry,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
cx: &mut App,
|
cx: &mut WindowContext,
|
||||||
) -> Div {
|
) -> Div {
|
||||||
let added = context_store.upgrade().map_or(false, |ctx_store| {
|
let added = context_store.upgrade().map_or(false, |ctx_store| {
|
||||||
ctx_store.read(cx).includes_thread(&thread.id).is_some()
|
ctx_store.read(cx).includes_thread(&thread.id).is_some()
|
||||||
});
|
});
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_1()
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(
|
.child(Icon::new(IconName::MessageCircle).size(IconSize::Small))
|
||||||
Icon::new(IconName::MessageCircle)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.child(Label::new(thread.summary.clone()))
|
.child(Label::new(thread.summary.clone()))
|
||||||
.child(div().w_full())
|
.child(div().w_full())
|
||||||
.when(added, |el| {
|
.when(added, |el| {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use futures::{self, future, Future, FutureExt};
|
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 language::Buffer;
|
||||||
use project::{ProjectPath, Worktree};
|
use project::{ProjectPath, Worktree};
|
||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
@@ -12,15 +12,15 @@ use text::BufferId;
|
|||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::context::{
|
use crate::context::{
|
||||||
AssistantContext, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext,
|
Context, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext, FetchedUrlContext,
|
||||||
FetchedUrlContext, FileContext, ThreadContext,
|
FileContext, ThreadContext,
|
||||||
};
|
};
|
||||||
use crate::context_strip::SuggestedContext;
|
use crate::context_strip::SuggestedContext;
|
||||||
use crate::thread::{Thread, ThreadId};
|
use crate::thread::{Thread, ThreadId};
|
||||||
|
|
||||||
pub struct ContextStore {
|
pub struct ContextStore {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context: Vec<AssistantContext>,
|
context: Vec<Context>,
|
||||||
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
|
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
|
||||||
next_context_id: ContextId,
|
next_context_id: ContextId,
|
||||||
files: BTreeMap<BufferId, ContextId>,
|
files: BTreeMap<BufferId, ContextId>,
|
||||||
@@ -30,7 +30,7 @@ pub struct ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContextStore {
|
impl ContextStore {
|
||||||
pub fn new(workspace: WeakEntity<Workspace>) -> Self {
|
pub fn new(workspace: WeakView<Workspace>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
workspace,
|
workspace,
|
||||||
context: Vec::new(),
|
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()
|
self.context()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|context| context.snapshot(cx))
|
.flat_map(|context| context.snapshot(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context(&self) -> &Vec<AssistantContext> {
|
pub fn context(&self) -> &Vec<Context> {
|
||||||
&self.context
|
&self.context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ impl ContextStore {
|
|||||||
pub fn add_file_from_path(
|
pub fn add_file_from_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
|
|
||||||
@@ -79,8 +82,8 @@ impl ContextStore {
|
|||||||
project.open_buffer(project_path.clone(), cx)
|
project.open_buffer(project_path.clone(), cx)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let buffer_entity = open_buffer_task.await?;
|
let buffer_model = open_buffer_task.await?;
|
||||||
let buffer_id = this.update(&mut cx, |_, cx| buffer_entity.read(cx).remote_id())?;
|
let buffer_id = this.update(&mut cx, |_, cx| buffer_model.read(cx).remote_id())?;
|
||||||
|
|
||||||
let already_included = this.update(&mut cx, |this, _cx| {
|
let already_included = this.update(&mut cx, |this, _cx| {
|
||||||
match this.will_include_buffer(buffer_id, &project_path.path) {
|
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_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(
|
collect_buffer_info_and_text(
|
||||||
project_path.path.clone(),
|
project_path.path.clone(),
|
||||||
buffer_entity,
|
buffer_model,
|
||||||
buffer,
|
buffer,
|
||||||
cx.to_async(),
|
cx.to_async(),
|
||||||
)
|
)
|
||||||
@@ -119,18 +122,18 @@ impl ContextStore {
|
|||||||
|
|
||||||
pub fn add_file_from_buffer(
|
pub fn add_file_from_buffer(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer_entity: Entity<Buffer>,
|
buffer_model: Model<Buffer>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
|
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 {
|
let Some(file) = buffer.file() else {
|
||||||
return Err(anyhow!("Buffer has no path."));
|
return Err(anyhow!("Buffer has no path."));
|
||||||
};
|
};
|
||||||
Ok(collect_buffer_info_and_text(
|
Ok(collect_buffer_info_and_text(
|
||||||
file.path().clone(),
|
file.path().clone(),
|
||||||
buffer_entity,
|
buffer_model,
|
||||||
buffer,
|
buffer,
|
||||||
cx.to_async(),
|
cx.to_async(),
|
||||||
))
|
))
|
||||||
@@ -150,13 +153,13 @@ impl ContextStore {
|
|||||||
let id = self.next_context_id.post_inc();
|
let id = self.next_context_id.post_inc();
|
||||||
self.files.insert(context_buffer.id, id);
|
self.files.insert(context_buffer.id, id);
|
||||||
self.context
|
self.context
|
||||||
.push(AssistantContext::File(FileContext { id, context_buffer }));
|
.push(Context::File(FileContext { id, context_buffer }));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_directory(
|
pub fn add_directory(
|
||||||
&mut self,
|
&mut self,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let Some(project) = workspace
|
let Some(project) = workspace
|
||||||
@@ -207,11 +210,11 @@ impl ContextStore {
|
|||||||
let mut buffer_infos = Vec::new();
|
let mut buffer_infos = Vec::new();
|
||||||
let mut text_tasks = Vec::new();
|
let mut text_tasks = Vec::new();
|
||||||
this.update(&mut cx, |_, cx| {
|
this.update(&mut cx, |_, cx| {
|
||||||
for (path, buffer_entity) in files.into_iter().zip(buffers) {
|
for (path, buffer_model) in files.into_iter().zip(buffers) {
|
||||||
let buffer_entity = buffer_entity?;
|
let buffer_model = buffer_model?;
|
||||||
let buffer = buffer_entity.read(cx);
|
let buffer = buffer_model.read(cx);
|
||||||
let (buffer_info, text_task) =
|
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);
|
buffer_infos.push(buffer_info);
|
||||||
text_tasks.push(text_task);
|
text_tasks.push(text_task);
|
||||||
}
|
}
|
||||||
@@ -241,15 +244,14 @@ impl ContextStore {
|
|||||||
let id = self.next_context_id.post_inc();
|
let id = self.next_context_id.post_inc();
|
||||||
self.directories.insert(path.to_path_buf(), id);
|
self.directories.insert(path.to_path_buf(), id);
|
||||||
|
|
||||||
self.context
|
self.context.push(Context::Directory(DirectoryContext::new(
|
||||||
.push(AssistantContext::Directory(DirectoryContext::new(
|
id,
|
||||||
id,
|
path,
|
||||||
path,
|
context_buffers,
|
||||||
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()) {
|
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
|
||||||
self.remove_context(context_id);
|
self.remove_context(context_id);
|
||||||
} else {
|
} 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 id = self.next_context_id.post_inc();
|
||||||
let text = thread.read(cx).text().into();
|
let text = thread.read(cx).text().into();
|
||||||
|
|
||||||
self.threads.insert(thread.read(cx).id().clone(), id);
|
self.threads.insert(thread.read(cx).id().clone(), id);
|
||||||
self.context
|
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>) {
|
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();
|
let id = self.next_context_id.post_inc();
|
||||||
|
|
||||||
self.fetched_urls.insert(url.clone(), id);
|
self.fetched_urls.insert(url.clone(), id);
|
||||||
self.context
|
self.context.push(Context::FetchedUrl(FetchedUrlContext {
|
||||||
.push(AssistantContext::FetchedUrl(FetchedUrlContext {
|
id,
|
||||||
id,
|
url: url.into(),
|
||||||
url: url.into(),
|
text: text.into(),
|
||||||
text: text.into(),
|
}));
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn accept_suggested_context(
|
pub fn accept_suggested_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
suggested: &SuggestedContext,
|
suggested: &SuggestedContext,
|
||||||
cx: &mut Context<ContextStore>,
|
cx: &mut ModelContext<ContextStore>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
match suggested {
|
match suggested {
|
||||||
SuggestedContext::File {
|
SuggestedContext::File {
|
||||||
@@ -314,16 +315,16 @@ impl ContextStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match self.context.remove(ix) {
|
match self.context.remove(ix) {
|
||||||
AssistantContext::File(_) => {
|
Context::File(_) => {
|
||||||
self.files.retain(|_, context_id| *context_id != id);
|
self.files.retain(|_, context_id| *context_id != id);
|
||||||
}
|
}
|
||||||
AssistantContext::Directory(_) => {
|
Context::Directory(_) => {
|
||||||
self.directories.retain(|_, context_id| *context_id != id);
|
self.directories.retain(|_, context_id| *context_id != id);
|
||||||
}
|
}
|
||||||
AssistantContext::FetchedUrl(_) => {
|
Context::FetchedUrl(_) => {
|
||||||
self.fetched_urls.retain(|_, context_id| *context_id != id);
|
self.fetched_urls.retain(|_, context_id| *context_id != id);
|
||||||
}
|
}
|
||||||
AssistantContext::Thread(_) => {
|
Context::Thread(_) => {
|
||||||
self.threads.retain(|_, context_id| *context_id != id);
|
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
|
/// Returns whether this file path is already included directly in the context, or if it will be
|
||||||
/// included in the context via a directory.
|
/// 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() {
|
if !self.files.is_empty() {
|
||||||
let found_file_context = self.context.iter().find(|context| match &context {
|
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);
|
let buffer = file_context.context_buffer.buffer.read(cx);
|
||||||
if let Some(file_path) = buffer_path_log_err(buffer) {
|
if let Some(file_path) = buffer_path_log_err(buffer) {
|
||||||
*file_path == *path
|
*file_path == *path
|
||||||
@@ -392,7 +393,7 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the context that matches the ID of the new context, if any match.
|
/// 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();
|
let id = new_context.id();
|
||||||
for context in self.context.iter_mut() {
|
for context in self.context.iter_mut() {
|
||||||
if context.id() == id {
|
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
|
self.context
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|context| match context {
|
.filter_map(|context| match context {
|
||||||
AssistantContext::File(file) => {
|
Context::File(file) => {
|
||||||
let buffer = file.context_buffer.buffer.read(cx);
|
let buffer = file.context_buffer.buffer.read(cx);
|
||||||
buffer_path_log_err(buffer).map(|p| p.to_path_buf())
|
buffer_path_log_err(buffer).map(|p| p.to_path_buf())
|
||||||
}
|
}
|
||||||
AssistantContext::Directory(_)
|
Context::Directory(_) | Context::FetchedUrl(_) | Context::Thread(_) => None,
|
||||||
| AssistantContext::FetchedUrl(_)
|
|
||||||
| AssistantContext::Thread(_) => None,
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -429,7 +428,7 @@ pub enum FileInclusion {
|
|||||||
|
|
||||||
// ContextBuffer without text.
|
// ContextBuffer without text.
|
||||||
struct BufferInfo {
|
struct BufferInfo {
|
||||||
buffer_entity: Entity<Buffer>,
|
buffer_model: Model<Buffer>,
|
||||||
id: BufferId,
|
id: BufferId,
|
||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
}
|
}
|
||||||
@@ -437,7 +436,7 @@ struct BufferInfo {
|
|||||||
fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
|
fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
|
||||||
ContextBuffer {
|
ContextBuffer {
|
||||||
id: info.id,
|
id: info.id,
|
||||||
buffer: info.buffer_entity,
|
buffer: info.buffer_model,
|
||||||
version: info.version,
|
version: info.version,
|
||||||
text,
|
text,
|
||||||
}
|
}
|
||||||
@@ -445,13 +444,13 @@ fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
|
|||||||
|
|
||||||
fn collect_buffer_info_and_text(
|
fn collect_buffer_info_and_text(
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
buffer_entity: Entity<Buffer>,
|
buffer_model: Model<Buffer>,
|
||||||
buffer: &Buffer,
|
buffer: &Buffer,
|
||||||
cx: AsyncApp,
|
cx: AsyncAppContext,
|
||||||
) -> (BufferInfo, Task<SharedString>) {
|
) -> (BufferInfo, Task<SharedString>) {
|
||||||
let buffer_info = BufferInfo {
|
let buffer_info = BufferInfo {
|
||||||
id: buffer.remote_id(),
|
id: buffer.remote_id(),
|
||||||
buffer_entity,
|
buffer_model,
|
||||||
version: buffer.version(),
|
version: buffer.version(),
|
||||||
};
|
};
|
||||||
// Important to collect version at the same time as content so that staleness logic is correct.
|
// 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(
|
pub fn refresh_context_store_text(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> impl Future<Output = ()> {
|
) -> impl Future<Output = ()> {
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
for context in &context_store.read(cx).context {
|
for context in &context_store.read(cx).context {
|
||||||
match context {
|
match context {
|
||||||
AssistantContext::File(file_context) => {
|
Context::File(file_context) => {
|
||||||
let context_store = context_store.clone();
|
let context_store = context_store.clone();
|
||||||
if let Some(task) = refresh_file_text(context_store, file_context, cx) {
|
if let Some(task) = refresh_file_text(context_store, file_context, cx) {
|
||||||
tasks.push(task);
|
tasks.push(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssistantContext::Directory(directory_context) => {
|
Context::Directory(directory_context) => {
|
||||||
let context_store = context_store.clone();
|
let context_store = context_store.clone();
|
||||||
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
|
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
|
||||||
tasks.push(task);
|
tasks.push(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssistantContext::Thread(thread_context) => {
|
Context::Thread(thread_context) => {
|
||||||
let context_store = context_store.clone();
|
let context_store = context_store.clone();
|
||||||
tasks.push(refresh_thread_text(context_store, thread_context, cx));
|
tasks.push(refresh_thread_text(context_store, thread_context, cx));
|
||||||
}
|
}
|
||||||
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful,
|
// 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
|
// and doing the caching properly could be tricky (unless it's already handled by
|
||||||
// the HttpClient?).
|
// the HttpClient?).
|
||||||
AssistantContext::FetchedUrl(_) => {}
|
Context::FetchedUrl(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,9 +558,9 @@ pub fn refresh_context_store_text(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_file_text(
|
fn refresh_file_text(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
file_context: &FileContext,
|
file_context: &FileContext,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Option<Task<()>> {
|
) -> Option<Task<()>> {
|
||||||
let id = file_context.id;
|
let id = file_context.id;
|
||||||
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
||||||
@@ -571,7 +570,7 @@ fn refresh_file_text(
|
|||||||
context_store
|
context_store
|
||||||
.update(&mut cx, |context_store, _| {
|
.update(&mut cx, |context_store, _| {
|
||||||
let new_file_context = FileContext { id, context_buffer };
|
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();
|
.ok();
|
||||||
}))
|
}))
|
||||||
@@ -581,9 +580,9 @@ fn refresh_file_text(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_directory_text(
|
fn refresh_directory_text(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
directory_context: &DirectoryContext,
|
directory_context: &DirectoryContext,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Option<Task<()>> {
|
) -> Option<Task<()>> {
|
||||||
let mut stale = false;
|
let mut stale = false;
|
||||||
let futures = directory_context
|
let futures = directory_context
|
||||||
@@ -612,16 +611,16 @@ fn refresh_directory_text(
|
|||||||
context_store
|
context_store
|
||||||
.update(&mut cx, |context_store, _| {
|
.update(&mut cx, |context_store, _| {
|
||||||
let new_directory_context = DirectoryContext::new(id, &path, context_buffers);
|
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();
|
.ok();
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_thread_text(
|
fn refresh_thread_text(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
thread_context: &ThreadContext,
|
thread_context: &ThreadContext,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let id = thread_context.id;
|
let id = thread_context.id;
|
||||||
let thread = thread_context.thread.clone();
|
let thread = thread_context.thread.clone();
|
||||||
@@ -629,11 +628,7 @@ fn refresh_thread_text(
|
|||||||
context_store
|
context_store
|
||||||
.update(&mut cx, |context_store, cx| {
|
.update(&mut cx, |context_store, cx| {
|
||||||
let text = thread.read(cx).text().into();
|
let text = thread.read(cx).text().into();
|
||||||
context_store.replace_context(AssistantContext::Thread(ThreadContext {
|
context_store.replace_context(Context::Thread(ThreadContext { id, thread, text }));
|
||||||
id,
|
|
||||||
thread,
|
|
||||||
text,
|
|
||||||
}));
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
@@ -641,7 +636,7 @@ fn refresh_thread_text(
|
|||||||
|
|
||||||
fn refresh_context_buffer(
|
fn refresh_context_buffer(
|
||||||
context_buffer: &ContextBuffer,
|
context_buffer: &ContextBuffer,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Option<impl Future<Output = ContextBuffer>> {
|
) -> Option<impl Future<Output = ContextBuffer>> {
|
||||||
let buffer = context_buffer.buffer.read(cx);
|
let buffer = context_buffer.buffer.read(cx);
|
||||||
let path = buffer_path_log_err(buffer)?;
|
let path = buffer_path_log_err(buffer)?;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use collections::HashSet;
|
|||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
AppContext, Bounds, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
|
||||||
WeakEntity,
|
Subscription, View, WeakModel, WeakView,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
@@ -24,37 +24,34 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct ContextStrip {
|
pub struct ContextStrip {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
pub context_picker: Entity<ContextPicker>,
|
pub context_picker: View<ContextPicker>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
suggest_context_kind: SuggestContextKind,
|
suggest_context_kind: SuggestContextKind,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
focused_index: Option<usize>,
|
focused_index: Option<usize>,
|
||||||
children_bounds: Option<Vec<Bounds<Pixels>>>,
|
children_bounds: Option<Vec<Bounds<Pixels>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextStrip {
|
impl ContextStrip {
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakView<Editor>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
suggest_context_kind: SuggestContextKind,
|
suggest_context_kind: SuggestContextKind,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let context_picker = cx.new(|cx| {
|
let context_picker = cx.new_view(|cx| {
|
||||||
ContextPicker::new(
|
ContextPicker::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
ConfirmBehavior::KeepOpen,
|
ConfirmBehavior::KeepOpen,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -62,9 +59,9 @@ impl ContextStrip {
|
|||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
|
cx.subscribe(&context_picker, Self::handle_context_picker_event),
|
||||||
cx.on_focus(&focus_handle, window, Self::handle_focus),
|
cx.on_focus(&focus_handle, Self::handle_focus),
|
||||||
cx.on_blur(&focus_handle, window, Self::handle_blur),
|
cx.on_blur(&focus_handle, Self::handle_blur),
|
||||||
];
|
];
|
||||||
|
|
||||||
Self {
|
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 {
|
match self.suggest_context_kind {
|
||||||
SuggestContextKind::File => self.suggested_file(cx),
|
SuggestContextKind::File => self.suggested_file(cx),
|
||||||
SuggestContextKind::Thread => self.suggested_thread(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 workspace = self.workspace.upgrade()?;
|
||||||
let active_item = workspace.read(cx).active_item(cx)?;
|
let active_item = workspace.read(cx).active_item(cx)?;
|
||||||
|
|
||||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(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_model = editor.buffer().read(cx).as_singleton()?;
|
||||||
let active_buffer = active_buffer_entity.read(cx);
|
let active_buffer = active_buffer_model.read(cx);
|
||||||
|
|
||||||
let path = active_buffer.file()?.path();
|
let path = active_buffer.file()?.path();
|
||||||
|
|
||||||
@@ -115,12 +112,12 @@ impl ContextStrip {
|
|||||||
|
|
||||||
Some(SuggestedContext::File {
|
Some(SuggestedContext::File {
|
||||||
name,
|
name,
|
||||||
buffer: active_buffer_entity.downgrade(),
|
buffer: active_buffer_model.downgrade(),
|
||||||
icon_path,
|
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() {
|
if !self.context_picker.read(cx).allow_threads() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -152,25 +149,24 @@ impl ContextStrip {
|
|||||||
|
|
||||||
fn handle_context_picker_event(
|
fn handle_context_picker_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_picker: &Entity<ContextPicker>,
|
_picker: View<ContextPicker>,
|
||||||
_event: &DismissEvent,
|
_event: &DismissEvent,
|
||||||
_window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
cx.emit(ContextStripEvent::PickerDismissed);
|
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();
|
self.focused_index = self.last_pill_index();
|
||||||
cx.notify();
|
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;
|
self.focused_index = None;
|
||||||
cx.notify();
|
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 {
|
self.focused_index = match self.focused_index {
|
||||||
Some(index) if index > 0 => Some(index - 1),
|
Some(index) if index > 0 => Some(index - 1),
|
||||||
_ => self.last_pill_index(),
|
_ => self.last_pill_index(),
|
||||||
@@ -179,7 +175,7 @@ impl ContextStrip {
|
|||||||
cx.notify();
|
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 {
|
let Some(last_index) = self.last_pill_index() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -192,7 +188,7 @@ impl ContextStrip {
|
|||||||
cx.notify();
|
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 {
|
let Some(focused_index) = self.focused_index else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -210,7 +206,7 @@ impl ContextStrip {
|
|||||||
cx.notify();
|
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 {
|
let Some(focused_index) = self.focused_index else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -280,12 +276,7 @@ impl ContextStrip {
|
|||||||
best.map(|(index, _, _)| index)
|
best.map(|(index, _, _)| index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_focused_context(
|
fn remove_focused_context(&mut self, _: &RemoveFocusedContext, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &RemoveFocusedContext,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
if let Some(index) = self.focused_index {
|
if let Some(index) = self.focused_index {
|
||||||
let mut is_empty = false;
|
let mut is_empty = false;
|
||||||
|
|
||||||
@@ -311,32 +302,22 @@ impl ContextStrip {
|
|||||||
self.focused_index == Some(context.len())
|
self.focused_index == Some(context.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_suggested_context(
|
fn accept_suggested_context(&mut self, _: &AcceptSuggestedContext, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &AcceptSuggestedContext,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
if let Some(suggested) = self.suggested_context(cx) {
|
if let Some(suggested) = self.suggested_context(cx) {
|
||||||
let context_store = self.context_store.read(cx);
|
let context_store = self.context_store.read(cx);
|
||||||
|
|
||||||
if self.is_suggested_focused(context_store.context()) {
|
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(
|
fn add_suggested_context(&mut self, suggested: &SuggestedContext, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
suggested: &SuggestedContext,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let task = self.context_store.update(cx, |context_store, cx| {
|
let task = self.context_store.update(cx, |context_store, cx| {
|
||||||
context_store.accept_suggested_context(&suggested, 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) {
|
match task.await.notify_async_err(&mut cx) {
|
||||||
None => {}
|
None => {}
|
||||||
Some(()) => {
|
Some(()) => {
|
||||||
@@ -353,14 +334,14 @@ impl ContextStrip {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for ContextStrip {
|
impl FocusableView for ContextStrip {
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ContextStrip {
|
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_store = self.context_store.read(cx);
|
||||||
let context = context_store
|
let context = context_store
|
||||||
.context()
|
.context()
|
||||||
@@ -393,20 +374,19 @@ impl Render for ContextStrip {
|
|||||||
.on_action(cx.listener(Self::remove_focused_context))
|
.on_action(cx.listener(Self::remove_focused_context))
|
||||||
.on_action(cx.listener(Self::accept_suggested_context))
|
.on_action(cx.listener(Self::accept_suggested_context))
|
||||||
.on_children_prepainted({
|
.on_children_prepainted({
|
||||||
let entity = cx.entity().downgrade();
|
let view = cx.view().downgrade();
|
||||||
move |children_bounds, _window, cx| {
|
move |children_bounds, cx| {
|
||||||
entity
|
view.update(cx, |this, _| {
|
||||||
.update(cx, |this, _| {
|
this.children_bounds = Some(children_bounds);
|
||||||
this.children_bounds = Some(children_bounds);
|
})
|
||||||
})
|
.ok();
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
PopoverMenu::new("context-picker")
|
PopoverMenu::new("context-picker")
|
||||||
.menu(move |window, cx| {
|
.menu(move |cx| {
|
||||||
context_picker.update(cx, |this, cx| {
|
context_picker.update(cx, |this, cx| {
|
||||||
this.init(window, cx);
|
this.init(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(context_picker.clone())
|
Some(context_picker.clone())
|
||||||
@@ -417,12 +397,12 @@ impl Render for ContextStrip {
|
|||||||
.style(ui::ButtonStyle::Filled)
|
.style(ui::ButtonStyle::Filled)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
move |window, cx| {
|
|
||||||
|
move |cx| {
|
||||||
Tooltip::for_action_in(
|
Tooltip::for_action_in(
|
||||||
"Add Context",
|
"Add Context",
|
||||||
&ToggleContextPicker,
|
&ToggleContextPicker,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -449,12 +429,8 @@ impl Render for ContextStrip {
|
|||||||
)
|
)
|
||||||
.opacity(0.5)
|
.opacity(0.5)
|
||||||
.children(
|
.children(
|
||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
|
||||||
&ToggleContextPicker,
|
.map(|binding| binding.into_any_element()),
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
)
|
|
||||||
.map(|binding| binding.into_any_element()),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -467,7 +443,7 @@ impl Render for ContextStrip {
|
|||||||
Some({
|
Some({
|
||||||
let id = context.id;
|
let id = context.id;
|
||||||
let context_store = self.context_store.clone();
|
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| {
|
context_store.update(cx, |this, _cx| {
|
||||||
this.remove_context(id);
|
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);
|
this.focused_index = Some(i);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})))
|
})))
|
||||||
@@ -488,11 +464,9 @@ impl Render for ContextStrip {
|
|||||||
suggested.kind(),
|
suggested.kind(),
|
||||||
self.is_suggested_focused(&context),
|
self.is_suggested_focused(&context),
|
||||||
)
|
)
|
||||||
.on_click(Rc::new(cx.listener(
|
.on_click(Rc::new(cx.listener(move |this, _event, cx| {
|
||||||
move |this, _event, window, cx| {
|
this.add_suggested_context(&suggested, cx);
|
||||||
this.add_suggested_context(&suggested, window, cx);
|
}))),
|
||||||
},
|
|
||||||
))),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(!context.is_empty(), {
|
.when(!context.is_empty(), {
|
||||||
@@ -502,20 +476,19 @@ impl Render for ContextStrip {
|
|||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
move |window, cx| {
|
move |cx| {
|
||||||
Tooltip::for_action_in(
|
Tooltip::for_action_in(
|
||||||
"Remove All Context",
|
"Remove All Context",
|
||||||
&RemoveAllContext,
|
&RemoveAllContext,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(cx.listener({
|
.on_click(cx.listener({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
move |_this, _event, window, cx| {
|
move |_this, _event, cx| {
|
||||||
focus_handle.dispatch_action(&RemoveAllContext, window, cx);
|
focus_handle.dispatch_action(&RemoveAllContext, cx);
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
@@ -543,11 +516,11 @@ pub enum SuggestedContext {
|
|||||||
File {
|
File {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
icon_path: Option<SharedString>,
|
icon_path: Option<SharedString>,
|
||||||
buffer: WeakEntity<Buffer>,
|
buffer: WeakModel<Buffer>,
|
||||||
},
|
},
|
||||||
Thread {
|
Thread {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
thread: WeakEntity<Thread>,
|
thread: WeakModel<Thread>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ use editor::{
|
|||||||
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, point, AnyElement, App, ClickEvent, Context, CursorStyle, Entity,
|
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
|
||||||
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
|
FocusHandle, FocusableView, FontWeight, Model, Subscription, TextStyle, View, ViewContext,
|
||||||
|
WeakModel, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||||
use language_model_selector::LanguageModelSelector;
|
use language_model_selector::LanguageModelSelector;
|
||||||
@@ -34,12 +35,12 @@ use util::ResultExt;
|
|||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub struct PromptEditor<T> {
|
pub struct PromptEditor<T> {
|
||||||
pub editor: Entity<Editor>,
|
pub editor: View<Editor>,
|
||||||
mode: PromptEditorMode,
|
mode: PromptEditorMode,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
context_strip: Entity<ContextStrip>,
|
context_strip: View<ContextStrip>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
model_selector: Entity<AssistantModelSelector>,
|
model_selector: View<AssistantModelSelector>,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||||
edited_since_done: bool,
|
edited_since_done: bool,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
@@ -55,7 +56,7 @@ pub struct PromptEditor<T> {
|
|||||||
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
|
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
|
||||||
|
|
||||||
impl<T: 'static> Render 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 ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||||
let mut buttons = Vec::new();
|
let mut buttons = Vec::new();
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
|
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
buttons.extend(self.render_buttons(window, cx));
|
buttons.extend(self.render_buttons(cx));
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("PromptEditor")
|
.key_context("PromptEditor")
|
||||||
@@ -162,7 +163,9 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
el.child(
|
el.child(
|
||||||
div()
|
div()
|
||||||
.id("error")
|
.id("error")
|
||||||
.tooltip(Tooltip::text(error_message))
|
.tooltip(move |cx| {
|
||||||
|
Tooltip::text(error_message.clone(), cx)
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::XCircle)
|
Icon::new(IconName::XCircle)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -176,7 +179,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(div().flex_1().child(self.render_editor(window, cx)))
|
.child(div().flex_1().child(self.render_editor(cx)))
|
||||||
.child(
|
.child(
|
||||||
WithRemSize::new(ui_font_size)
|
WithRemSize::new(ui_font_size)
|
||||||
.flex()
|
.flex()
|
||||||
@@ -206,8 +209,8 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Focusable for PromptEditor<T> {
|
impl<T: 'static> FocusableView for PromptEditor<T> {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.editor.focus_handle(cx)
|
self.editor.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,50 +218,47 @@ impl<T: 'static> Focusable for PromptEditor<T> {
|
|||||||
impl<T: 'static> PromptEditor<T> {
|
impl<T: 'static> PromptEditor<T> {
|
||||||
const MAX_LINES: u8 = 8;
|
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 {
|
match &self.mode {
|
||||||
PromptEditorMode::Buffer { codegen, .. } => codegen.read(cx).status(cx),
|
PromptEditorMode::Buffer { codegen, .. } => codegen.read(cx).status(cx),
|
||||||
PromptEditorMode::Terminal { codegen, .. } => &codegen.read(cx).status,
|
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.clear();
|
||||||
self.editor_subscriptions.push(cx.subscribe_in(
|
self.editor_subscriptions
|
||||||
&self.editor,
|
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
|
||||||
window,
|
|
||||||
Self::handle_prompt_editor_events,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_show_cursor_when_unfocused(
|
pub fn set_show_cursor_when_unfocused(
|
||||||
&mut self,
|
&mut self,
|
||||||
show_cursor_when_unfocused: bool,
|
show_cursor_when_unfocused: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, 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 prompt = self.prompt(cx);
|
||||||
let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
|
let focus = self.editor.focus_handle(cx).contains_focused(cx);
|
||||||
self.editor = cx.new(|cx| {
|
self.editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, 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_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_placeholder_text("Add a prompt…", cx);
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
if focus {
|
if focus {
|
||||||
window.focus(&editor.focus_handle(cx));
|
editor.focus(cx);
|
||||||
}
|
}
|
||||||
editor
|
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 {
|
let action = match mode {
|
||||||
PromptEditorMode::Buffer { codegen, .. } => {
|
PromptEditorMode::Buffer { codegen, .. } => {
|
||||||
if codegen.read(cx).is_insertion {
|
if codegen.read(cx).is_insertion {
|
||||||
@@ -271,50 +271,46 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let assistant_panel_keybinding =
|
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 ― "))
|
.map(|keybinding| format!("{keybinding} to chat ― "))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
format!("{action}… ({assistant_panel_keybinding}↓↑ for history)")
|
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)
|
self.editor.read(cx).text(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_rate_limit_notice(
|
fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &ClickEvent,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.show_rate_limit_notice = !self.show_rate_limit_notice;
|
self.show_rate_limit_notice = !self.show_rate_limit_notice;
|
||||||
if 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();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_prompt_editor_events(
|
fn handle_prompt_editor_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &Entity<Editor>,
|
_: View<Editor>,
|
||||||
event: &EditorEvent,
|
event: &EditorEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited { .. } => {
|
EditorEvent::Edited { .. } => {
|
||||||
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace
|
||||||
let is_via_ssh = workspace
|
.update(cx, |workspace, cx| {
|
||||||
.project()
|
let is_via_ssh = workspace
|
||||||
.update(cx, |project, _| project.is_via_ssh());
|
.project()
|
||||||
|
.update(cx, |project, _| project.is_via_ssh());
|
||||||
|
|
||||||
workspace
|
workspace
|
||||||
.client()
|
.client()
|
||||||
.telemetry()
|
.telemetry()
|
||||||
.log_edit_event("inline assist", is_via_ssh);
|
.log_edit_event("inline assist", is_via_ssh);
|
||||||
});
|
})
|
||||||
|
.log_err();
|
||||||
}
|
}
|
||||||
let prompt = self.editor.read(cx).text(cx);
|
let prompt = self.editor.read(cx).text(cx);
|
||||||
if self
|
if self
|
||||||
@@ -338,40 +334,20 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_context_picker(
|
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
self.context_picker_menu_handle.toggle(cx);
|
||||||
_: &ToggleContextPicker,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.context_picker_menu_handle.toggle(window, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_model_selector(
|
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
self.model_selector_menu_handle.toggle(cx);
|
||||||
_: &ToggleModelSelector,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.model_selector_menu_handle.toggle(window, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_all_context(
|
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &RemoveAllContext,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.context_store.update(cx, |store, _cx| store.clear());
|
self.context_store.update(cx, |store, _cx| store.clear());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel(
|
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &editor::actions::Cancel,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
match self.codegen_status(cx) {
|
match self.codegen_status(cx) {
|
||||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||||
cx.emit(PromptEditorEvent::CancelRequested);
|
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) {
|
match self.codegen_status(cx) {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
cx.emit(PromptEditorEvent::StartRequested);
|
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 let Some(ix) = self.prompt_history_ix {
|
||||||
if ix > 0 {
|
if ix > 0 {
|
||||||
self.prompt_history_ix = Some(ix - 1);
|
self.prompt_history_ix = Some(ix - 1);
|
||||||
let prompt = self.prompt_history[ix - 1].as_str();
|
let prompt = self.prompt_history[ix - 1].as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_beginning(&Default::default(), window, cx);
|
editor.move_to_beginning(&Default::default(), cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if !self.prompt_history.is_empty() {
|
} else if !self.prompt_history.is_empty() {
|
||||||
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
||||||
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_beginning(&Default::default(), window, 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 let Some(ix) = self.prompt_history_ix {
|
||||||
if ix < self.prompt_history.len() - 1 {
|
if ix < self.prompt_history.len() - 1 {
|
||||||
self.prompt_history_ix = Some(ix + 1);
|
self.prompt_history_ix = Some(ix + 1);
|
||||||
let prompt = self.prompt_history[ix + 1].as_str();
|
let prompt = self.prompt_history[ix + 1].as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_end(&Default::default(), window, cx)
|
editor.move_to_end(&Default::default(), cx)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.prompt_history_ix = None;
|
self.prompt_history_ix = None;
|
||||||
let prompt = self.pending_prompt.as_str();
|
let prompt = self.pending_prompt.as_str();
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.set_text(prompt, window, cx);
|
editor.set_text(prompt, cx);
|
||||||
editor.move_to_end(&Default::default(), window, cx)
|
editor.move_to_end(&Default::default(), cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
let mode = match &self.mode {
|
||||||
PromptEditorMode::Buffer { codegen, .. } => {
|
PromptEditorMode::Buffer { codegen, .. } => {
|
||||||
let codegen = codegen.read(cx);
|
let codegen = codegen.read(cx);
|
||||||
@@ -467,22 +443,21 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.icon(IconName::Return)
|
.icon(IconName::Return)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.icon_color(Color::Muted)
|
.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()]
|
.into_any_element()]
|
||||||
}
|
}
|
||||||
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
|
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
|
||||||
.icon_color(Color::Error)
|
.icon_color(Color::Error)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
mode.tooltip_interrupt(),
|
mode.tooltip_interrupt(),
|
||||||
Some(&menu::Cancel),
|
Some(&menu::Cancel),
|
||||||
"Changes won't be discarded",
|
"Changes won't be discarded",
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||||
.into_any_element()],
|
.into_any_element()],
|
||||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||||
let has_error = matches!(codegen_status, 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)
|
vec![IconButton::new("restart", IconName::RotateCw)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
mode.tooltip_restart(),
|
mode.tooltip_restart(),
|
||||||
Some(&menu::Confirm),
|
Some(&menu::Confirm),
|
||||||
"Changes will be discarded",
|
"Changes will be discarded",
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
.on_click(cx.listener(|_, _, cx| {
|
||||||
cx.emit(PromptEditorEvent::StartRequested);
|
cx.emit(PromptEditorEvent::StartRequested);
|
||||||
}))
|
}))
|
||||||
.into_any_element()]
|
.into_any_element()]
|
||||||
@@ -507,10 +481,10 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
let accept = IconButton::new("accept", IconName::Check)
|
let accept = IconButton::new("accept", IconName::Check)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(move |window, cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, 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 });
|
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||||
}))
|
}))
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
@@ -521,15 +495,14 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
IconButton::new("confirm", IconName::Play)
|
IconButton::new("confirm", IconName::Play)
|
||||||
.icon_color(Color::Info)
|
.icon_color(Color::Info)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::for_action(
|
Tooltip::for_action(
|
||||||
"Execute Generated Command",
|
"Execute Generated Command",
|
||||||
&menu::SecondaryConfirm,
|
&menu::SecondaryConfirm,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
.on_click(cx.listener(|_, _, cx| {
|
||||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
||||||
}))
|
}))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
@@ -541,12 +514,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cycle_prev(
|
fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &CyclePreviousInlineAssist,
|
|
||||||
_: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
PromptEditorMode::Buffer { codegen, .. } => {
|
PromptEditorMode::Buffer { codegen, .. } => {
|
||||||
codegen.update(cx, |codegen, cx| codegen.cycle_prev(cx));
|
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 {
|
match &self.mode {
|
||||||
PromptEditorMode::Buffer { codegen, .. } => {
|
PromptEditorMode::Buffer { codegen, .. } => {
|
||||||
codegen.update(cx, |codegen, cx| codegen.cycle_next(cx));
|
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)
|
IconButton::new("cancel", IconName::Close)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip(Tooltip::text("Close Assistant"))
|
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
|
||||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||||
.into_any_element()
|
.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 disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
||||||
|
|
||||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
@@ -617,13 +585,13 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
move |window, cx| {
|
move |cx| {
|
||||||
cx.new(|_| {
|
cx.new_view(|cx| {
|
||||||
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(
|
||||||
&CyclePreviousInlineAssist,
|
&CyclePreviousInlineAssist,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
cx,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if !disabled && current_index != 0 {
|
if !disabled && current_index != 0 {
|
||||||
@@ -634,8 +602,8 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
this.cycle_prev(&CyclePreviousInlineAssist, window, cx);
|
this.cycle_prev(&CyclePreviousInlineAssist, cx);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -658,13 +626,13 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = self.editor.focus_handle(cx);
|
let focus_handle = self.editor.focus_handle(cx);
|
||||||
move |window, cx| {
|
move |cx| {
|
||||||
cx.new(|_| {
|
cx.new_view(|cx| {
|
||||||
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(
|
||||||
&CycleNextInlineAssist,
|
&CycleNextInlineAssist,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
cx,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if !disabled && current_index != total_models - 1 {
|
if !disabled && current_index != total_models - 1 {
|
||||||
@@ -675,14 +643,14 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(
|
||||||
this.cycle_next(&CycleNextInlineAssist, window, cx)
|
cx.listener(|this, _, cx| this.cycle_next(&CycleNextInlineAssist, cx)),
|
||||||
})),
|
),
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.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(
|
Popover::new().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.occlude()
|
.occlude()
|
||||||
@@ -706,7 +674,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
} else {
|
} else {
|
||||||
ui::ToggleState::Unselected
|
ui::ToggleState::Unselected
|
||||||
},
|
},
|
||||||
|selection, _, cx| {
|
|selection, cx| {
|
||||||
let is_dismissed = match selection {
|
let is_dismissed = match selection {
|
||||||
ui::ToggleState::Unselected => false,
|
ui::ToggleState::Unselected => false,
|
||||||
ui::ToggleState::Indeterminate => return,
|
ui::ToggleState::Indeterminate => return,
|
||||||
@@ -725,11 +693,10 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
|
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
|
||||||
)
|
)
|
||||||
.child(Button::new("more-info", "More Info").on_click(
|
.child(Button::new("more-info", "More Info").on_click(
|
||||||
|_event, window, cx| {
|
|_event, cx| {
|
||||||
window.dispatch_action(
|
cx.dispatch_action(Box::new(
|
||||||
Box::new(zed_actions::OpenAccountSettings),
|
zed_actions::OpenAccountSettings,
|
||||||
cx,
|
))
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@@ -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 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()
|
div()
|
||||||
.key_context("InlineAssistEditor")
|
.key_context("InlineAssistEditor")
|
||||||
@@ -773,15 +740,17 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
|
|
||||||
fn handle_context_strip_event(
|
fn handle_context_strip_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_context_strip: &Entity<ContextStrip>,
|
_context_strip: View<ContextStrip>,
|
||||||
event: &ContextStripEvent,
|
event: &ContextStripEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
ContextStripEvent::PickerDismissed
|
ContextStripEvent::PickerDismissed
|
||||||
| ContextStripEvent::BlurredEmpty
|
| 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 => {}
|
ContextStripEvent::BlurredDown => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,12 +759,12 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
pub enum PromptEditorMode {
|
pub enum PromptEditorMode {
|
||||||
Buffer {
|
Buffer {
|
||||||
id: InlineAssistId,
|
id: InlineAssistId,
|
||||||
codegen: Entity<BufferCodegen>,
|
codegen: Model<BufferCodegen>,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
},
|
},
|
||||||
Terminal {
|
Terminal {
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
codegen: Entity<TerminalCodegen>,
|
codegen: Model<TerminalCodegen>,
|
||||||
height_in_lines: u8,
|
height_in_lines: u8,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -826,14 +795,13 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
id: InlineAssistId,
|
id: InlineAssistId,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
prompt_buffer: Entity<MultiBuffer>,
|
prompt_buffer: Model<MultiBuffer>,
|
||||||
codegen: Entity<BufferCodegen>,
|
codegen: Model<BufferCodegen>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
|
||||||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
|
||||||
) -> PromptEditor<BufferCodegen> {
|
) -> PromptEditor<BufferCodegen> {
|
||||||
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
||||||
let mode = PromptEditorMode::Buffer {
|
let mode = PromptEditorMode::Buffer {
|
||||||
@@ -842,7 +810,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
gutter_dimensions,
|
gutter_dimensions,
|
||||||
};
|
};
|
||||||
|
|
||||||
let prompt_editor = cx.new(|cx| {
|
let prompt_editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::new(
|
let mut editor = Editor::new(
|
||||||
EditorMode::AutoHeight {
|
EditorMode::AutoHeight {
|
||||||
max_lines: Self::MAX_LINES as usize,
|
max_lines: Self::MAX_LINES as usize,
|
||||||
@@ -850,7 +818,6 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, 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
|
// always show the cursor (even when it isn't focused) because
|
||||||
// typing in one will make what you typed appear in all of them.
|
// typing in one will make what you typed appear in all of them.
|
||||||
editor.set_show_cursor_when_unfocused(true, cx);
|
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
|
editor
|
||||||
});
|
});
|
||||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let model_selector_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(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
@@ -872,25 +839,23 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let context_strip_subscription =
|
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 {
|
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
|
||||||
editor: prompt_editor.clone(),
|
editor: prompt_editor.clone(),
|
||||||
context_store,
|
context_store,
|
||||||
context_strip,
|
context_strip,
|
||||||
context_picker_menu_handle,
|
context_picker_menu_handle,
|
||||||
model_selector: cx.new(|cx| {
|
model_selector: cx.new_view(|cx| {
|
||||||
AssistantModelSelector::new(
|
AssistantModelSelector::new(
|
||||||
fs,
|
fs,
|
||||||
model_selector_menu_handle.clone(),
|
model_selector_menu_handle.clone(),
|
||||||
prompt_editor.focus_handle(cx),
|
prompt_editor.focus_handle(cx),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -907,14 +872,14 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
_phantom: Default::default(),
|
_phantom: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.subscribe_to_editor(window, cx);
|
this.subscribe_to_editor(cx);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_codegen_changed(
|
fn handle_codegen_changed(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Entity<BufferCodegen>,
|
_: Model<BufferCodegen>,
|
||||||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
|
||||||
) {
|
) {
|
||||||
match self.codegen_status(cx) {
|
match self.codegen_status(cx) {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
@@ -953,7 +918,7 @@ impl PromptEditor<BufferCodegen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn codegen(&self) -> &Entity<BufferCodegen> {
|
pub fn codegen(&self) -> &Model<BufferCodegen> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
PromptEditorMode::Buffer { codegen, .. } => codegen,
|
PromptEditorMode::Buffer { codegen, .. } => codegen,
|
||||||
PromptEditorMode::Terminal { .. } => unreachable!(),
|
PromptEditorMode::Terminal { .. } => unreachable!(),
|
||||||
@@ -986,14 +951,13 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
pub fn new_terminal(
|
pub fn new_terminal(
|
||||||
id: TerminalInlineAssistId,
|
id: TerminalInlineAssistId,
|
||||||
prompt_history: VecDeque<String>,
|
prompt_history: VecDeque<String>,
|
||||||
prompt_buffer: Entity<MultiBuffer>,
|
prompt_buffer: Model<MultiBuffer>,
|
||||||
codegen: Entity<TerminalCodegen>,
|
codegen: Model<TerminalCodegen>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
||||||
let mode = PromptEditorMode::Terminal {
|
let mode = PromptEditorMode::Terminal {
|
||||||
@@ -1002,7 +966,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
height_in_lines: 1,
|
height_in_lines: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let prompt_editor = cx.new(|cx| {
|
let prompt_editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::new(
|
let mut editor = Editor::new(
|
||||||
EditorMode::AutoHeight {
|
EditorMode::AutoHeight {
|
||||||
max_lines: Self::MAX_LINES as usize,
|
max_lines: Self::MAX_LINES as usize,
|
||||||
@@ -1010,17 +974,16 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
prompt_buffer,
|
prompt_buffer,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, 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
|
editor
|
||||||
});
|
});
|
||||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let model_selector_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(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
@@ -1028,25 +991,23 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let context_strip_subscription =
|
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 {
|
let mut this = Self {
|
||||||
editor: prompt_editor.clone(),
|
editor: prompt_editor.clone(),
|
||||||
context_store,
|
context_store,
|
||||||
context_strip,
|
context_strip,
|
||||||
context_picker_menu_handle,
|
context_picker_menu_handle,
|
||||||
model_selector: cx.new(|cx| {
|
model_selector: cx.new_view(|cx| {
|
||||||
AssistantModelSelector::new(
|
AssistantModelSelector::new(
|
||||||
fs,
|
fs,
|
||||||
model_selector_menu_handle.clone(),
|
model_selector_menu_handle.clone(),
|
||||||
prompt_editor.focus_handle(cx),
|
prompt_editor.focus_handle(cx),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -1063,11 +1024,11 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
_phantom: Default::default(),
|
_phantom: Default::default(),
|
||||||
};
|
};
|
||||||
this.count_lines(cx);
|
this.count_lines(cx);
|
||||||
this.subscribe_to_editor(window, cx);
|
this.subscribe_to_editor(cx);
|
||||||
this
|
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(
|
let height_in_lines = cmp::max(
|
||||||
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
||||||
cmp::min(
|
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 {
|
match &self.codegen().read(cx).status {
|
||||||
CodegenStatus::Idle => {
|
CodegenStatus::Idle => {
|
||||||
self.editor
|
self.editor
|
||||||
@@ -1109,7 +1070,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn codegen(&self) -> &Entity<TerminalCodegen> {
|
pub fn codegen(&self) -> &Model<TerminalCodegen> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
PromptEditorMode::Buffer { .. } => unreachable!(),
|
PromptEditorMode::Buffer { .. } => unreachable!(),
|
||||||
PromptEditorMode::Terminal { codegen, .. } => codegen,
|
PromptEditorMode::Terminal { codegen, .. } => codegen,
|
||||||
@@ -1133,7 +1094,7 @@ fn dismissed_rate_limit_notice() -> bool {
|
|||||||
.map_or(false, |s| s.is_some())
|
.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 {
|
db::write_and_log(cx, move || async move {
|
||||||
if is_dismissed {
|
if is_dismissed {
|
||||||
db::kvp::KEY_VALUE_STORE
|
db::kvp::KEY_VALUE_STORE
|
||||||
|
|||||||
@@ -4,19 +4,16 @@ use editor::actions::MoveUp;
|
|||||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
|
pulsating_between, Animation, AnimationExt, AppContext, DismissEvent, FocusableView, Model,
|
||||||
TextStyle, WeakEntity,
|
Subscription, TextStyle, View, WeakModel, WeakView,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||||
use language_model_selector::LanguageModelSelector;
|
use language_model_selector::LanguageModelSelector;
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use text::Bias;
|
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor};
|
||||||
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
|
|
||||||
};
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::assistant_model_selector::AssistantModelSelector;
|
use crate::assistant_model_selector::AssistantModelSelector;
|
||||||
@@ -28,14 +25,14 @@ use crate::thread_store::ThreadStore;
|
|||||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
thread: Entity<Thread>,
|
thread: Model<Thread>,
|
||||||
editor: Entity<Editor>,
|
editor: View<Editor>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
context_strip: Entity<ContextStrip>,
|
context_strip: View<ContextStrip>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
inline_context_picker: Entity<ContextPicker>,
|
inline_context_picker: View<ContextPicker>,
|
||||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
model_selector: Entity<AssistantModelSelector>,
|
model_selector: View<AssistantModelSelector>,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||||
use_tools: bool,
|
use_tools: bool,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
@@ -44,38 +41,36 @@ pub struct MessageEditor {
|
|||||||
impl MessageEditor {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakModel<ThreadStore>,
|
||||||
thread: Entity<Thread>,
|
thread: Model<Thread>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> 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 context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||||
|
|
||||||
let editor = cx.new(|cx| {
|
let editor = cx.new_view(|cx| {
|
||||||
let mut editor = Editor::auto_height(10, window, cx);
|
let mut editor = Editor::auto_height(10, cx);
|
||||||
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
|
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
|
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
let inline_context_picker = cx.new(|cx| {
|
let inline_context_picker = cx.new_view(|cx| {
|
||||||
ContextPicker::new(
|
ContextPicker::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
editor.downgrade(),
|
editor.downgrade(),
|
||||||
ConfirmBehavior::Close,
|
ConfirmBehavior::Close,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let context_strip = cx.new(|cx| {
|
let context_strip = cx.new_view(|cx| {
|
||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
@@ -83,19 +78,17 @@ impl MessageEditor {
|
|||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::File,
|
SuggestContextKind::File,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.subscribe_in(&editor, window, Self::handle_editor_event),
|
cx.subscribe(&editor, Self::handle_editor_event),
|
||||||
cx.subscribe_in(
|
cx.subscribe(
|
||||||
&inline_context_picker,
|
&inline_context_picker,
|
||||||
window,
|
|
||||||
Self::handle_inline_context_picker_event,
|
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 {
|
Self {
|
||||||
@@ -106,12 +99,11 @@ impl MessageEditor {
|
|||||||
context_picker_menu_handle,
|
context_picker_menu_handle,
|
||||||
inline_context_picker,
|
inline_context_picker,
|
||||||
inline_context_picker_menu_handle,
|
inline_context_picker_menu_handle,
|
||||||
model_selector: cx.new(|cx| {
|
model_selector: cx.new_view(|cx| {
|
||||||
AssistantModelSelector::new(
|
AssistantModelSelector::new(
|
||||||
fs,
|
fs,
|
||||||
model_selector_menu_handle.clone(),
|
model_selector_menu_handle.clone(),
|
||||||
editor.focus_handle(cx),
|
editor.focus_handle(cx),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -121,59 +113,29 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_model_selector(
|
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
self.model_selector_menu_handle.toggle(cx)
|
||||||
_: &ToggleModelSelector,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.model_selector_menu_handle.toggle(window, 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;
|
self.use_tools = !self.use_tools;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_context_picker(
|
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
self.context_picker_menu_handle.toggle(cx);
|
||||||
_: &ToggleContextPicker,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.context_picker_menu_handle.toggle(window, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_all_context(
|
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &RemoveAllContext,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.context_store.update(cx, |store, _cx| store.clear());
|
self.context_store.update(cx, |store, _cx| store.clear());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
|
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||||
self.send_to_model(RequestKind::Chat, window, cx);
|
self.send_to_model(RequestKind::Chat, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_editor_empty(&self, cx: &App) -> bool {
|
fn send_to_model(&mut self, request_kind: RequestKind, cx: &mut ViewContext<Self>) {
|
||||||
self.editor.read(cx).text(cx).is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_model_selected(&self, cx: &App) -> 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>,
|
|
||||||
) {
|
|
||||||
let provider = LanguageModelRegistry::read_global(cx).active_provider();
|
let provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||||
if provider
|
if provider
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -190,7 +152,7 @@ impl MessageEditor {
|
|||||||
|
|
||||||
let user_message = self.editor.update(cx, |editor, cx| {
|
let user_message = self.editor.update(cx, |editor, cx| {
|
||||||
let text = editor.text(cx);
|
let text = editor.text(cx);
|
||||||
editor.clear(window, cx);
|
editor.clear(cx);
|
||||||
text
|
text
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -229,10 +191,9 @@ impl MessageEditor {
|
|||||||
|
|
||||||
fn handle_editor_event(
|
fn handle_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &Entity<Editor>,
|
editor: View<Editor>,
|
||||||
event: &EditorEvent,
|
event: &EditorEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::SelectionsChanged { .. } => {
|
EditorEvent::SelectionsChanged { .. } => {
|
||||||
@@ -240,13 +201,10 @@ impl MessageEditor {
|
|||||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
||||||
if newest_cursor.column > 0 {
|
if newest_cursor.column > 0 {
|
||||||
let behind_cursor = snapshot.clip_point(
|
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
|
||||||
Point::new(newest_cursor.row, newest_cursor.column - 1),
|
|
||||||
Bias::Left,
|
|
||||||
);
|
|
||||||
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
|
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
|
||||||
if char_behind_cursor == Some('@') {
|
if char_behind_cursor == Some('@') {
|
||||||
self.inline_context_picker_menu_handle.show(window, cx);
|
self.inline_context_picker_menu_handle.show(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -257,66 +215,57 @@ impl MessageEditor {
|
|||||||
|
|
||||||
fn handle_inline_context_picker_event(
|
fn handle_inline_context_picker_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_inline_context_picker: &Entity<ContextPicker>,
|
_inline_context_picker: View<ContextPicker>,
|
||||||
_event: &DismissEvent,
|
_event: &DismissEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||||
window.focus(&editor_focus_handle);
|
cx.focus(&editor_focus_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_context_strip_event(
|
fn handle_context_strip_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_context_strip: &Entity<ContextStrip>,
|
_context_strip: View<ContextStrip>,
|
||||||
event: &ContextStripEvent,
|
event: &ContextStripEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
ContextStripEvent::PickerDismissed
|
ContextStripEvent::PickerDismissed
|
||||||
| ContextStripEvent::BlurredEmpty
|
| ContextStripEvent::BlurredEmpty
|
||||||
| ContextStripEvent::BlurredDown => {
|
| ContextStripEvent::BlurredDown => {
|
||||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||||
window.focus(&editor_focus_handle);
|
cx.focus(&editor_focus_handle);
|
||||||
}
|
}
|
||||||
ContextStripEvent::BlurredUp => {}
|
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()
|
if self.context_picker_menu_handle.is_deployed()
|
||||||
|| self.inline_context_picker_menu_handle.is_deployed()
|
|| self.inline_context_picker_menu_handle.is_deployed()
|
||||||
{
|
{
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
} else {
|
} else {
|
||||||
self.context_strip.focus_handle(cx).focus(window);
|
cx.focus_view(&self.context_strip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for MessageEditor {
|
impl FocusableView for MessageEditor {
|
||||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||||
self.editor.focus_handle(cx)
|
self.editor.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for MessageEditor {
|
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 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 focus_handle = self.editor.focus_handle(cx);
|
||||||
let inline_context_picker = self.inline_context_picker.clone();
|
let inline_context_picker = self.inline_context_picker.clone();
|
||||||
let bg_color = cx.theme().colors().editor_background;
|
let bg_color = cx.theme().colors().editor_background;
|
||||||
let is_streaming_completion = self.thread.read(cx).is_streaming();
|
let is_streaming_completion = self.thread.read(cx).is_streaming();
|
||||||
let button_width = px(64.);
|
let button_width = px(64.);
|
||||||
let is_model_selected = self.is_model_selected(cx);
|
|
||||||
let is_editor_empty = self.is_editor_empty(cx);
|
|
||||||
let submit_label_color = if is_editor_empty {
|
|
||||||
Color::Muted
|
|
||||||
} else {
|
|
||||||
Color::Default
|
|
||||||
};
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("MessageEditor")
|
.key_context("MessageEditor")
|
||||||
@@ -358,9 +307,9 @@ impl Render for MessageEditor {
|
|||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
PopoverMenu::new("inline-context-picker")
|
PopoverMenu::new("inline-context-picker")
|
||||||
.menu(move |window, cx| {
|
.menu(move |cx| {
|
||||||
inline_context_picker.update(cx, |this, cx| {
|
inline_context_picker.update(cx, |this, cx| {
|
||||||
this.init(window, cx);
|
this.init(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(inline_context_picker.clone())
|
Some(inline_context_picker.clone())
|
||||||
@@ -383,7 +332,7 @@ impl Render for MessageEditor {
|
|||||||
.child(
|
.child(
|
||||||
Switch::new("use-tools", self.use_tools.into())
|
Switch::new("use-tools", self.use_tools.into())
|
||||||
.label("Tools")
|
.label("Tools")
|
||||||
.on_click(cx.listener(|this, selection, _window, _cx| {
|
.on_click(cx.listener(|this, selection, _cx| {
|
||||||
this.use_tools = match selection {
|
this.use_tools = match selection {
|
||||||
ToggleState::Selected => true,
|
ToggleState::Selected => true,
|
||||||
ToggleState::Unselected
|
ToggleState::Unselected
|
||||||
@@ -393,7 +342,7 @@ impl Render for MessageEditor {
|
|||||||
.key_binding(KeyBinding::for_action_in(
|
.key_binding(KeyBinding::for_action_in(
|
||||||
&ChatMode,
|
&ChatMode,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
cx,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.child(h_flex().gap_1().child(self.model_selector.clone()).child(
|
.child(h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||||
@@ -422,52 +371,35 @@ impl Render for MessageEditor {
|
|||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(
|
||||||
&editor::actions::Cancel,
|
&editor::actions::Cancel,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
cx,
|
||||||
)
|
)
|
||||||
.map(|binding| binding.into_any_element()),
|
.map(|binding| binding.into_any_element()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click(move |_event, cx| {
|
||||||
focus_handle.dispatch_action(
|
focus_handle
|
||||||
&editor::actions::Cancel,
|
.dispatch_action(&editor::actions::Cancel, cx);
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ButtonLike::new("submit-message")
|
ButtonLike::new("submit-message")
|
||||||
.width(button_width.into())
|
.width(button_width.into())
|
||||||
.style(ButtonStyle::Filled)
|
.style(ButtonStyle::Filled)
|
||||||
.disabled(is_editor_empty || !is_model_selected)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(Label::new("Submit").size(LabelSize::Small))
|
||||||
Label::new("Submit")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(submit_label_color),
|
|
||||||
)
|
|
||||||
.children(
|
.children(
|
||||||
KeyBinding::for_action_in(
|
KeyBinding::for_action_in(
|
||||||
&Chat,
|
&Chat,
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
window,
|
cx,
|
||||||
)
|
)
|
||||||
.map(|binding| binding.into_any_element()),
|
.map(|binding| binding.into_any_element()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click(move |_event, cx| {
|
||||||
focus_handle.dispatch_action(&Chat, window, cx);
|
focus_handle.dispatch_action(&Chat, cx);
|
||||||
})
|
|
||||||
.when(is_editor_empty, |button| {
|
|
||||||
button
|
|
||||||
.tooltip(Tooltip::text("Type a message to submit"))
|
|
||||||
})
|
|
||||||
.when(!is_model_selected, |button| {
|
|
||||||
button.tooltip(Tooltip::text(
|
|
||||||
"Select a model to continue",
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::inline_prompt_editor::CodegenStatus;
|
use crate::inline_prompt_editor::CodegenStatus;
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
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_model::{LanguageModelRegistry, LanguageModelRequest};
|
||||||
use language_models::report_assistant_event;
|
use language_models::report_assistant_event;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
@@ -11,7 +11,7 @@ use terminal::Terminal;
|
|||||||
pub struct TerminalCodegen {
|
pub struct TerminalCodegen {
|
||||||
pub status: CodegenStatus,
|
pub status: CodegenStatus,
|
||||||
pub telemetry: Option<Arc<Telemetry>>,
|
pub telemetry: Option<Arc<Telemetry>>,
|
||||||
terminal: Entity<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
generation: Task<()>,
|
generation: Task<()>,
|
||||||
pub message_id: Option<String>,
|
pub message_id: Option<String>,
|
||||||
transaction: Option<TerminalTransaction>,
|
transaction: Option<TerminalTransaction>,
|
||||||
@@ -20,7 +20,7 @@ pub struct TerminalCodegen {
|
|||||||
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
|
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
|
||||||
|
|
||||||
impl 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 {
|
Self {
|
||||||
terminal,
|
terminal,
|
||||||
telemetry,
|
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 {
|
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -131,20 +131,20 @@ impl TerminalCodegen {
|
|||||||
cx.notify();
|
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.status = CodegenStatus::Done;
|
||||||
self.generation = Task::ready(());
|
self.generation = Task::ready(());
|
||||||
cx.emit(CodegenEvent::Finished);
|
cx.emit(CodegenEvent::Finished);
|
||||||
cx.notify();
|
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() {
|
if let Some(transaction) = self.transaction.take() {
|
||||||
transaction.complete(cx);
|
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() {
|
if let Some(transaction) = self.transaction.take() {
|
||||||
transaction.undo(cx);
|
transaction.undo(cx);
|
||||||
}
|
}
|
||||||
@@ -160,27 +160,27 @@ pub const CLEAR_INPUT: &str = "\x15";
|
|||||||
const CARRIAGE_RETURN: &str = "\x0d";
|
const CARRIAGE_RETURN: &str = "\x0d";
|
||||||
|
|
||||||
struct TerminalTransaction {
|
struct TerminalTransaction {
|
||||||
terminal: Entity<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalTransaction {
|
impl TerminalTransaction {
|
||||||
pub fn start(terminal: Entity<Terminal>) -> Self {
|
pub fn start(terminal: Model<Terminal>) -> Self {
|
||||||
Self { terminal }
|
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
|
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||||
let input = Self::sanitize_input(hunk);
|
let input = Self::sanitize_input(hunk);
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(input));
|
.update(cx, |terminal, _| terminal.input(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&self, cx: &mut App) {
|
pub fn undo(&self, cx: &mut AppContext) {
|
||||||
self.terminal
|
self.terminal
|
||||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
.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, _| {
|
self.terminal.update(cx, |terminal, _| {
|
||||||
terminal.input(CARRIAGE_RETURN.to_string())
|
terminal.input(CARRIAGE_RETURN.to_string())
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ use client::telemetry::Telemetry;
|
|||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
use editor::{actions::SelectAll, MultiBuffer};
|
use editor::{actions::SelectAll, MultiBuffer};
|
||||||
use fs::Fs;
|
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::Buffer;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||||
@@ -28,7 +31,7 @@ pub fn init(
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
|
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
|
||||||
}
|
}
|
||||||
@@ -65,20 +68,20 @@ impl TerminalInlineAssistant {
|
|||||||
|
|
||||||
pub fn assist(
|
pub fn assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
terminal_view: &Entity<TerminalView>,
|
terminal_view: &View<TerminalView>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
let terminal = terminal_view.read(cx).terminal().clone();
|
let terminal = terminal_view.read(cx).terminal().clone();
|
||||||
let assist_id = self.next_assist_id.post_inc();
|
let assist_id = self.next_assist_id.post_inc();
|
||||||
let prompt_buffer =
|
let prompt_buffer = cx.new_model(|cx| {
|
||||||
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(String::new(), cx)), cx));
|
MultiBuffer::singleton(cx.new_model(|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 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(
|
PromptEditor::new_terminal(
|
||||||
assist_id,
|
assist_id,
|
||||||
self.prompt_history.clone(),
|
self.prompt_history.clone(),
|
||||||
@@ -88,7 +91,6 @@ impl TerminalInlineAssistant {
|
|||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -98,7 +100,7 @@ impl TerminalInlineAssistant {
|
|||||||
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
||||||
};
|
};
|
||||||
terminal_view.update(cx, |terminal_view, cx| {
|
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(
|
let terminal_assistant = TerminalInlineAssist::new(
|
||||||
@@ -107,27 +109,21 @@ impl TerminalInlineAssistant {
|
|||||||
prompt_editor,
|
prompt_editor,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
context_store,
|
context_store,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.assists.insert(assist_id, terminal_assistant);
|
self.assists.insert(assist_id, terminal_assistant);
|
||||||
|
|
||||||
self.focus_assist(assist_id, window, cx);
|
self.focus_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_assist(
|
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
|
||||||
&mut self,
|
|
||||||
assist_id: TerminalInlineAssistId,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
let assist = &self.assists[&assist_id];
|
let assist = &self.assists[&assist_id];
|
||||||
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
|
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
|
||||||
prompt_editor.update(cx, |this, cx| {
|
prompt_editor.update(cx, |this, cx| {
|
||||||
this.editor.update(cx, |editor, cx| {
|
this.editor.update(cx, |editor, cx| {
|
||||||
window.focus(&editor.focus_handle(cx));
|
editor.focus(cx);
|
||||||
editor.select_all(&SelectAll, window, cx);
|
editor.select_all(&SelectAll, cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -135,10 +131,9 @@ impl TerminalInlineAssistant {
|
|||||||
|
|
||||||
fn handle_prompt_editor_event(
|
fn handle_prompt_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
|
prompt_editor: View<PromptEditor<TerminalCodegen>>,
|
||||||
event: &PromptEditorEvent,
|
event: &PromptEditorEvent,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
let assist_id = prompt_editor.read(cx).id();
|
let assist_id = prompt_editor.read(cx).id();
|
||||||
match event {
|
match event {
|
||||||
@@ -149,21 +144,21 @@ impl TerminalInlineAssistant {
|
|||||||
self.stop_assist(assist_id, cx);
|
self.stop_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::ConfirmRequested { execute } => {
|
PromptEditorEvent::ConfirmRequested { execute } => {
|
||||||
self.finish_assist(assist_id, false, *execute, window, cx);
|
self.finish_assist(assist_id, false, *execute, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::CancelRequested => {
|
PromptEditorEvent::CancelRequested => {
|
||||||
self.finish_assist(assist_id, true, false, window, cx);
|
self.finish_assist(assist_id, true, false, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::DismissRequested => {
|
PromptEditorEvent::DismissRequested => {
|
||||||
self.dismiss_assist(assist_id, window, cx);
|
self.dismiss_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
PromptEditorEvent::Resized { height_in_lines } => {
|
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) {
|
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
assist
|
assist
|
||||||
} else {
|
} else {
|
||||||
@@ -201,7 +196,7 @@ impl TerminalInlineAssistant {
|
|||||||
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
|
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) {
|
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
assist
|
assist
|
||||||
} else {
|
} else {
|
||||||
@@ -214,7 +209,7 @@ impl TerminalInlineAssistant {
|
|||||||
fn request_for_inline_assist(
|
fn request_for_inline_assist(
|
||||||
&self,
|
&self,
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
cx: &mut App,
|
cx: &mut WindowContext,
|
||||||
) -> Result<LanguageModelRequest> {
|
) -> Result<LanguageModelRequest> {
|
||||||
let assist = self.assists.get(&assist_id).context("invalid assist")?;
|
let assist = self.assists.get(&assist_id).context("invalid assist")?;
|
||||||
|
|
||||||
@@ -222,7 +217,7 @@ impl TerminalInlineAssistant {
|
|||||||
let (latest_output, working_directory) = assist
|
let (latest_output, working_directory) = assist
|
||||||
.terminal
|
.terminal
|
||||||
.update(cx, |terminal, cx| {
|
.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 latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES);
|
||||||
let working_directory = terminal
|
let working_directory = terminal
|
||||||
.working_directory()
|
.working_directory()
|
||||||
@@ -270,17 +265,16 @@ impl TerminalInlineAssistant {
|
|||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
undo: bool,
|
undo: bool,
|
||||||
execute: bool,
|
execute: bool,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
self.dismiss_assist(assist_id, window, cx);
|
self.dismiss_assist(assist_id, cx);
|
||||||
|
|
||||||
if let Some(assist) = self.assists.remove(&assist_id) {
|
if let Some(assist) = self.assists.remove(&assist_id) {
|
||||||
assist
|
assist
|
||||||
.terminal
|
.terminal
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.clear_block_below_cursor(cx);
|
this.clear_block_below_cursor(cx);
|
||||||
this.focus_handle(cx).focus(window);
|
this.focus_handle(cx).focus(cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
@@ -323,8 +317,7 @@ impl TerminalInlineAssistant {
|
|||||||
fn dismiss_assist(
|
fn dismiss_assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(assist) = self.assists.get_mut(&assist_id) else {
|
let Some(assist) = self.assists.get_mut(&assist_id) else {
|
||||||
return false;
|
return false;
|
||||||
@@ -337,7 +330,7 @@ impl TerminalInlineAssistant {
|
|||||||
.terminal
|
.terminal
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.clear_block_below_cursor(cx);
|
this.clear_block_below_cursor(cx);
|
||||||
this.focus_handle(cx).focus(window);
|
this.focus_handle(cx).focus(cx);
|
||||||
})
|
})
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
@@ -346,8 +339,7 @@ impl TerminalInlineAssistant {
|
|||||||
&mut self,
|
&mut self,
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
height: u8,
|
height: u8,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
if let Some(assist) = self.assists.get_mut(&assist_id) {
|
if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||||
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
|
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
|
||||||
@@ -359,7 +351,7 @@ impl TerminalInlineAssistant {
|
|||||||
height,
|
height,
|
||||||
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
|
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();
|
.log_err();
|
||||||
}
|
}
|
||||||
@@ -368,23 +360,22 @@ impl TerminalInlineAssistant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TerminalInlineAssist {
|
struct TerminalInlineAssist {
|
||||||
terminal: WeakEntity<TerminalView>,
|
terminal: WeakView<TerminalView>,
|
||||||
prompt_editor: Option<Entity<PromptEditor<TerminalCodegen>>>,
|
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
|
||||||
codegen: Entity<TerminalCodegen>,
|
codegen: Model<TerminalCodegen>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalInlineAssist {
|
impl TerminalInlineAssist {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
assist_id: TerminalInlineAssistId,
|
assist_id: TerminalInlineAssistId,
|
||||||
terminal: &Entity<TerminalView>,
|
terminal: &View<TerminalView>,
|
||||||
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
|
prompt_editor: View<PromptEditor<TerminalCodegen>>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let codegen = prompt_editor.read(cx).codegen().clone();
|
let codegen = prompt_editor.read(cx).codegen().clone();
|
||||||
Self {
|
Self {
|
||||||
@@ -394,12 +385,12 @@ impl TerminalInlineAssist {
|
|||||||
workspace: workspace.clone(),
|
workspace: workspace.clone(),
|
||||||
context_store,
|
context_store,
|
||||||
_subscriptions: vec![
|
_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| {
|
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 {
|
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
|
||||||
CodegenEvent::Finished => {
|
CodegenEvent::Finished => {
|
||||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||||
@@ -428,7 +419,7 @@ impl TerminalInlineAssist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if assist.prompt_editor.is_none() {
|
if assist.prompt_editor.is_none() {
|
||||||
this.finish_assist(assist_id, false, false, window, cx);
|
this.finish_assist(assist_id, false, false, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
|
|||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use futures::{FutureExt as _, StreamExt as _};
|
use futures::{FutureExt as _, StreamExt as _};
|
||||||
use gpui::{App, Context, EventEmitter, SharedString, Task};
|
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||||
@@ -18,7 +18,6 @@ use util::{post_inc, TryFutureExt as _};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
||||||
use crate::thread_store::SavedThread;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum RequestKind {
|
pub enum RequestKind {
|
||||||
@@ -76,7 +75,7 @@ pub struct Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
Self {
|
||||||
id: ThreadId::new(),
|
id: ThreadId::new(),
|
||||||
updated_at: Utc::now(),
|
updated_at: Utc::now(),
|
||||||
@@ -95,40 +94,6 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_saved(
|
|
||||||
id: ThreadId,
|
|
||||||
saved: SavedThread,
|
|
||||||
tools: Arc<ToolWorkingSet>,
|
|
||||||
_cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let next_message_id = MessageId(saved.messages.len());
|
|
||||||
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
updated_at: saved.updated_at,
|
|
||||||
summary: Some(saved.summary),
|
|
||||||
pending_summary: Task::ready(None),
|
|
||||||
messages: saved
|
|
||||||
.messages
|
|
||||||
.into_iter()
|
|
||||||
.map(|message| Message {
|
|
||||||
id: message.id,
|
|
||||||
role: message.role,
|
|
||||||
text: message.text,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
next_message_id,
|
|
||||||
context: BTreeMap::default(),
|
|
||||||
context_by_message: HashMap::default(),
|
|
||||||
completion_count: 0,
|
|
||||||
pending_completions: Vec::new(),
|
|
||||||
tools,
|
|
||||||
tool_uses_by_message: HashMap::default(),
|
|
||||||
tool_results_by_message: HashMap::default(),
|
|
||||||
pending_tool_uses_by_id: HashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> &ThreadId {
|
pub fn id(&self) -> &ThreadId {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
@@ -154,7 +119,7 @@ impl Thread {
|
|||||||
self.summary.clone().unwrap_or(DEFAULT)
|
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());
|
self.summary = Some(summary.into());
|
||||||
cx.emit(ThreadEvent::SummaryChanged);
|
cx.emit(ThreadEvent::SummaryChanged);
|
||||||
}
|
}
|
||||||
@@ -194,7 +159,7 @@ impl Thread {
|
|||||||
&mut self,
|
&mut self,
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
context: Vec<ContextSnapshot>,
|
context: Vec<ContextSnapshot>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let message_id = self.insert_message(Role::User, text, cx);
|
let message_id = self.insert_message(Role::User, text, cx);
|
||||||
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
|
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
|
||||||
@@ -207,7 +172,7 @@ impl Thread {
|
|||||||
&mut self,
|
&mut self,
|
||||||
role: Role,
|
role: Role,
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> MessageId {
|
) -> MessageId {
|
||||||
let id = self.next_message_id.post_inc();
|
let id = self.next_message_id.post_inc();
|
||||||
self.messages.push(Message {
|
self.messages.push(Message {
|
||||||
@@ -244,7 +209,7 @@ impl Thread {
|
|||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
_request_kind: RequestKind,
|
_request_kind: RequestKind,
|
||||||
_cx: &App,
|
_cx: &AppContext,
|
||||||
) -> LanguageModelRequest {
|
) -> LanguageModelRequest {
|
||||||
let mut request = LanguageModelRequest {
|
let mut request = LanguageModelRequest {
|
||||||
messages: vec![],
|
messages: vec![],
|
||||||
@@ -314,7 +279,7 @@ impl Thread {
|
|||||||
&mut self,
|
&mut self,
|
||||||
request: LanguageModelRequest,
|
request: LanguageModelRequest,
|
||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let pending_completion_id = post_inc(&mut self.completion_count);
|
let pending_completion_id = post_inc(&mut self.completion_count);
|
||||||
|
|
||||||
@@ -439,7 +404,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 {
|
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -497,7 +462,7 @@ impl Thread {
|
|||||||
assistant_message_id: MessageId,
|
assistant_message_id: MessageId,
|
||||||
tool_use_id: LanguageModelToolUseId,
|
tool_use_id: LanguageModelToolUseId,
|
||||||
output: Task<Result<String>>,
|
output: Task<Result<String>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let insert_output_task = cx.spawn(|thread, mut cx| {
|
let insert_output_task = cx.spawn(|thread, mut cx| {
|
||||||
let tool_use_id = tool_use_id.clone();
|
let tool_use_id = tool_use_id.clone();
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
|
uniform_list, AppContext, FocusHandle, FocusableView, Model, ScrollStrategy,
|
||||||
WeakEntity,
|
UniformListScrollHandle, WeakView,
|
||||||
};
|
};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
||||||
|
|
||||||
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
|
use crate::thread::Thread;
|
||||||
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::{AssistantPanel, RemoveSelectedThread};
|
use crate::{AssistantPanel, RemoveSelectedThread};
|
||||||
|
|
||||||
pub struct ThreadHistory {
|
pub struct ThreadHistory {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Model<ThreadStore>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadHistory {
|
impl ThreadHistory {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Model<ThreadStore>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
@@ -32,83 +32,74 @@ impl ThreadHistory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_prev(
|
pub fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||||
_: &menu::SelectPrev,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let count = self.thread_store.read(cx).thread_count();
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if self.selected_index == 0 {
|
if self.selected_index == 0 {
|
||||||
self.set_selected_index(count - 1, window, cx);
|
self.set_selected_index(count - 1, cx);
|
||||||
} else {
|
} else {
|
||||||
self.set_selected_index(self.selected_index - 1, window, cx);
|
self.set_selected_index(self.selected_index - 1, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_next(
|
pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||||
_: &menu::SelectNext,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let count = self.thread_store.read(cx).thread_count();
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if self.selected_index == count - 1 {
|
if self.selected_index == count - 1 {
|
||||||
self.set_selected_index(0, window, cx);
|
self.set_selected_index(0, cx);
|
||||||
} else {
|
} 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();
|
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||||
if count > 0 {
|
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();
|
let count = self.thread_store.read(cx).non_empty_len(cx);
|
||||||
if count > 0 {
|
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.selected_index = index;
|
||||||
self.scroll_handle
|
self.scroll_handle
|
||||||
.scroll_to_item(index, ScrollStrategy::Top);
|
.scroll_to_item(index, ScrollStrategy::Top);
|
||||||
cx.notify();
|
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());
|
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||||
|
|
||||||
if let Some(thread) = threads.get(self.selected_index) {
|
if let Some(thread) = threads.get(self.selected_index) {
|
||||||
self.assistant_panel
|
self.assistant_panel
|
||||||
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
.update(cx, move |this, cx| {
|
||||||
|
let thread_id = thread.read(cx).id().clone();
|
||||||
|
this.open_thread(&thread_id, cx)
|
||||||
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_selected_thread(
|
fn remove_selected_thread(&mut self, _: &RemoveSelectedThread, cx: &mut ViewContext<Self>) {
|
||||||
&mut self,
|
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||||
_: &RemoveSelectedThread,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
|
||||||
|
|
||||||
if let Some(thread) = threads.get(self.selected_index) {
|
if let Some(thread) = threads.get(self.selected_index) {
|
||||||
self.assistant_panel
|
self.assistant_panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.delete_thread(&thread.id, cx);
|
let thread_id = thread.read(cx).id().clone();
|
||||||
|
this.delete_thread(&thread_id, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@@ -117,15 +108,15 @@ impl ThreadHistory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for ThreadHistory {
|
impl FocusableView for ThreadHistory {
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ThreadHistory {
|
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 threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
|
||||||
let selected_index = self.selected_index;
|
let selected_index = self.selected_index;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -154,10 +145,10 @@ impl Render for ThreadHistory {
|
|||||||
} else {
|
} else {
|
||||||
history.child(
|
history.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
cx.entity().clone(),
|
cx.view().clone(),
|
||||||
"thread-history",
|
"thread-history",
|
||||||
threads.len(),
|
threads.len(),
|
||||||
move |history, range, _window, _cx| {
|
move |history, range, _cx| {
|
||||||
threads[range]
|
threads[range]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -181,15 +172,15 @@ impl Render for ThreadHistory {
|
|||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct PastThread {
|
pub struct PastThread {
|
||||||
thread: SavedThreadMetadata,
|
thread: Model<Thread>,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PastThread {
|
impl PastThread {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread: SavedThreadMetadata,
|
thread: Model<Thread>,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -201,11 +192,15 @@ impl PastThread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for 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 (id, summary) = {
|
||||||
|
let thread = self.thread.read(cx);
|
||||||
|
(thread.id().clone(), thread.summary_or_default())
|
||||||
|
};
|
||||||
|
|
||||||
let thread_timestamp = time_format::format_localized_timestamp(
|
let thread_timestamp = time_format::format_localized_timestamp(
|
||||||
OffsetDateTime::from_unix_timestamp(self.thread.updated_at.timestamp()).unwrap(),
|
OffsetDateTime::from_unix_timestamp(self.thread.read(cx).updated_at().timestamp())
|
||||||
|
.unwrap(),
|
||||||
OffsetDateTime::now_utc(),
|
OffsetDateTime::now_utc(),
|
||||||
self.assistant_panel
|
self.assistant_panel
|
||||||
.update(cx, |this, _cx| this.local_timezone())
|
.update(cx, |this, _cx| this.local_timezone())
|
||||||
@@ -213,7 +208,7 @@ impl RenderOnce for PastThread {
|
|||||||
time_format::TimestampFormat::EnhancedAbsolute,
|
time_format::TimestampFormat::EnhancedAbsolute,
|
||||||
);
|
);
|
||||||
|
|
||||||
ListItem::new(SharedString::from(self.thread.id.to_string()))
|
ListItem::new(("past-thread", self.thread.entity_id()))
|
||||||
.outlined()
|
.outlined()
|
||||||
.toggle_state(self.selected)
|
.toggle_state(self.selected)
|
||||||
.start_slot(
|
.start_slot(
|
||||||
@@ -225,21 +220,21 @@ impl RenderOnce for PastThread {
|
|||||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||||
.end_slot(
|
.end_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
Label::new(thread_timestamp)
|
Label::new(thread_timestamp)
|
||||||
.color(Color::Muted)
|
.color(Color::Disabled)
|
||||||
.size(LabelSize::XSmall),
|
.size(LabelSize::Small),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("delete", IconName::TrashAlt)
|
IconButton::new("delete", IconName::TrashAlt)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::Small)
|
||||||
.tooltip(Tooltip::text("Delete Thread"))
|
.tooltip(|cx| Tooltip::text("Delete Thread", cx))
|
||||||
.on_click({
|
.on_click({
|
||||||
let assistant_panel = self.assistant_panel.clone();
|
let assistant_panel = self.assistant_panel.clone();
|
||||||
let id = self.thread.id.clone();
|
let id = id.clone();
|
||||||
move |_event, _window, cx| {
|
move |_event, cx| {
|
||||||
assistant_panel
|
assistant_panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.delete_thread(&id, cx);
|
this.delete_thread(&id, cx);
|
||||||
@@ -251,11 +246,11 @@ impl RenderOnce for PastThread {
|
|||||||
)
|
)
|
||||||
.on_click({
|
.on_click({
|
||||||
let assistant_panel = self.assistant_panel.clone();
|
let assistant_panel = self.assistant_panel.clone();
|
||||||
let id = self.thread.id.clone();
|
let id = id.clone();
|
||||||
move |_event, window, cx| {
|
move |_event, cx| {
|
||||||
assistant_panel
|
assistant_panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.open_thread(&id, window, cx).detach_and_log_err(cx);
|
this.open_thread(&id, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,171 +1,98 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::Result;
|
||||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::ContextServerManager;
|
||||||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||||
use futures::future::{self, BoxFuture, Shared};
|
use gpui::{prelude::*, AppContext, Model, ModelContext, Task};
|
||||||
use futures::FutureExt as _;
|
|
||||||
use gpui::{prelude::*, App, BackgroundExecutor, Context, Entity, SharedString, Task};
|
|
||||||
use heed::types::SerdeBincode;
|
|
||||||
use heed::Database;
|
|
||||||
use language_model::Role;
|
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use serde::{Deserialize, Serialize};
|
use unindent::Unindent;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
use crate::thread::{MessageId, Thread, ThreadId};
|
use crate::thread::{Thread, ThreadId};
|
||||||
|
|
||||||
pub struct ThreadStore {
|
pub struct ThreadStore {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Model<ContextServerManager>,
|
||||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||||
threads: Vec<SavedThreadMetadata>,
|
threads: Vec<Model<Thread>>,
|
||||||
database_future: Shared<BoxFuture<'static, Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadStore {
|
impl ThreadStore {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) -> Result<Entity<Self>> {
|
) -> Task<Result<Model<Self>>> {
|
||||||
let this = cx.new(|cx| {
|
cx.spawn(|mut cx| async move {
|
||||||
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
|
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||||
let context_server_manager = cx.new(|cx| {
|
let context_server_factory_registry =
|
||||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
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 mut this = Self {
|
||||||
let database_future = executor
|
project,
|
||||||
.spawn({
|
tools,
|
||||||
let executor = executor.clone();
|
context_server_manager,
|
||||||
let database_path = paths::support_dir().join("threads/threads-db.0.mdb");
|
context_server_tool_ids: HashMap::default(),
|
||||||
async move { ThreadsDatabase::new(database_path, executor) }
|
threads: Vec::new(),
|
||||||
})
|
};
|
||||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
this.mock_recent_threads(cx);
|
||||||
.boxed()
|
this.register_context_server_handlers(cx);
|
||||||
.shared();
|
|
||||||
|
|
||||||
let this = Self {
|
this
|
||||||
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);
|
|
||||||
|
|
||||||
this
|
Ok(this)
|
||||||
});
|
})
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of threads.
|
/// Returns the number of non-empty threads.
|
||||||
pub fn thread_count(&self) -> usize {
|
pub fn non_empty_len(&self, cx: &AppContext) -> usize {
|
||||||
self.threads.len()
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.filter(|thread| !thread.read(cx).is_empty())
|
||||||
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threads(&self) -> Vec<SavedThreadMetadata> {
|
pub fn threads(&self, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
|
||||||
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
|
let mut threads = self
|
||||||
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
|
.threads
|
||||||
|
.iter()
|
||||||
|
.filter(|thread| !thread.read(cx).is_empty())
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.read(cx).updated_at()));
|
||||||
threads
|
threads
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recent_threads(&self, limit: usize) -> Vec<SavedThreadMetadata> {
|
pub fn recent_threads(&self, limit: usize, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
|
||||||
self.threads().into_iter().take(limit).collect()
|
self.threads(cx).into_iter().take(limit).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> {
|
||||||
cx.new(|cx| Thread::new(self.tools.clone(), cx))
|
let thread = cx.new_model(|cx| Thread::new(self.tools.clone(), cx));
|
||||||
|
self.threads.push(thread.clone());
|
||||||
|
thread
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_thread(
|
pub fn open_thread(&self, id: &ThreadId, cx: &mut ModelContext<Self>) -> Option<Model<Thread>> {
|
||||||
&self,
|
self.threads
|
||||||
id: &ThreadId,
|
.iter()
|
||||||
cx: &mut Context<Self>,
|
.find(|thread| thread.read(cx).id() == id)
|
||||||
) -> Task<Result<Entity<Thread>>> {
|
.cloned()
|
||||||
let id = id.clone();
|
|
||||||
let database_future = self.database_future.clone();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
|
||||||
let thread = database
|
|
||||||
.try_find_thread(id.clone())
|
|
||||||
.await?
|
|
||||||
.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))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
|
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut ModelContext<Self>) {
|
||||||
let (metadata, thread) = thread.update(cx, |thread, _cx| {
|
self.threads.retain(|thread| thread.read(cx).id() != id);
|
||||||
let id = thread.id().clone();
|
|
||||||
let thread = SavedThread {
|
|
||||||
summary: thread.summary_or_default(),
|
|
||||||
updated_at: thread.updated_at(),
|
|
||||||
messages: thread
|
|
||||||
.messages()
|
|
||||||
.map(|message| SavedMessage {
|
|
||||||
id: message.id,
|
|
||||||
role: message.role,
|
|
||||||
text: message.text.clone(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
(id, thread)
|
|
||||||
});
|
|
||||||
|
|
||||||
let database_future = self.database_future.clone();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
|
||||||
database.save_thread(metadata, thread).await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| this.reload(cx))?.await
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
|
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
|
||||||
let id = id.clone();
|
|
||||||
let database_future = self.database_future.clone();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
|
||||||
database.delete_thread(id.clone()).await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _cx| {
|
|
||||||
this.threads.retain(|thread| thread.id != id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
|
||||||
let database_future = self.database_future.clone();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let threads = database_future
|
|
||||||
.await
|
|
||||||
.map_err(|err| anyhow!(err))?
|
|
||||||
.list_threads()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.threads = threads;
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
|
||||||
cx.subscribe(
|
cx.subscribe(
|
||||||
&self.context_server_manager.clone(),
|
&self.context_server_manager.clone(),
|
||||||
Self::handle_context_server_event,
|
Self::handle_context_server_event,
|
||||||
@@ -175,9 +102,9 @@ impl ThreadStore {
|
|||||||
|
|
||||||
fn handle_context_server_event(
|
fn handle_context_server_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Model<ContextServerManager>,
|
||||||
event: &context_server::manager::Event,
|
event: &context_server::manager::Event,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let tool_working_set = self.tools.clone();
|
let tool_working_set = self.tools.clone();
|
||||||
match event {
|
match event {
|
||||||
@@ -232,108 +159,133 @@ impl ThreadStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
impl ThreadStore {
|
||||||
pub struct SavedThreadMetadata {
|
/// Creates some mocked recent threads for testing purposes.
|
||||||
pub id: ThreadId,
|
fn mock_recent_threads(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
pub summary: SharedString,
|
use language_model::Role;
|
||||||
pub updated_at: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
self.threads.push(cx.new_model(|cx| {
|
||||||
pub struct SavedThread {
|
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||||
pub summary: SharedString,
|
thread.set_summary("Introduction to quantum computing", cx);
|
||||||
pub updated_at: DateTime<Utc>,
|
thread.insert_user_message("Hello! Can you help me understand quantum computing?", Vec::new(), cx);
|
||||||
pub messages: Vec<SavedMessage>,
|
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", cx);
|
||||||
}
|
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", Vec::new(), cx);
|
||||||
|
thread.insert_message(Role::Assistant, "Certainly! Quantum entanglement is a key principle used in quantum computing. When two qubits become entangled, the state of one qubit is directly related to the state of the other, regardless of the distance between them. This property is used in quantum computing to create complex quantum states and to perform operations on multiple qubits simultaneously. Entanglement allows quantum computers to process information in ways that classical computers cannot, potentially solving certain problems much more efficiently. For example, it's crucial in quantum error correction and in algorithms like quantum teleportation, which is important for quantum communication.", cx);
|
||||||
|
thread
|
||||||
|
}));
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
self.threads.push(cx.new_model(|cx| {
|
||||||
pub struct SavedMessage {
|
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||||
pub id: MessageId,
|
thread.set_summary("Rust web development and async programming", cx);
|
||||||
pub role: Role,
|
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", Vec::new(), cx);
|
||||||
pub text: String,
|
thread.insert_message(Role::Assistant, "Certainly! Here's an example of a simple web server in Rust using the `actix-web` framework:
|
||||||
}
|
|
||||||
|
|
||||||
struct ThreadsDatabase {
|
```rust
|
||||||
executor: BackgroundExecutor,
|
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
|
||||||
env: heed::Env,
|
|
||||||
threads: Database<SerdeBincode<ThreadId>, SerdeBincode<SavedThread>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadsDatabase {
|
async fn hello() -> impl Responder {
|
||||||
pub fn new(path: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
|
HttpResponse::Ok().body(\"Hello, World!\")
|
||||||
std::fs::create_dir_all(&path)?;
|
}
|
||||||
|
|
||||||
const ONE_GB_IN_BYTES: usize = 1024 * 1024 * 1024;
|
#[actix_web::main]
|
||||||
let env = unsafe {
|
async fn main() -> std::io::Result<()> {
|
||||||
heed::EnvOpenOptions::new()
|
HttpServer::new(|| {
|
||||||
.map_size(ONE_GB_IN_BYTES)
|
App::new()
|
||||||
.max_dbs(1)
|
.route(\"/\", web::get().to(hello))
|
||||||
.open(path)?
|
})
|
||||||
};
|
.bind(\"127.0.0.1:8080\")?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
let mut txn = env.write_txn()?;
|
This code creates a basic web server that responds with 'Hello, World!' when you access the root URL. Here's a breakdown of what's happening:
|
||||||
let threads = env.create_database(&mut txn, Some("threads"))?;
|
|
||||||
txn.commit()?;
|
|
||||||
|
|
||||||
Ok(Self {
|
1. We import necessary items from the `actix-web` crate.
|
||||||
executor,
|
2. We define an async `hello` function that returns a simple HTTP response.
|
||||||
env,
|
3. In the `main` function, we set up the server to listen on `127.0.0.1:8080`.
|
||||||
threads,
|
4. We configure the app to respond to GET requests on the root path with our `hello` function.
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_threads(&self) -> Task<Result<Vec<SavedThreadMetadata>>> {
|
To run this, you'd need to add `actix-web` to your `Cargo.toml` dependencies:
|
||||||
let env = self.env.clone();
|
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
```toml
|
||||||
let txn = env.read_txn()?;
|
[dependencies]
|
||||||
let mut iter = threads.iter(&txn)?;
|
actix-web = \"4.0\"
|
||||||
let mut threads = Vec::new();
|
```
|
||||||
while let Some((key, value)) = iter.next().transpose()? {
|
|
||||||
threads.push(SavedThreadMetadata {
|
Then you can run the server with `cargo run` and access it at `http://localhost:8080`.".unindent(), cx);
|
||||||
id: key,
|
thread.insert_user_message("That's great! Can you explain more about async functions in Rust?", Vec::new(), cx);
|
||||||
summary: value.summary,
|
thread.insert_message(Role::Assistant, "Certainly! Async functions are a key feature in Rust for writing efficient, non-blocking code, especially for I/O-bound operations. Here's an overview:
|
||||||
updated_at: value.updated_at,
|
|
||||||
});
|
1. **Syntax**: Async functions are declared using the `async` keyword:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn my_async_function() -> Result<(), Error> {
|
||||||
|
// Asynchronous code here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Futures**: Async functions return a `Future`. A `Future` represents a value that may not be available yet but will be at some point.
|
||||||
|
|
||||||
|
3. **Await**: Inside an async function, you can use the `.await` syntax to wait for other async operations to complete:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
async fn fetch_data() -> Result<String, Error> {
|
||||||
|
let response = make_http_request().await?;
|
||||||
|
let data = process_response(response).await?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Non-blocking**: Async functions allow the runtime to work on other tasks while waiting for I/O or other operations to complete, making efficient use of system resources.
|
||||||
|
|
||||||
|
5. **Runtime**: To execute async code, you need a runtime like `tokio` or `async-std`. Actix-web, which we used in the previous example, includes its own runtime.
|
||||||
|
|
||||||
|
6. **Error Handling**: Async functions work well with Rust's `?` operator for error handling.
|
||||||
|
|
||||||
|
Async programming in Rust provides a powerful way to write concurrent code that's both safe and efficient. It's particularly useful for servers, network programming, and any application that deals with many concurrent operations.".unindent(), cx);
|
||||||
|
thread
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.threads.push(cx.new_model(|cx| {
|
||||||
|
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||||
|
thread.set_summary("Rust code with long lines", cx);
|
||||||
|
thread.insert_user_message("Could you write me some Rust code with long lines?", Vec::new(), cx);
|
||||||
|
thread.insert_message(Role::Assistant, r#"Here's some Rust code with some intentionally long lines:
|
||||||
|
```rust
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let very_long_vector = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
|
||||||
|
|
||||||
|
let complicated_hashmap: HashMap<String, Vec<(i32, f64, String)>> = [("key1".to_string(), vec![(1, 1.1, "value1".to_string()), (2, 2.2, "value2".to_string())]), ("key2".to_string(), vec![(3, 3.3, "value3".to_string()), (4, 4.4, "value4".to_string())])].iter().cloned().collect();
|
||||||
|
|
||||||
|
let nested_structure = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
let long_closure = |x: i32, y: i32, z: i32| -> i32 { let result = x * y + z; println!("The result of the long closure calculation is: {}", result); result };
|
||||||
|
|
||||||
|
let thread_handles: Vec<_> = (0..10).map(|i| {
|
||||||
|
let nested_structure_clone = Arc::clone(&nested_structure);
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut lock = nested_structure_clone.lock().unwrap();
|
||||||
|
lock.entry(format!("thread_{}", i)).or_insert_with(|| HashSet::new()).insert(i * i);
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
for handle in thread_handles {
|
||||||
|
handle.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("The final state of the nested structure is: {:?}", nested_structure.lock().unwrap());
|
||||||
|
|
||||||
|
let complex_expression = very_long_vector.iter().filter(|&&x| x % 2 == 0).map(|&x| x * x).fold(0, |acc, x| acc + x) + long_closure(5, 10, 15);
|
||||||
|
|
||||||
|
println!("The result of the complex expression is: {}", complex_expression);
|
||||||
}
|
}
|
||||||
|
```"#.unindent(), cx);
|
||||||
Ok(threads)
|
thread
|
||||||
})
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_find_thread(&self, id: ThreadId) -> Task<Result<Option<SavedThread>>> {
|
|
||||||
let env = self.env.clone();
|
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
|
||||||
let txn = env.read_txn()?;
|
|
||||||
let thread = threads.get(&txn, &id)?;
|
|
||||||
Ok(thread)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_thread(&self, id: ThreadId, thread: SavedThread) -> Task<Result<()>> {
|
|
||||||
let env = self.env.clone();
|
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
|
||||||
let mut txn = env.write_txn()?;
|
|
||||||
threads.put(&mut txn, &id, &thread)?;
|
|
||||||
txn.commit()?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_thread(&self, id: ThreadId) -> Task<Result<()>> {
|
|
||||||
let env = self.env.clone();
|
|
||||||
let threads = self.threads;
|
|
||||||
|
|
||||||
self.executor.spawn(async move {
|
|
||||||
let mut txn = env.write_txn()?;
|
|
||||||
threads.delete(&mut txn, &id)?;
|
|
||||||
txn.commit()?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ pub enum ContextPill {
|
|||||||
context: ContextSnapshot,
|
context: ContextSnapshot,
|
||||||
dupe_name: bool,
|
dupe_name: bool,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
on_click: 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 Window, &mut App)>>,
|
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||||
},
|
},
|
||||||
Suggested {
|
Suggested {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
icon_path: Option<SharedString>,
|
icon_path: Option<SharedString>,
|
||||||
kind: ContextKind,
|
kind: ContextKind,
|
||||||
focused: bool,
|
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,
|
context: ContextSnapshot,
|
||||||
dupe_name: bool,
|
dupe_name: bool,
|
||||||
focused: 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 {
|
||||||
Self::Added {
|
Self::Added {
|
||||||
context,
|
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 {
|
match &mut self {
|
||||||
ContextPill::Added { on_click, .. } => {
|
ContextPill::Added { on_click, .. } => {
|
||||||
*on_click = Some(listener);
|
*on_click = Some(listener);
|
||||||
@@ -95,7 +95,7 @@ impl ContextPill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for 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 color = cx.theme().colors();
|
||||||
|
|
||||||
let base_pill = h_flex()
|
let base_pill = h_flex()
|
||||||
@@ -139,7 +139,7 @@ impl RenderOnce for ContextPill {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when_some(context.tooltip.clone(), |element, tooltip| {
|
.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| {
|
.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)
|
IconButton::new(("remove", context.id.0), IconName::Close)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.tooltip(Tooltip::text("Remove Context"))
|
.tooltip(|cx| Tooltip::text("Remove Context", cx))
|
||||||
.on_click({
|
.on_click({
|
||||||
let on_remove = on_remove.clone();
|
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| {
|
.when_some(on_click.as_ref(), |element, on_click| {
|
||||||
let on_click = on_click.clone();
|
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 {
|
ContextPill::Suggested {
|
||||||
name,
|
name,
|
||||||
@@ -195,12 +195,10 @@ impl RenderOnce for ContextPill {
|
|||||||
.size(IconSize::XSmall)
|
.size(IconSize::XSmall)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
|
||||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
|
|
||||||
})
|
|
||||||
.when_some(on_click.as_ref(), |element, on_click| {
|
.when_some(on_click.as_ref(), |element, on_click| {
|
||||||
let on_click = on_click.clone();
|
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))
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ anyhow.workspace = true
|
|||||||
assistant_settings.workspace = true
|
assistant_settings.workspace = true
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
assistant_slash_commands.workspace = true
|
assistant_slash_commands.workspace = true
|
||||||
|
assistant_tool.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
context_server.workspace = true
|
context_server.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
feature_flags.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ mod slash_command_picker;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use gpui::App;
|
use gpui::AppContext;
|
||||||
|
|
||||||
pub use crate::context::*;
|
pub use crate::context::*;
|
||||||
pub use crate::context_editor::*;
|
pub use crate::context_editor::*;
|
||||||
@@ -18,6 +18,6 @@ pub use crate::context_store::*;
|
|||||||
pub use crate::patch::*;
|
pub use crate::patch::*;
|
||||||
pub use crate::slash_command::*;
|
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());
|
context_store::init(&client.into());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,22 @@ use assistant_slash_command::{
|
|||||||
SlashCommandResult, SlashCommandWorkingSet,
|
SlashCommandResult, SlashCommandWorkingSet,
|
||||||
};
|
};
|
||||||
use assistant_slash_commands::FileCommandMetadata;
|
use assistant_slash_commands::FileCommandMetadata;
|
||||||
|
use assistant_tool::ToolWorkingSet;
|
||||||
use client::{self, proto, telemetry::Telemetry};
|
use client::{self, proto, telemetry::Telemetry};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
|
||||||
use fs::{Fs, RemoveOptions};
|
use fs::{Fs, RemoveOptions};
|
||||||
use futures::{future::Shared, FutureExt, StreamExt};
|
use futures::{future::Shared, FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
|
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString,
|
||||||
Task,
|
Subscription, Task,
|
||||||
};
|
};
|
||||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||||
|
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse,
|
||||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||||
};
|
};
|
||||||
use language_models::{
|
use language_models::{
|
||||||
@@ -435,6 +438,11 @@ pub enum ContextEvent {
|
|||||||
SlashCommandOutputSectionAdded {
|
SlashCommandOutputSectionAdded {
|
||||||
section: SlashCommandOutputSection<language::Anchor>,
|
section: SlashCommandOutputSection<language::Anchor>,
|
||||||
},
|
},
|
||||||
|
UsePendingTools,
|
||||||
|
ToolFinished {
|
||||||
|
tool_use_id: LanguageModelToolUseId,
|
||||||
|
output_range: Range<language::Anchor>,
|
||||||
|
},
|
||||||
Operation(ContextOperation),
|
Operation(ContextOperation),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,12 +528,21 @@ pub enum Content {
|
|||||||
render_image: Arc<RenderImage>,
|
render_image: Arc<RenderImage>,
|
||||||
image: Shared<Task<Option<LanguageModelImage>>>,
|
image: Shared<Task<Option<LanguageModelImage>>>,
|
||||||
},
|
},
|
||||||
|
ToolUse {
|
||||||
|
range: Range<language::Anchor>,
|
||||||
|
tool_use: LanguageModelToolUse,
|
||||||
|
},
|
||||||
|
ToolResult {
|
||||||
|
range: Range<language::Anchor>,
|
||||||
|
tool_use_id: LanguageModelToolUseId,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
fn range(&self) -> Range<language::Anchor> {
|
fn range(&self) -> Range<language::Anchor> {
|
||||||
match self {
|
match self {
|
||||||
Self::Image { anchor, .. } => *anchor..*anchor,
|
Self::Image { anchor, .. } => *anchor..*anchor,
|
||||||
|
Self::ToolUse { range, .. } | Self::ToolResult { range, .. } => range.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,18 +588,20 @@ pub enum XmlTagKind {
|
|||||||
Operation,
|
Operation,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AssistantContext {
|
pub struct Context {
|
||||||
id: ContextId,
|
id: ContextId,
|
||||||
timestamp: clock::Lamport,
|
timestamp: clock::Lamport,
|
||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
pending_ops: Vec<ContextOperation>,
|
pending_ops: Vec<ContextOperation>,
|
||||||
operations: Vec<ContextOperation>,
|
operations: Vec<ContextOperation>,
|
||||||
buffer: Entity<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
parsed_slash_commands: Vec<ParsedSlashCommand>,
|
parsed_slash_commands: Vec<ParsedSlashCommand>,
|
||||||
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
|
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
|
||||||
edits_since_last_parse: language::Subscription,
|
edits_since_last_parse: language::Subscription,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
|
tools: Arc<ToolWorkingSet>,
|
||||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||||
|
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||||
message_anchors: Vec<MessageAnchor>,
|
message_anchors: Vec<MessageAnchor>,
|
||||||
contents: Vec<Content>,
|
contents: Vec<Content>,
|
||||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
@@ -600,7 +619,7 @@ pub struct AssistantContext {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
patches: Vec<AssistantPatch>,
|
patches: Vec<AssistantPatch>,
|
||||||
xml_tags: Vec<XmlTag>,
|
xml_tags: Vec<XmlTag>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<Model<Project>>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
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(
|
pub fn local(
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<Model<Project>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
cx: &mut Context<Self>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
ContextId::new(),
|
ContextId::new(),
|
||||||
@@ -644,6 +664,7 @@ impl AssistantContext {
|
|||||||
language_registry,
|
language_registry,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
project,
|
project,
|
||||||
telemetry,
|
telemetry,
|
||||||
cx,
|
cx,
|
||||||
@@ -658,11 +679,12 @@ impl AssistantContext {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
project: Option<Entity<Project>>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
project: Option<Model<Project>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.new(|_cx| {
|
let buffer = cx.new_model(|_cx| {
|
||||||
let buffer = Buffer::remote(
|
let buffer = Buffer::remote(
|
||||||
language::BufferId::new(1).unwrap(),
|
language::BufferId::new(1).unwrap(),
|
||||||
replica_id,
|
replica_id,
|
||||||
@@ -685,6 +707,7 @@ impl AssistantContext {
|
|||||||
messages_metadata: Default::default(),
|
messages_metadata: Default::default(),
|
||||||
parsed_slash_commands: Vec::new(),
|
parsed_slash_commands: Vec::new(),
|
||||||
invoked_slash_commands: HashMap::default(),
|
invoked_slash_commands: HashMap::default(),
|
||||||
|
pending_tool_uses_by_id: HashMap::default(),
|
||||||
slash_command_output_sections: Vec::new(),
|
slash_command_output_sections: Vec::new(),
|
||||||
edits_since_last_parse: edits_since_last_slash_command_parse,
|
edits_since_last_parse: edits_since_last_slash_command_parse,
|
||||||
summary: None,
|
summary: None,
|
||||||
@@ -702,6 +725,7 @@ impl AssistantContext {
|
|||||||
project,
|
project,
|
||||||
language_registry,
|
language_registry,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
patches: Vec::new(),
|
patches: Vec::new(),
|
||||||
xml_tags: Vec::new(),
|
xml_tags: Vec::new(),
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
@@ -731,7 +755,7 @@ impl AssistantContext {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
|
pub(crate) fn serialize(&self, cx: &AppContext) -> SavedContext {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
SavedContext {
|
SavedContext {
|
||||||
id: Some(self.id.clone()),
|
id: Some(self.id.clone()),
|
||||||
@@ -778,9 +802,10 @@ impl AssistantContext {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
project: Option<Entity<Project>>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
|
project: Option<Model<Project>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
||||||
let mut this = Self::new(
|
let mut this = Self::new(
|
||||||
@@ -790,6 +815,7 @@ impl AssistantContext {
|
|||||||
language_registry,
|
language_registry,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
project,
|
project,
|
||||||
telemetry,
|
telemetry,
|
||||||
cx,
|
cx,
|
||||||
@@ -811,7 +837,7 @@ impl AssistantContext {
|
|||||||
self.timestamp.replica_id
|
self.timestamp.replica_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self, cx: &App) -> ContextVersion {
|
pub fn version(&self, cx: &AppContext) -> ContextVersion {
|
||||||
ContextVersion {
|
ContextVersion {
|
||||||
context: self.version.clone(),
|
context: self.version.clone(),
|
||||||
buffer: self.buffer.read(cx).version(),
|
buffer: self.buffer.read(cx).version(),
|
||||||
@@ -822,7 +848,15 @@ impl AssistantContext {
|
|||||||
&self.slash_commands
|
&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
|
self.buffer
|
||||||
.update(cx, |buffer, cx| buffer.set_capability(capability, cx));
|
.update(cx, |buffer, cx| buffer.set_capability(capability, cx));
|
||||||
}
|
}
|
||||||
@@ -836,7 +870,7 @@ impl AssistantContext {
|
|||||||
pub fn serialize_ops(
|
pub fn serialize_ops(
|
||||||
&self,
|
&self,
|
||||||
since: &ContextVersion,
|
since: &ContextVersion,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Task<Vec<proto::ContextOperation>> {
|
) -> Task<Vec<proto::ContextOperation>> {
|
||||||
let buffer_ops = self
|
let buffer_ops = self
|
||||||
.buffer
|
.buffer
|
||||||
@@ -871,7 +905,7 @@ impl AssistantContext {
|
|||||||
pub fn apply_ops(
|
pub fn apply_ops(
|
||||||
&mut self,
|
&mut self,
|
||||||
ops: impl IntoIterator<Item = ContextOperation>,
|
ops: impl IntoIterator<Item = ContextOperation>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let mut buffer_ops = Vec::new();
|
let mut buffer_ops = Vec::new();
|
||||||
for op in ops {
|
for op in ops {
|
||||||
@@ -885,7 +919,7 @@ impl AssistantContext {
|
|||||||
self.flush_ops(cx);
|
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 changed_messages = HashSet::default();
|
||||||
let mut summary_changed = false;
|
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()) {
|
if !self.version.observed_all(op.version()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1035,7 +1069,7 @@ impl AssistantContext {
|
|||||||
fn has_received_operations_for_anchor_range(
|
fn has_received_operations_for_anchor_range(
|
||||||
&self,
|
&self,
|
||||||
range: Range<text::Anchor>,
|
range: Range<text::Anchor>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let version = &self.buffer.read(cx).version;
|
let version = &self.buffer.read(cx).version;
|
||||||
let observed_start = range.start == language::Anchor::MIN
|
let observed_start = range.start == language::Anchor::MIN
|
||||||
@@ -1047,12 +1081,12 @@ impl AssistantContext {
|
|||||||
observed_start && observed_end
|
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());
|
self.operations.push(op.clone());
|
||||||
cx.emit(ContextEvent::Operation(op));
|
cx.emit(ContextEvent::Operation(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer(&self) -> &Entity<Buffer> {
|
pub fn buffer(&self) -> &Model<Buffer> {
|
||||||
&self.buffer
|
&self.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,7 +1094,7 @@ impl AssistantContext {
|
|||||||
self.language_registry.clone()
|
self.language_registry.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project(&self) -> Option<Entity<Project>> {
|
pub fn project(&self) -> Option<Model<Project>> {
|
||||||
self.project.clone()
|
self.project.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,7 +1110,7 @@ impl AssistantContext {
|
|||||||
self.summary.as_ref()
|
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 buffer = self.buffer.read(cx);
|
||||||
let index = self.patches.binary_search_by(|patch| {
|
let index = self.patches.binary_search_by(|patch| {
|
||||||
let patch_range = patch.range.to_point(&buffer);
|
let patch_range = patch.range.to_point(&buffer);
|
||||||
@@ -1102,7 +1136,7 @@ impl AssistantContext {
|
|||||||
pub fn patch_for_range(
|
pub fn patch_for_range(
|
||||||
&self,
|
&self,
|
||||||
range: &Range<language::Anchor>,
|
range: &Range<language::Anchor>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Option<&AssistantPatch> {
|
) -> Option<&AssistantPatch> {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
let index = self.patch_index_for_range(range, buffer).ok()?;
|
let index = self.patch_index_for_range(range, buffer).ok()?;
|
||||||
@@ -1133,7 +1167,7 @@ impl AssistantContext {
|
|||||||
&self.slash_command_output_sections
|
&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);
|
let buffer = self.buffer.read(cx);
|
||||||
self.slash_command_output_sections.iter().any(|section| {
|
self.slash_command_output_sections.iter().any(|section| {
|
||||||
section.is_valid(buffer)
|
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");
|
let markdown = self.language_registry.language_for_name("Markdown");
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let markdown = markdown.await?;
|
let markdown = markdown.await?;
|
||||||
@@ -1161,9 +1203,9 @@ impl AssistantContext {
|
|||||||
|
|
||||||
fn handle_buffer_event(
|
fn handle_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Entity<Buffer>,
|
_: Model<Buffer>,
|
||||||
event: &language::BufferEvent,
|
event: &language::BufferEvent,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
language::BufferEvent::Operation {
|
language::BufferEvent::Operation {
|
||||||
@@ -1185,7 +1227,7 @@ impl AssistantContext {
|
|||||||
self.token_count
|
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),
|
// 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.
|
// 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);
|
let request = self.to_completion_request(RequestType::Chat, cx);
|
||||||
@@ -1213,7 +1255,7 @@ impl AssistantContext {
|
|||||||
&mut self,
|
&mut self,
|
||||||
cache_configuration: &Option<LanguageModelCacheConfiguration>,
|
cache_configuration: &Option<LanguageModelCacheConfiguration>,
|
||||||
speculative: bool,
|
speculative: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let cache_configuration =
|
let cache_configuration =
|
||||||
cache_configuration
|
cache_configuration
|
||||||
@@ -1315,7 +1357,7 @@ impl AssistantContext {
|
|||||||
new_anchor_needs_caching
|
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();
|
let cache_configuration = model.cache_configuration();
|
||||||
|
|
||||||
if !self.mark_cache_anchors(&cache_configuration, true, cx) {
|
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
|
let cached_message_ids: Vec<MessageId> = self
|
||||||
.messages_metadata
|
.messages_metadata
|
||||||
.iter()
|
.iter()
|
||||||
@@ -1390,7 +1432,7 @@ impl AssistantContext {
|
|||||||
cx.notify();
|
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 buffer = self.buffer.read(cx).text_snapshot();
|
||||||
let mut row_ranges = self
|
let mut row_ranges = self
|
||||||
.edits_since_last_parse
|
.edits_since_last_parse
|
||||||
@@ -1463,7 +1505,7 @@ impl AssistantContext {
|
|||||||
buffer: &BufferSnapshot,
|
buffer: &BufferSnapshot,
|
||||||
updated: &mut Vec<ParsedSlashCommand>,
|
updated: &mut Vec<ParsedSlashCommand>,
|
||||||
removed: &mut Vec<Range<text::Anchor>>,
|
removed: &mut Vec<Range<text::Anchor>>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) {
|
) {
|
||||||
let old_range = self.pending_command_indices_for_range(range.clone(), cx);
|
let old_range = self.pending_command_indices_for_range(range.clone(), cx);
|
||||||
|
|
||||||
@@ -1517,7 +1559,7 @@ impl AssistantContext {
|
|||||||
fn invalidate_pending_slash_commands(
|
fn invalidate_pending_slash_commands(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &BufferSnapshot,
|
buffer: &BufferSnapshot,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let mut invalidated_command_ids = Vec::new();
|
let mut invalidated_command_ids = Vec::new();
|
||||||
for (&command_id, command) in self.invoked_slash_commands.iter_mut() {
|
for (&command_id, command) in self.invoked_slash_commands.iter_mut() {
|
||||||
@@ -1551,7 +1593,7 @@ impl AssistantContext {
|
|||||||
buffer: &BufferSnapshot,
|
buffer: &BufferSnapshot,
|
||||||
updated: &mut Vec<Range<text::Anchor>>,
|
updated: &mut Vec<Range<text::Anchor>>,
|
||||||
removed: &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.
|
// Rebuild the XML tags in the edited range.
|
||||||
let intersecting_tags_range =
|
let intersecting_tags_range =
|
||||||
@@ -1594,7 +1636,7 @@ impl AssistantContext {
|
|||||||
&self,
|
&self,
|
||||||
buffer: &BufferSnapshot,
|
buffer: &BufferSnapshot,
|
||||||
range: Range<text::Anchor>,
|
range: Range<text::Anchor>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Vec<XmlTag> {
|
) -> Vec<XmlTag> {
|
||||||
let mut messages = self.messages(cx).peekable();
|
let mut messages = self.messages(cx).peekable();
|
||||||
|
|
||||||
@@ -1651,7 +1693,7 @@ impl AssistantContext {
|
|||||||
tags_start_ix: usize,
|
tags_start_ix: usize,
|
||||||
buffer_end: text::Anchor,
|
buffer_end: text::Anchor,
|
||||||
buffer: &BufferSnapshot,
|
buffer: &BufferSnapshot,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Vec<AssistantPatch> {
|
) -> Vec<AssistantPatch> {
|
||||||
let mut new_patches = Vec::new();
|
let mut new_patches = Vec::new();
|
||||||
let mut pending_patch = None;
|
let mut pending_patch = None;
|
||||||
@@ -1809,7 +1851,7 @@ impl AssistantContext {
|
|||||||
pub fn pending_command_for_position(
|
pub fn pending_command_for_position(
|
||||||
&mut self,
|
&mut self,
|
||||||
position: language::Anchor,
|
position: language::Anchor,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<&mut ParsedSlashCommand> {
|
) -> Option<&mut ParsedSlashCommand> {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
match self
|
match self
|
||||||
@@ -1833,7 +1875,7 @@ impl AssistantContext {
|
|||||||
pub fn pending_commands_for_range(
|
pub fn pending_commands_for_range(
|
||||||
&self,
|
&self,
|
||||||
range: Range<language::Anchor>,
|
range: Range<language::Anchor>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> &[ParsedSlashCommand] {
|
) -> &[ParsedSlashCommand] {
|
||||||
let range = self.pending_command_indices_for_range(range, cx);
|
let range = self.pending_command_indices_for_range(range, cx);
|
||||||
&self.parsed_slash_commands[range]
|
&self.parsed_slash_commands[range]
|
||||||
@@ -1842,7 +1884,7 @@ impl AssistantContext {
|
|||||||
fn pending_command_indices_for_range(
|
fn pending_command_indices_for_range(
|
||||||
&self,
|
&self,
|
||||||
range: Range<language::Anchor>,
|
range: Range<language::Anchor>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Range<usize> {
|
) -> Range<usize> {
|
||||||
self.indices_intersecting_buffer_range(&self.parsed_slash_commands, range, cx)
|
self.indices_intersecting_buffer_range(&self.parsed_slash_commands, range, cx)
|
||||||
}
|
}
|
||||||
@@ -1851,7 +1893,7 @@ impl AssistantContext {
|
|||||||
&self,
|
&self,
|
||||||
all_annotations: &[T],
|
all_annotations: &[T],
|
||||||
range: Range<language::Anchor>,
|
range: Range<language::Anchor>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Range<usize> {
|
) -> Range<usize> {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
let start_ix = match all_annotations
|
let start_ix = match all_annotations
|
||||||
@@ -1874,7 +1916,7 @@ impl AssistantContext {
|
|||||||
name: &str,
|
name: &str,
|
||||||
output: Task<SlashCommandResult>,
|
output: Task<SlashCommandResult>,
|
||||||
ensure_trailing_newline: bool,
|
ensure_trailing_newline: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let version = self.version.clone();
|
let version = self.version.clone();
|
||||||
let command_id = InvokedSlashCommandId(self.next_timestamp());
|
let command_id = InvokedSlashCommandId(self.next_timestamp());
|
||||||
@@ -2142,7 +2184,7 @@ impl AssistantContext {
|
|||||||
fn insert_slash_command_output_section(
|
fn insert_slash_command_output_section(
|
||||||
&mut self,
|
&mut self,
|
||||||
section: SlashCommandOutputSection<language::Anchor>,
|
section: SlashCommandOutputSection<language::Anchor>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
let insertion_ix = match self
|
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);
|
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| {
|
self.message_anchors.iter().rev().find_map(|message| {
|
||||||
message
|
message
|
||||||
.start
|
.start
|
||||||
@@ -2184,7 +2288,7 @@ impl AssistantContext {
|
|||||||
pub fn assist(
|
pub fn assist(
|
||||||
&mut self,
|
&mut self,
|
||||||
request_type: RequestType,
|
request_type: RequestType,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<MessageAnchor> {
|
) -> Option<MessageAnchor> {
|
||||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||||
let provider = model_registry.active_provider()?;
|
let provider = model_registry.active_provider()?;
|
||||||
@@ -2198,7 +2302,23 @@ impl AssistantContext {
|
|||||||
// Compute which messages to cache, including the last one.
|
// Compute which messages to cache, including the last one.
|
||||||
self.mark_cache_anchors(&model.cache_configuration(), false, cx);
|
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
|
let assistant_message = self
|
||||||
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
|
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
|
||||||
@@ -2255,7 +2375,44 @@ impl AssistantContext {
|
|||||||
cx,
|
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 {
|
if let Ok(stop_reason) = result {
|
||||||
match stop_reason {
|
match stop_reason {
|
||||||
StopReason::ToolUse => {}
|
StopReason::ToolUse => {
|
||||||
|
cx.emit(ContextEvent::UsePendingTools);
|
||||||
|
}
|
||||||
StopReason::EndTurn => {}
|
StopReason::EndTurn => {}
|
||||||
StopReason::MaxTokens => {}
|
StopReason::MaxTokens => {}
|
||||||
}
|
}
|
||||||
@@ -2360,7 +2519,7 @@ impl AssistantContext {
|
|||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
request_type: RequestType,
|
request_type: RequestType,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> LanguageModelRequest {
|
) -> LanguageModelRequest {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
|
|
||||||
@@ -2417,6 +2576,23 @@ impl AssistantContext {
|
|||||||
.push(language_model::MessageContent::Image(image));
|
.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;
|
offset = range.end;
|
||||||
@@ -2455,7 +2631,7 @@ impl AssistantContext {
|
|||||||
completion_request
|
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() {
|
if let Some(pending_completion) = self.pending_completions.pop() {
|
||||||
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
|
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
|
||||||
if metadata.status == MessageStatus::Pending {
|
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 {
|
for id in &ids {
|
||||||
if let Some(metadata) = self.messages_metadata.get(id) {
|
if let Some(metadata) = self.messages_metadata.get(id) {
|
||||||
let role = metadata.role.cycle();
|
let role = metadata.role.cycle();
|
||||||
@@ -2479,7 +2655,7 @@ impl AssistantContext {
|
|||||||
self.message_roles_updated(ids, cx);
|
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();
|
let mut ranges = Vec::new();
|
||||||
for message in self.messages(cx) {
|
for message in self.messages(cx) {
|
||||||
if ids.contains(&message.id) {
|
if ids.contains(&message.id) {
|
||||||
@@ -2502,7 +2678,7 @@ impl AssistantContext {
|
|||||||
pub fn update_metadata(
|
pub fn update_metadata(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: MessageId,
|
id: MessageId,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
f: impl FnOnce(&mut MessageMetadata),
|
f: impl FnOnce(&mut MessageMetadata),
|
||||||
) {
|
) {
|
||||||
let version = self.version.clone();
|
let version = self.version.clone();
|
||||||
@@ -2526,7 +2702,7 @@ impl AssistantContext {
|
|||||||
message_id: MessageId,
|
message_id: MessageId,
|
||||||
role: Role,
|
role: Role,
|
||||||
status: MessageStatus,
|
status: MessageStatus,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<MessageAnchor> {
|
) -> Option<MessageAnchor> {
|
||||||
if let Some(prev_message_ix) = self
|
if let Some(prev_message_ix) = self
|
||||||
.message_anchors
|
.message_anchors
|
||||||
@@ -2560,7 +2736,7 @@ impl AssistantContext {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
role: Role,
|
role: Role,
|
||||||
status: MessageStatus,
|
status: MessageStatus,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> MessageAnchor {
|
) -> MessageAnchor {
|
||||||
let start = self.buffer.update(cx, |buffer, cx| {
|
let start = self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.edit([(offset..offset, "\n")], None, cx);
|
buffer.edit([(offset..offset, "\n")], None, cx);
|
||||||
@@ -2590,7 +2766,7 @@ impl AssistantContext {
|
|||||||
anchor
|
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 buffer = self.buffer.read(cx);
|
||||||
let insertion_ix = match self
|
let insertion_ix = match self
|
||||||
.contents
|
.contents
|
||||||
@@ -2606,7 +2782,7 @@ impl AssistantContext {
|
|||||||
cx.emit(ContextEvent::MessagesEdited);
|
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);
|
let buffer = self.buffer.read(cx);
|
||||||
self.contents
|
self.contents
|
||||||
.iter()
|
.iter()
|
||||||
@@ -2620,7 +2796,7 @@ impl AssistantContext {
|
|||||||
pub fn split_message(
|
pub fn split_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
|
) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
|
||||||
let start_message = self.message_for_offset(range.start, cx);
|
let start_message = self.message_for_offset(range.start, cx);
|
||||||
let end_message = self.message_for_offset(range.end, cx);
|
let end_message = self.message_for_offset(range.end, cx);
|
||||||
@@ -2746,7 +2922,7 @@ impl AssistantContext {
|
|||||||
&mut self,
|
&mut self,
|
||||||
new_anchor: MessageAnchor,
|
new_anchor: MessageAnchor,
|
||||||
new_metadata: MessageMetadata,
|
new_metadata: MessageMetadata,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
cx.emit(ContextEvent::MessagesEdited);
|
cx.emit(ContextEvent::MessagesEdited);
|
||||||
|
|
||||||
@@ -2764,7 +2940,7 @@ impl AssistantContext {
|
|||||||
self.message_anchors.insert(insertion_ix, new_anchor);
|
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 {
|
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||||
return;
|
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()
|
self.messages_for_offsets([offset], cx).pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn messages_for_offsets(
|
pub fn messages_for_offsets(
|
||||||
&self,
|
&self,
|
||||||
offsets: impl IntoIterator<Item = usize>,
|
offsets: impl IntoIterator<Item = usize>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Vec<Message> {
|
) -> Vec<Message> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
@@ -2882,14 +3058,14 @@ impl AssistantContext {
|
|||||||
fn messages_from_anchors<'a>(
|
fn messages_from_anchors<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
message_anchors: impl Iterator<Item = &'a MessageAnchor> + 'a,
|
message_anchors: impl Iterator<Item = &'a MessageAnchor> + 'a,
|
||||||
cx: &'a App,
|
cx: &'a AppContext,
|
||||||
) -> impl 'a + Iterator<Item = Message> {
|
) -> impl 'a + Iterator<Item = Message> {
|
||||||
let buffer = self.buffer.read(cx);
|
let buffer = self.buffer.read(cx);
|
||||||
|
|
||||||
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
|
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)
|
self.messages_from_anchors(self.message_anchors.iter(), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2937,7 +3113,7 @@ impl AssistantContext {
|
|||||||
&mut self,
|
&mut self,
|
||||||
debounce: Option<Duration>,
|
debounce: Option<Duration>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut Context<AssistantContext>,
|
cx: &mut ModelContext<Context>,
|
||||||
) {
|
) {
|
||||||
if self.replica_id() != ReplicaId::default() {
|
if self.replica_id() != ReplicaId::default() {
|
||||||
// Prevent saving a remote context for now.
|
// 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 timestamp = self.next_timestamp();
|
||||||
let summary = self.summary.get_or_insert(ContextSummary::default());
|
let summary = self.summary.get_or_insert(ContextSummary::default());
|
||||||
summary.timestamp = timestamp;
|
summary.timestamp = timestamp;
|
||||||
@@ -3163,8 +3339,8 @@ impl SavedContext {
|
|||||||
|
|
||||||
fn into_ops(
|
fn into_ops(
|
||||||
self,
|
self,
|
||||||
buffer: &Entity<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
cx: &mut Context<AssistantContext>,
|
cx: &mut ModelContext<Context>,
|
||||||
) -> Vec<ContextOperation> {
|
) -> Vec<ContextOperation> {
|
||||||
let mut operations = Vec::new();
|
let mut operations = Vec::new();
|
||||||
let mut version = clock::Global::new();
|
let mut version = clock::Global::new();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AssistantContext, AssistantEdit, AssistantEditKind, CacheStatus, ContextEvent, ContextId,
|
AssistantEdit, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId,
|
||||||
ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
|
ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -8,13 +8,14 @@ use assistant_slash_command::{
|
|||||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
|
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
|
||||||
};
|
};
|
||||||
use assistant_slash_commands::FileSlashCommand;
|
use assistant_slash_commands::FileSlashCommand;
|
||||||
|
use assistant_tool::ToolWorkingSet;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
stream::{self, StreamExt},
|
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::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
||||||
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -33,7 +34,7 @@ use std::{
|
|||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
||||||
use ui::{IconName, Window};
|
use ui::{IconName, WindowContext};
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::{
|
use util::{
|
||||||
test::{generate_marked_text, marked_text_ranges},
|
test::{generate_marked_text, marked_text_ranges},
|
||||||
@@ -42,19 +43,20 @@ use util::{
|
|||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
#[gpui::test]
|
#[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);
|
let settings_store = SettingsStore::test(cx);
|
||||||
LanguageModelRegistry::test(cx);
|
LanguageModelRegistry::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry,
|
registry,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -181,20 +183,21 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_message_splitting(cx: &mut App) {
|
fn test_message_splitting(cx: &mut AppContext) {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
LanguageModelRegistry::test(cx);
|
LanguageModelRegistry::test(cx);
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||||
|
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -284,19 +287,20 @@ fn test_message_splitting(cx: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_messages_for_offsets(cx: &mut App) {
|
fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
LanguageModelRegistry::test(cx);
|
LanguageModelRegistry::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry,
|
registry,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -363,9 +367,9 @@ fn test_messages_for_offsets(cx: &mut App) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fn message_ids_for_offsets(
|
fn message_ids_for_offsets(
|
||||||
context: &Entity<AssistantContext>,
|
context: &Model<Context>,
|
||||||
offsets: &[usize],
|
offsets: &[usize],
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Vec<MessageId> {
|
) -> Vec<MessageId> {
|
||||||
context
|
context
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -403,13 +407,14 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -603,7 +608,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_text_and_context_ranges(
|
fn assert_text_and_context_ranges(
|
||||||
buffer: &Entity<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
ranges: &RefCell<ContextRanges>,
|
ranges: &RefCell<ContextRanges>,
|
||||||
expected_marked_text: &str,
|
expected_marked_text: &str,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
@@ -692,13 +697,14 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
// Create a new context
|
// Create a new context
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
Some(project),
|
Some(project),
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -956,13 +962,14 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
// Ensure steps are re-parsed when deserializing.
|
// Ensure steps are re-parsed when deserializing.
|
||||||
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
|
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
|
||||||
let deserialized_context = cx.new(|cx| {
|
let deserialized_context = cx.new_model(|cx| {
|
||||||
AssistantContext::deserialize(
|
Context::deserialize(
|
||||||
serialized_context,
|
serialized_context,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
@@ -999,11 +1006,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn edit(
|
fn edit(context: &Model<Context>, new_text_marked_with_edits: &str, cx: &mut TestAppContext) {
|
||||||
context: &Entity<AssistantContext>,
|
|
||||||
new_text_marked_with_edits: &str,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) {
|
|
||||||
context.update(cx, |context, cx| {
|
context.update(cx, |context, cx| {
|
||||||
context.buffer.update(cx, |buffer, cx| {
|
context.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.edit_via_marked_text(&new_text_marked_with_edits.unindent(), None, 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]
|
#[track_caller]
|
||||||
fn expect_patches(
|
fn expect_patches(
|
||||||
context: &Entity<AssistantContext>,
|
context: &Model<Context>,
|
||||||
expected_marked_text: &str,
|
expected_marked_text: &str,
|
||||||
expected_suggestions: &[&[AssistantEdit]],
|
expected_suggestions: &[&[AssistantEdit]],
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
@@ -1074,13 +1077,14 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
|||||||
cx.update(LanguageModelRegistry::test);
|
cx.update(LanguageModelRegistry::test);
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
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 serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
|
||||||
let deserialized_context = cx.new(|cx| {
|
let deserialized_context = cx.new_model(|cx| {
|
||||||
AssistantContext::deserialize(
|
Context::deserialize(
|
||||||
serialized_context,
|
serialized_context,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
@@ -1174,14 +1179,15 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
let context_id = ContextId::new();
|
let context_id = ContextId::new();
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
for i in 0..num_peers {
|
for i in 0..num_peers {
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::new(
|
Context::new(
|
||||||
context_id.clone(),
|
context_id.clone(),
|
||||||
i as ReplicaId,
|
i as ReplicaId,
|
||||||
language::Capability::ReadWrite,
|
language::Capability::ReadWrite,
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
@@ -1428,19 +1434,20 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_mark_cache_anchors(cx: &mut App) {
|
fn test_mark_cache_anchors(cx: &mut AppContext) {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
LanguageModelRegistry::test(cx);
|
LanguageModelRegistry::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
registry,
|
registry,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
Arc::new(SlashCommandWorkingSet::default()),
|
Arc::new(SlashCommandWorkingSet::default()),
|
||||||
|
Arc::new(ToolWorkingSet::default()),
|
||||||
cx,
|
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
|
context
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.messages(cx)
|
.messages(cx)
|
||||||
@@ -1596,8 +1603,8 @@ fn messages(context: &Entity<AssistantContext>, cx: &App) -> Vec<(MessageId, Rol
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn messages_cache(
|
fn messages_cache(
|
||||||
context: &Entity<AssistantContext>,
|
context: &Model<Context>,
|
||||||
cx: &App,
|
cx: &AppContext,
|
||||||
) -> Vec<(MessageId, Option<MessageCacheMetadata>)> {
|
) -> Vec<(MessageId, Option<MessageCacheMetadata>)> {
|
||||||
context
|
context
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -1626,9 +1633,8 @@ impl SlashCommand for FakeSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakEntity<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
_cx: &mut WindowContext,
|
||||||
_cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
Task::ready(Ok(vec![]))
|
Task::ready(Ok(vec![]))
|
||||||
}
|
}
|
||||||
@@ -1642,10 +1648,9 @@ impl SlashCommand for FakeSlashCommand {
|
|||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: BufferSnapshot,
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakEntity<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_window: &mut Window,
|
_cx: &mut WindowContext,
|
||||||
_cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
Task::ready(Ok(SlashCommandOutput {
|
Task::ready(Ok(SlashCommandOutput {
|
||||||
text: format!("Executed fake command: {}", self.0),
|
text: format!("Executed fake command: {}", self.0),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
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 picker::{Picker, PickerDelegate};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use ui::utils::{format_distance_from_now, DateTimeType};
|
use ui::utils::{format_distance_from_now, DateTimeType};
|
||||||
@@ -23,23 +25,21 @@ enum SavedContextPickerEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ContextHistory {
|
pub struct ContextHistory {
|
||||||
picker: Entity<Picker<SavedContextPickerDelegate>>,
|
picker: View<Picker<SavedContextPickerDelegate>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextHistory {
|
impl ContextHistory {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let picker = cx.new(|cx| {
|
let picker = cx.new_view(|cx| {
|
||||||
Picker::uniform_list(
|
Picker::uniform_list(
|
||||||
SavedContextPickerDelegate::new(project, context_store.clone()),
|
SavedContextPickerDelegate::new(project, context_store.clone()),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.modal(false)
|
.modal(false)
|
||||||
@@ -47,11 +47,10 @@ impl ContextHistory {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.observe_in(&context_store, window, |this, _, window, cx| {
|
cx.observe(&context_store, |this, _, cx| {
|
||||||
this.picker
|
this.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||||
.update(cx, |picker, cx| picker.refresh(window, cx));
|
|
||||||
}),
|
}),
|
||||||
cx.subscribe_in(&picker, window, Self::handle_picker_event),
|
cx.subscribe(&picker, Self::handle_picker_event),
|
||||||
];
|
];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -63,10 +62,9 @@ impl ContextHistory {
|
|||||||
|
|
||||||
fn handle_picker_event(
|
fn handle_picker_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &Entity<Picker<SavedContextPickerDelegate>>,
|
_: View<Picker<SavedContextPickerDelegate>>,
|
||||||
event: &SavedContextPickerEvent,
|
event: &SavedContextPickerEvent,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Self>,
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let SavedContextPickerEvent::Confirmed(context) = event;
|
let SavedContextPickerEvent::Confirmed(context) = event;
|
||||||
|
|
||||||
@@ -78,12 +76,12 @@ impl ContextHistory {
|
|||||||
.update(cx, |workspace, cx| match context {
|
.update(cx, |workspace, cx| match context {
|
||||||
ContextMetadata::Remote(metadata) => {
|
ContextMetadata::Remote(metadata) => {
|
||||||
assistant_panel_delegate
|
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);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
ContextMetadata::Saved(metadata) => {
|
ContextMetadata::Saved(metadata) => {
|
||||||
assistant_panel_delegate
|
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);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -92,13 +90,13 @@ impl ContextHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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())
|
div().size_full().child(self.picker.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for ContextHistory {
|
impl FocusableView for ContextHistory {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.picker.focus_handle(cx)
|
self.picker.focus_handle(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,14 +106,14 @@ impl EventEmitter<()> for ContextHistory {}
|
|||||||
impl Item for ContextHistory {
|
impl Item for ContextHistory {
|
||||||
type Event = ();
|
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())
|
Some("History".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SavedContextPickerDelegate {
|
struct SavedContextPickerDelegate {
|
||||||
store: Entity<ContextStore>,
|
store: Model<ContextStore>,
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
matches: Vec<ContextMetadata>,
|
matches: Vec<ContextMetadata>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
@@ -123,7 +121,7 @@ struct SavedContextPickerDelegate {
|
|||||||
impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
|
impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
|
||||||
|
|
||||||
impl SavedContextPickerDelegate {
|
impl SavedContextPickerDelegate {
|
||||||
fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {
|
fn new(project: Model<Project>, store: Model<ContextStore>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
project,
|
project,
|
||||||
store,
|
store,
|
||||||
@@ -144,25 +142,15 @@ impl PickerDelegate for SavedContextPickerDelegate {
|
|||||||
self.selected_index
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected_index(
|
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||||
&mut self,
|
|
||||||
ix: usize,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
self.selected_index = ix;
|
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()
|
"Search...".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
_window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let search = self.store.read(cx).search(query, cx);
|
let search = self.store.read(cx).search(query, cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let matches = search.await;
|
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) {
|
if let Some(metadata) = self.matches.get(self.selected_index) {
|
||||||
cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
|
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(
|
fn render_match(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_window: &mut Window,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let context = self.matches.get(ix)?;
|
let context = self.matches.get(ix)?;
|
||||||
let item = match context {
|
let item = match context {
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
|
||||||
SavedContextMetadata,
|
SavedContextMetadata,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
||||||
|
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::ContextServerManager;
|
||||||
use context_server::ContextServerFactoryRegistry;
|
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use fuzzy::StringMatchCandidate;
|
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 language::LanguageRegistry;
|
||||||
use paths::contexts_dir;
|
use paths::contexts_dir;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
@@ -31,11 +34,11 @@ use std::{
|
|||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
pub(crate) fn init(client: &AnyProtoClient) {
|
pub(crate) fn init(client: &AnyProtoClient) {
|
||||||
client.add_entity_message_handler(ContextStore::handle_advertise_contexts);
|
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
|
||||||
client.add_entity_request_handler(ContextStore::handle_open_context);
|
client.add_model_request_handler(ContextStore::handle_open_context);
|
||||||
client.add_entity_request_handler(ContextStore::handle_create_context);
|
client.add_model_request_handler(ContextStore::handle_create_context);
|
||||||
client.add_entity_message_handler(ContextStore::handle_update_context);
|
client.add_model_message_handler(ContextStore::handle_update_context);
|
||||||
client.add_entity_request_handler(ContextStore::handle_synchronize_contexts);
|
client.add_model_request_handler(ContextStore::handle_synchronize_contexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -47,16 +50,18 @@ pub struct RemoteContextMetadata {
|
|||||||
pub struct ContextStore {
|
pub struct ContextStore {
|
||||||
contexts: Vec<ContextHandle>,
|
contexts: Vec<ContextHandle>,
|
||||||
contexts_metadata: Vec<SavedContextMetadata>,
|
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_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
|
||||||
|
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||||
host_contexts: Vec<RemoteContextMetadata>,
|
host_contexts: Vec<RemoteContextMetadata>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
|
tools: Arc<ToolWorkingSet>,
|
||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
_watch_updates: Task<Option<()>>,
|
_watch_updates: Task<Option<()>>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
project_is_shared: bool,
|
project_is_shared: bool,
|
||||||
client_subscription: Option<client::Subscription>,
|
client_subscription: Option<client::Subscription>,
|
||||||
_project_subscriptions: Vec<gpui::Subscription>,
|
_project_subscriptions: Vec<gpui::Subscription>,
|
||||||
@@ -70,19 +75,19 @@ pub enum ContextStoreEvent {
|
|||||||
impl EventEmitter<ContextStoreEvent> for ContextStore {}
|
impl EventEmitter<ContextStoreEvent> for ContextStore {}
|
||||||
|
|
||||||
enum ContextHandle {
|
enum ContextHandle {
|
||||||
Weak(WeakEntity<AssistantContext>),
|
Weak(WeakModel<Context>),
|
||||||
Strong(Entity<AssistantContext>),
|
Strong(Model<Context>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextHandle {
|
impl ContextHandle {
|
||||||
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
|
fn upgrade(&self) -> Option<Model<Context>> {
|
||||||
match self {
|
match self {
|
||||||
ContextHandle::Weak(weak) => weak.upgrade(),
|
ContextHandle::Weak(weak) => weak.upgrade(),
|
||||||
ContextHandle::Strong(strong) => Some(strong.clone()),
|
ContextHandle::Strong(strong) => Some(strong.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade(&self) -> WeakEntity<AssistantContext> {
|
fn downgrade(&self) -> WeakModel<Context> {
|
||||||
match self {
|
match self {
|
||||||
ContextHandle::Weak(weak) => weak.clone(),
|
ContextHandle::Weak(weak) => weak.clone(),
|
||||||
ContextHandle::Strong(strong) => strong.downgrade(),
|
ContextHandle::Strong(strong) => strong.downgrade(),
|
||||||
@@ -92,11 +97,12 @@ impl ContextHandle {
|
|||||||
|
|
||||||
impl ContextStore {
|
impl ContextStore {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
cx: &mut App,
|
tools: Arc<ToolWorkingSet>,
|
||||||
) -> Task<Result<Entity<Self>>> {
|
cx: &mut AppContext,
|
||||||
|
) -> Task<Result<Model<Self>>> {
|
||||||
let fs = project.read(cx).fs().clone();
|
let fs = project.read(cx).fs().clone();
|
||||||
let languages = project.read(cx).languages().clone();
|
let languages = project.read(cx).languages().clone();
|
||||||
let telemetry = project.read(cx).client().telemetry().clone();
|
let telemetry = project.read(cx).client().telemetry().clone();
|
||||||
@@ -104,10 +110,10 @@ impl ContextStore {
|
|||||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
||||||
|
|
||||||
let this = cx.new(|cx: &mut Context<Self>| {
|
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||||
let context_server_factory_registry =
|
let context_server_factory_registry =
|
||||||
ContextServerFactoryRegistry::default_global(cx);
|
ContextServerFactoryRegistry::default_global(cx);
|
||||||
let context_server_manager = cx.new(|cx| {
|
let context_server_manager = cx.new_model(|cx| {
|
||||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||||
});
|
});
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
@@ -115,10 +121,12 @@ impl ContextStore {
|
|||||||
contexts_metadata: Vec::new(),
|
contexts_metadata: Vec::new(),
|
||||||
context_server_manager,
|
context_server_manager,
|
||||||
context_server_slash_command_ids: HashMap::default(),
|
context_server_slash_command_ids: HashMap::default(),
|
||||||
|
context_server_tool_ids: HashMap::default(),
|
||||||
host_contexts: Vec::new(),
|
host_contexts: Vec::new(),
|
||||||
fs,
|
fs,
|
||||||
languages,
|
languages,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
telemetry,
|
telemetry,
|
||||||
_watch_updates: cx.spawn(|this, mut cx| {
|
_watch_updates: cx.spawn(|this, mut cx| {
|
||||||
async move {
|
async move {
|
||||||
@@ -144,18 +152,20 @@ impl ContextStore {
|
|||||||
this.handle_project_changed(project.clone(), cx);
|
this.handle_project_changed(project.clone(), cx);
|
||||||
this.synchronize_contexts(cx);
|
this.synchronize_contexts(cx);
|
||||||
this.register_context_server_handlers(cx);
|
this.register_context_server_handlers(cx);
|
||||||
this.reload(cx).detach_and_log_err(cx);
|
|
||||||
this
|
this
|
||||||
})?;
|
})?;
|
||||||
|
this.update(&mut cx, |this, cx| this.reload(cx))?
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
|
||||||
Ok(this)
|
Ok(this)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_advertise_contexts(
|
async fn handle_advertise_contexts(
|
||||||
this: Entity<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::AdvertiseContexts>,
|
envelope: TypedEnvelope<proto::AdvertiseContexts>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.host_contexts = envelope
|
this.host_contexts = envelope
|
||||||
@@ -172,9 +182,9 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_open_context(
|
async fn handle_open_context(
|
||||||
this: Entity<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::OpenContext>,
|
envelope: TypedEnvelope<proto::OpenContext>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::OpenContextResponse> {
|
) -> Result<proto::OpenContextResponse> {
|
||||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||||
let operations = this.update(&mut cx, |this, cx| {
|
let operations = this.update(&mut cx, |this, cx| {
|
||||||
@@ -202,9 +212,9 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_create_context(
|
async fn handle_create_context(
|
||||||
this: Entity<Self>,
|
this: Model<Self>,
|
||||||
_: TypedEnvelope<proto::CreateContext>,
|
_: TypedEnvelope<proto::CreateContext>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::CreateContextResponse> {
|
) -> Result<proto::CreateContextResponse> {
|
||||||
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
||||||
if this.project.read(cx).is_via_collab() {
|
if this.project.read(cx).is_via_collab() {
|
||||||
@@ -230,9 +240,9 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_update_context(
|
async fn handle_update_context(
|
||||||
this: Entity<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::UpdateContext>,
|
envelope: TypedEnvelope<proto::UpdateContext>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||||
@@ -246,9 +256,9 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_synchronize_contexts(
|
async fn handle_synchronize_contexts(
|
||||||
this: Entity<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::SynchronizeContexts>,
|
envelope: TypedEnvelope<proto::SynchronizeContexts>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::SynchronizeContextsResponse> {
|
) -> Result<proto::SynchronizeContextsResponse> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if this.project.read(cx).is_via_collab() {
|
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 is_shared = self.project.read(cx).is_shared();
|
||||||
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
|
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
|
||||||
if is_shared == was_shared {
|
if is_shared == was_shared {
|
||||||
@@ -310,7 +320,7 @@ impl ContextStore {
|
|||||||
.client
|
.client
|
||||||
.subscribe_to_entity(remote_id)
|
.subscribe_to_entity(remote_id)
|
||||||
.log_err()
|
.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);
|
self.advertise_contexts(cx);
|
||||||
} else {
|
} else {
|
||||||
self.client_subscription = None;
|
self.client_subscription = None;
|
||||||
@@ -319,9 +329,9 @@ impl ContextStore {
|
|||||||
|
|
||||||
fn handle_project_event(
|
fn handle_project_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Entity<Project>,
|
_: Model<Project>,
|
||||||
event: &project::Event,
|
event: &project::Event,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
project::Event::Reshared => {
|
project::Event::Reshared => {
|
||||||
@@ -351,14 +361,15 @@ impl ContextStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> {
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::local(
|
Context::local(
|
||||||
self.languages.clone(),
|
self.languages.clone(),
|
||||||
Some(self.project.clone()),
|
Some(self.project.clone()),
|
||||||
Some(self.telemetry.clone()),
|
Some(self.telemetry.clone()),
|
||||||
self.prompt_builder.clone(),
|
self.prompt_builder.clone(),
|
||||||
self.slash_commands.clone(),
|
self.slash_commands.clone(),
|
||||||
|
self.tools.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -368,8 +379,8 @@ impl ContextStore {
|
|||||||
|
|
||||||
pub fn create_remote_context(
|
pub fn create_remote_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Entity<AssistantContext>>> {
|
) -> Task<Result<Model<Context>>> {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow!("project was not remote")));
|
||||||
@@ -382,19 +393,21 @@ impl ContextStore {
|
|||||||
let telemetry = self.telemetry.clone();
|
let telemetry = self.telemetry.clone();
|
||||||
let prompt_builder = self.prompt_builder.clone();
|
let prompt_builder = self.prompt_builder.clone();
|
||||||
let slash_commands = self.slash_commands.clone();
|
let slash_commands = self.slash_commands.clone();
|
||||||
|
let tools = self.tools.clone();
|
||||||
let request = self.client.request(proto::CreateContext { project_id });
|
let request = self.client.request(proto::CreateContext { project_id });
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let response = request.await?;
|
let response = request.await?;
|
||||||
let context_id = ContextId::from_proto(response.context_id);
|
let context_id = ContextId::from_proto(response.context_id);
|
||||||
let context_proto = response.context.context("invalid context")?;
|
let context_proto = response.context.context("invalid context")?;
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::new(
|
Context::new(
|
||||||
context_id.clone(),
|
context_id.clone(),
|
||||||
replica_id,
|
replica_id,
|
||||||
capability,
|
capability,
|
||||||
language_registry,
|
language_registry,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
Some(project),
|
Some(project),
|
||||||
Some(telemetry),
|
Some(telemetry),
|
||||||
cx,
|
cx,
|
||||||
@@ -426,8 +439,8 @@ impl ContextStore {
|
|||||||
pub fn open_local_context(
|
pub fn open_local_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
cx: &Context<Self>,
|
cx: &ModelContext<Self>,
|
||||||
) -> Task<Result<Entity<AssistantContext>>> {
|
) -> Task<Result<Model<Context>>> {
|
||||||
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
|
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
|
||||||
return Task::ready(Ok(existing_context));
|
return Task::ready(Ok(existing_context));
|
||||||
}
|
}
|
||||||
@@ -445,16 +458,18 @@ impl ContextStore {
|
|||||||
});
|
});
|
||||||
let prompt_builder = self.prompt_builder.clone();
|
let prompt_builder = self.prompt_builder.clone();
|
||||||
let slash_commands = self.slash_commands.clone();
|
let slash_commands = self.slash_commands.clone();
|
||||||
|
let tools = self.tools.clone();
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let saved_context = load.await?;
|
let saved_context = load.await?;
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::deserialize(
|
Context::deserialize(
|
||||||
saved_context,
|
saved_context,
|
||||||
path.clone(),
|
path.clone(),
|
||||||
languages,
|
languages,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
Some(project),
|
Some(project),
|
||||||
Some(telemetry),
|
Some(telemetry),
|
||||||
cx,
|
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| {
|
self.contexts.iter().find_map(|context| {
|
||||||
let context = context.upgrade()?;
|
let context = context.upgrade()?;
|
||||||
if context.read(cx).path() == Some(path) {
|
if context.read(cx).path() == Some(path) {
|
||||||
@@ -482,11 +497,7 @@ impl ContextStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loaded_context_for_id(
|
pub fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
|
||||||
&self,
|
|
||||||
id: &ContextId,
|
|
||||||
cx: &App,
|
|
||||||
) -> Option<Entity<AssistantContext>> {
|
|
||||||
self.contexts.iter().find_map(|context| {
|
self.contexts.iter().find_map(|context| {
|
||||||
let context = context.upgrade()?;
|
let context = context.upgrade()?;
|
||||||
if context.read(cx).id() == id {
|
if context.read(cx).id() == id {
|
||||||
@@ -500,8 +511,8 @@ impl ContextStore {
|
|||||||
pub fn open_remote_context(
|
pub fn open_remote_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
context_id: ContextId,
|
context_id: ContextId,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Entity<AssistantContext>>> {
|
) -> Task<Result<Model<Context>>> {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow!("project was not remote")));
|
||||||
@@ -522,17 +533,19 @@ impl ContextStore {
|
|||||||
});
|
});
|
||||||
let prompt_builder = self.prompt_builder.clone();
|
let prompt_builder = self.prompt_builder.clone();
|
||||||
let slash_commands = self.slash_commands.clone();
|
let slash_commands = self.slash_commands.clone();
|
||||||
|
let tools = self.tools.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let response = request.await?;
|
let response = request.await?;
|
||||||
let context_proto = response.context.context("invalid context")?;
|
let context_proto = response.context.context("invalid context")?;
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new_model(|cx| {
|
||||||
AssistantContext::new(
|
Context::new(
|
||||||
context_id.clone(),
|
context_id.clone(),
|
||||||
replica_id,
|
replica_id,
|
||||||
capability,
|
capability,
|
||||||
language_registry,
|
language_registry,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
slash_commands,
|
slash_commands,
|
||||||
|
tools,
|
||||||
Some(project),
|
Some(project),
|
||||||
Some(telemetry),
|
Some(telemetry),
|
||||||
cx,
|
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 {
|
let handle = if self.project_is_shared {
|
||||||
ContextHandle::Strong(context.clone())
|
ContextHandle::Strong(context.clone())
|
||||||
} else {
|
} else {
|
||||||
@@ -574,9 +587,9 @@ impl ContextStore {
|
|||||||
|
|
||||||
fn handle_context_event(
|
fn handle_context_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: Entity<AssistantContext>,
|
context: Model<Context>,
|
||||||
event: &ContextEvent,
|
event: &ContextEvent,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||||
return;
|
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 {
|
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -635,7 +648,7 @@ impl ContextStore {
|
|||||||
.ok();
|
.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 {
|
let Some(project_id) = self.project.read(cx).remote_id() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -690,7 +703,7 @@ impl ContextStore {
|
|||||||
.detach_and_log_err(cx);
|
.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 metadata = self.contexts_metadata.clone();
|
||||||
let executor = cx.background_executor().clone();
|
let executor = cx.background_executor().clone();
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
@@ -724,7 +737,7 @@ impl ContextStore {
|
|||||||
&self.host_contexts
|
&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();
|
let fs = self.fs.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
fs.create_dir(contexts_dir()).await?;
|
fs.create_dir(contexts_dir()).await?;
|
||||||
@@ -773,8 +786,8 @@ impl ContextStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restart_context_servers(&mut self, cx: &mut Context<Self>) {
|
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
cx.update_entity(
|
cx.update_model(
|
||||||
&self.context_server_manager,
|
&self.context_server_manager,
|
||||||
|context_server_manager, cx| {
|
|context_server_manager, cx| {
|
||||||
for server in context_server_manager.servers() {
|
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(
|
cx.subscribe(
|
||||||
&self.context_server_manager.clone(),
|
&self.context_server_manager.clone(),
|
||||||
Self::handle_context_server_event,
|
Self::handle_context_server_event,
|
||||||
@@ -796,11 +809,12 @@ impl ContextStore {
|
|||||||
|
|
||||||
fn handle_context_server_event(
|
fn handle_context_server_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
context_server_manager: Entity<ContextServerManager>,
|
context_server_manager: Model<ContextServerManager>,
|
||||||
event: &context_server::manager::Event,
|
event: &context_server::manager::Event,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let slash_command_working_set = self.slash_commands.clone();
|
let slash_command_working_set = self.slash_commands.clone();
|
||||||
|
let tool_working_set = self.tools.clone();
|
||||||
match event {
|
match event {
|
||||||
context_server::manager::Event::ServerStarted { server_id } => {
|
context_server::manager::Event::ServerStarted { server_id } => {
|
||||||
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
||||||
@@ -840,6 +854,29 @@ impl ContextStore {
|
|||||||
.log_err();
|
.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();
|
.detach();
|
||||||
@@ -851,6 +888,10 @@ impl ContextStore {
|
|||||||
{
|
{
|
||||||
slash_command_working_set.remove(&slash_command_ids);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context as _, Result};
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::ProposedChangesEditor;
|
use editor::ProposedChangesEditor;
|
||||||
use futures::{future, TryFutureExt as _};
|
use futures::{future, TryFutureExt as _};
|
||||||
use gpui::{App, AsyncApp, Entity, SharedString};
|
use gpui::{AppContext, AsyncAppContext, Model, SharedString};
|
||||||
use language::{AutoindentMode, Buffer, BufferSnapshot};
|
use language::{AutoindentMode, Buffer, BufferSnapshot};
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use std::{cmp, ops::Range, path::Path, sync::Arc};
|
use std::{cmp, ops::Range, path::Path, sync::Arc};
|
||||||
@@ -56,7 +56,7 @@ pub enum AssistantEditKind {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct ResolvedPatch {
|
pub struct ResolvedPatch {
|
||||||
pub edit_groups: HashMap<Entity<Buffer>, Vec<ResolvedEditGroup>>,
|
pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>,
|
||||||
pub errors: Vec<AssistantPatchResolutionError>,
|
pub errors: Vec<AssistantPatchResolutionError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ impl SearchMatrix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedPatch {
|
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 {
|
for (buffer, groups) in &self.edit_groups {
|
||||||
let branch = editor.branch_buffer_for_base(buffer).unwrap();
|
let branch = editor.branch_buffer_for_base(buffer).unwrap();
|
||||||
Self::apply_edit_groups(groups, &branch, cx);
|
Self::apply_edit_groups(groups, &branch, cx);
|
||||||
@@ -129,7 +129,11 @@ impl ResolvedPatch {
|
|||||||
editor.recalculate_all_buffer_diffs();
|
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();
|
let mut edits = Vec::new();
|
||||||
for group in groups {
|
for group in groups {
|
||||||
for suggestion in &group.edits {
|
for suggestion in &group.edits {
|
||||||
@@ -228,9 +232,9 @@ impl AssistantEdit {
|
|||||||
|
|
||||||
pub async fn resolve(
|
pub async fn resolve(
|
||||||
&self,
|
&self,
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<(Entity<Buffer>, ResolvedEdit)> {
|
) -> Result<(Model<Buffer>, ResolvedEdit)> {
|
||||||
let path = self.path.clone();
|
let path = self.path.clone();
|
||||||
let kind = self.kind.clone();
|
let kind = self.kind.clone();
|
||||||
let buffer = project
|
let buffer = project
|
||||||
@@ -421,7 +425,11 @@ impl AssistantEditKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantPatch {
|
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();
|
let mut resolve_tasks = Vec::new();
|
||||||
for (ix, edit) in self.edits.iter().enumerate() {
|
for (ix, edit) in self.edits.iter().enumerate() {
|
||||||
if let Ok(edit) = edit.as_ref() {
|
if let Ok(edit) = edit.as_ref() {
|
||||||
@@ -547,7 +555,7 @@ impl Eq for AssistantPatch {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::{App, AppContext as _};
|
use gpui::{AppContext, Context};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
|
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
|
||||||
};
|
};
|
||||||
@@ -557,7 +565,7 @@ mod tests {
|
|||||||
use util::test::{generate_marked_text, marked_text_ranges};
|
use util::test::{generate_marked_text, marked_text_ranges};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_resolve_location(cx: &mut App) {
|
fn test_resolve_location(cx: &mut AppContext) {
|
||||||
assert_location_resolution(
|
assert_location_resolution(
|
||||||
concat!(
|
concat!(
|
||||||
" Lorem\n",
|
" Lorem\n",
|
||||||
@@ -628,7 +636,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_resolve_edits(cx: &mut App) {
|
fn test_resolve_edits(cx: &mut AppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
||||||
assert_edits(
|
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);
|
let settings_store = SettingsStore::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
@@ -904,9 +912,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[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 (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 snapshot = buffer.read(cx).snapshot();
|
||||||
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
|
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
|
||||||
let text_with_actual_range = generate_marked_text(&text, &[range], false);
|
let text_with_actual_range = generate_marked_text(&text, &[range], false);
|
||||||
@@ -918,10 +930,10 @@ mod tests {
|
|||||||
old_text: String,
|
old_text: String,
|
||||||
edits: Vec<AssistantEditKind>,
|
edits: Vec<AssistantEditKind>,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let buffer =
|
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 snapshot = buffer.read(cx).snapshot();
|
||||||
let resolved_edits = edits
|
let resolved_edits = edits
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ pub use assistant_slash_command::SlashCommand;
|
|||||||
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
|
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
|
||||||
use editor::{CompletionProvider, Editor};
|
use editor::{CompletionProvider, Editor};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{App, Context, Entity, Task, WeakEntity, Window};
|
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
|
||||||
use language::{Anchor, Buffer, CompletionDocumentation, LanguageServerId, ToPoint};
|
use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::CompletionIntent;
|
use project::CompletionIntent;
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
@@ -23,15 +23,15 @@ use workspace::Workspace;
|
|||||||
pub struct SlashCommandCompletionProvider {
|
pub struct SlashCommandCompletionProvider {
|
||||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
editor: Option<WeakEntity<ContextEditor>>,
|
editor: Option<WeakView<ContextEditor>>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SlashCommandCompletionProvider {
|
impl SlashCommandCompletionProvider {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
editor: Option<WeakEntity<ContextEditor>>,
|
editor: Option<WeakView<ContextEditor>>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
|
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
|
||||||
@@ -46,8 +46,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
command_name: &str,
|
command_name: &str,
|
||||||
command_range: Range<Anchor>,
|
command_range: Range<Anchor>,
|
||||||
name_range: Range<Anchor>,
|
name_range: Range<Anchor>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<project::Completion>>> {
|
) -> Task<Result<Vec<project::Completion>>> {
|
||||||
let slash_commands = self.slash_commands.clone();
|
let slash_commands = self.slash_commands.clone();
|
||||||
let candidates = slash_commands
|
let candidates = slash_commands
|
||||||
@@ -59,7 +58,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
let command_name = command_name.to_string();
|
let command_name = command_name.to_string();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
window.spawn(cx, |mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let matches = match_strings(
|
let matches = match_strings(
|
||||||
&candidates,
|
&candidates,
|
||||||
&command_name,
|
&command_name,
|
||||||
@@ -70,7 +69,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx.update(|_, cx| {
|
cx.update(|cx| {
|
||||||
matches
|
matches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|mat| {
|
.filter_map(|mat| {
|
||||||
@@ -92,37 +91,32 @@ impl SlashCommandCompletionProvider {
|
|||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
let workspace = workspace.clone();
|
let workspace = workspace.clone();
|
||||||
Arc::new(
|
Arc::new(
|
||||||
move |intent: CompletionIntent,
|
move |intent: CompletionIntent, cx: &mut WindowContext| {
|
||||||
window: &mut Window,
|
if !requires_argument
|
||||||
cx: &mut App| {
|
&& (!accepts_arguments || intent.is_complete())
|
||||||
if !requires_argument
|
{
|
||||||
&& (!accepts_arguments || intent.is_complete())
|
editor
|
||||||
{
|
.update(cx, |editor, cx| {
|
||||||
editor
|
editor.run_command(
|
||||||
.update(cx, |editor, cx| {
|
command_range.clone(),
|
||||||
editor.run_command(
|
&command_name,
|
||||||
command_range.clone(),
|
&[],
|
||||||
&command_name,
|
true,
|
||||||
&[],
|
workspace.clone(),
|
||||||
true,
|
cx,
|
||||||
workspace.clone(),
|
);
|
||||||
window,
|
})
|
||||||
cx,
|
.ok();
|
||||||
);
|
false
|
||||||
})
|
} else {
|
||||||
.ok();
|
requires_argument || accepts_arguments
|
||||||
false
|
}
|
||||||
} else {
|
},
|
||||||
requires_argument || accepts_arguments
|
) as Arc<_>
|
||||||
}
|
|
||||||
},
|
|
||||||
) as Arc<_>
|
|
||||||
});
|
});
|
||||||
Some(project::Completion {
|
Some(project::Completion {
|
||||||
old_range: name_range.clone(),
|
old_range: name_range.clone(),
|
||||||
documentation: Some(CompletionDocumentation::SingleLine(
|
documentation: Some(Documentation::SingleLine(command.description())),
|
||||||
command.description(),
|
|
||||||
)),
|
|
||||||
new_text,
|
new_text,
|
||||||
label: command.label(cx),
|
label: command.label(cx),
|
||||||
server_id: LanguageServerId(0),
|
server_id: LanguageServerId(0),
|
||||||
@@ -136,7 +130,6 @@ impl SlashCommandCompletionProvider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn complete_command_argument(
|
fn complete_command_argument(
|
||||||
&self,
|
&self,
|
||||||
command_name: &str,
|
command_name: &str,
|
||||||
@@ -144,8 +137,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
command_range: Range<Anchor>,
|
command_range: Range<Anchor>,
|
||||||
argument_range: Range<Anchor>,
|
argument_range: Range<Anchor>,
|
||||||
last_argument_range: Range<Anchor>,
|
last_argument_range: Range<Anchor>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<project::Completion>>> {
|
) -> Task<Result<Vec<project::Completion>>> {
|
||||||
let new_cancel_flag = Arc::new(AtomicBool::new(false));
|
let new_cancel_flag = Arc::new(AtomicBool::new(false));
|
||||||
let mut flag = self.cancel_flag.lock();
|
let mut flag = self.cancel_flag.lock();
|
||||||
@@ -156,7 +148,6 @@ impl SlashCommandCompletionProvider {
|
|||||||
arguments,
|
arguments,
|
||||||
new_cancel_flag.clone(),
|
new_cancel_flag.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let command_name: Arc<str> = command_name.into();
|
let command_name: Arc<str> = command_name.into();
|
||||||
@@ -184,9 +175,7 @@ impl SlashCommandCompletionProvider {
|
|||||||
|
|
||||||
let command_range = command_range.clone();
|
let command_range = command_range.clone();
|
||||||
let command_name = command_name.clone();
|
let command_name = command_name.clone();
|
||||||
move |intent: CompletionIntent,
|
move |intent: CompletionIntent, cx: &mut WindowContext| {
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App| {
|
|
||||||
if new_argument.after_completion.run()
|
if new_argument.after_completion.run()
|
||||||
|| intent.is_complete()
|
|| intent.is_complete()
|
||||||
{
|
{
|
||||||
@@ -198,7 +187,6 @@ impl SlashCommandCompletionProvider {
|
|||||||
&completed_arguments,
|
&completed_arguments,
|
||||||
true,
|
true,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -242,11 +230,10 @@ impl SlashCommandCompletionProvider {
|
|||||||
impl CompletionProvider for SlashCommandCompletionProvider {
|
impl CompletionProvider for SlashCommandCompletionProvider {
|
||||||
fn completions(
|
fn completions(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Entity<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
buffer_position: Anchor,
|
buffer_position: Anchor,
|
||||||
_: editor::CompletionContext,
|
_: editor::CompletionContext,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Editor>,
|
||||||
cx: &mut Context<Editor>,
|
|
||||||
) -> Task<Result<Vec<project::Completion>>> {
|
) -> Task<Result<Vec<project::Completion>>> {
|
||||||
let Some((name, arguments, command_range, last_argument_range)) =
|
let Some((name, arguments, command_range, last_argument_range)) =
|
||||||
buffer.update(cx, |buffer, _cx| {
|
buffer.update(cx, |buffer, _cx| {
|
||||||
@@ -301,31 +288,30 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
|||||||
command_range,
|
command_range,
|
||||||
argument_range,
|
argument_range,
|
||||||
last_argument_range,
|
last_argument_range,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
} else {
|
} 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(
|
fn resolve_completions(
|
||||||
&self,
|
&self,
|
||||||
_: Entity<Buffer>,
|
_: Model<Buffer>,
|
||||||
_: Vec<usize>,
|
_: Vec<usize>,
|
||||||
_: Rc<RefCell<Box<[project::Completion]>>>,
|
_: Rc<RefCell<Box<[project::Completion]>>>,
|
||||||
_: &mut Context<Editor>,
|
_: &mut ViewContext<Editor>,
|
||||||
) -> Task<Result<bool>> {
|
) -> Task<Result<bool>> {
|
||||||
Task::ready(Ok(true))
|
Task::ready(Ok(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_completion_trigger(
|
fn is_completion_trigger(
|
||||||
&self,
|
&self,
|
||||||
buffer: &Entity<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
position: language::Anchor,
|
position: language::Anchor,
|
||||||
_text: &str,
|
_text: &str,
|
||||||
_trigger_in_words: bool,
|
_trigger_in_words: bool,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let position = position.to_point(buffer);
|
let position = position.to_point(buffer);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_slash_command::SlashCommandWorkingSet;
|
use assistant_slash_command::SlashCommandWorkingSet;
|
||||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakEntity};
|
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
|
||||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
|
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ use crate::context_editor::ContextEditor;
|
|||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
||||||
working_set: Arc<SlashCommandWorkingSet>,
|
working_set: Arc<SlashCommandWorkingSet>,
|
||||||
active_context_editor: WeakEntity<ContextEditor>,
|
active_context_editor: WeakView<ContextEditor>,
|
||||||
trigger: T,
|
trigger: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ enum SlashCommandEntry {
|
|||||||
Info(SlashCommandInfo),
|
Info(SlashCommandInfo),
|
||||||
Advert {
|
Advert {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
renderer: fn(&mut Window, &mut App) -> AnyElement,
|
renderer: fn(&mut WindowContext) -> AnyElement,
|
||||||
on_confirm: fn(&mut Window, &mut App),
|
on_confirm: fn(&mut WindowContext),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,14 +44,14 @@ impl AsRef<str> for SlashCommandEntry {
|
|||||||
pub(crate) struct SlashCommandDelegate {
|
pub(crate) struct SlashCommandDelegate {
|
||||||
all_commands: Vec<SlashCommandEntry>,
|
all_commands: Vec<SlashCommandEntry>,
|
||||||
filtered_commands: Vec<SlashCommandEntry>,
|
filtered_commands: Vec<SlashCommandEntry>,
|
||||||
active_context_editor: WeakEntity<ContextEditor>,
|
active_context_editor: WeakView<ContextEditor>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
working_set: Arc<SlashCommandWorkingSet>,
|
working_set: Arc<SlashCommandWorkingSet>,
|
||||||
active_context_editor: WeakEntity<ContextEditor>,
|
active_context_editor: WeakView<ContextEditor>,
|
||||||
trigger: T,
|
trigger: T,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
SlashCommandSelector {
|
SlashCommandSelector {
|
||||||
@@ -73,23 +73,18 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
self.selected_index
|
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));
|
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
|
||||||
cx.notify();
|
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()
|
"Select a command...".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Task<()> {
|
|
||||||
let all_commands = self.all_commands.clone();
|
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
|
let filtered_commands = cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
@@ -109,9 +104,9 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
this.update_in(&mut cx, |this, window, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.delegate.filtered_commands = filtered_commands;
|
this.delegate.filtered_commands = filtered_commands;
|
||||||
this.delegate.set_selected_index(0, window, cx);
|
this.delegate.set_selected_index(0, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
@@ -144,25 +139,25 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
ret
|
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) {
|
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
||||||
match command {
|
match command {
|
||||||
SlashCommandEntry::Info(info) => {
|
SlashCommandEntry::Info(info) => {
|
||||||
self.active_context_editor
|
self.active_context_editor
|
||||||
.update(cx, |context_editor, cx| {
|
.update(cx, |context_editor, cx| {
|
||||||
context_editor.insert_command(&info.name, window, cx)
|
context_editor.insert_command(&info.name, cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
SlashCommandEntry::Advert { on_confirm, .. } => {
|
SlashCommandEntry::Advert { on_confirm, .. } => {
|
||||||
on_confirm(window, cx);
|
on_confirm(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.emit(DismissEvent);
|
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 {
|
fn editor_position(&self) -> PickerEditorPosition {
|
||||||
PickerEditorPosition::End
|
PickerEditorPosition::End
|
||||||
@@ -172,8 +167,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
window: &mut Window,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let command_info = self.filtered_commands.get(ix)?;
|
let command_info = self.filtered_commands.get(ix)?;
|
||||||
|
|
||||||
@@ -185,7 +179,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let description = info.description.clone();
|
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(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -235,14 +229,14 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Dense)
|
.spacing(ListItemSpacing::Dense)
|
||||||
.toggle_state(selected)
|
.toggle_state(selected)
|
||||||
.child(renderer(window, cx)),
|
.child(renderer(cx)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
let all_models = self
|
let all_models = self
|
||||||
.working_set
|
.working_set
|
||||||
.featured_command_names(cx)
|
.featured_command_names(cx)
|
||||||
@@ -265,7 +259,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
|||||||
})
|
})
|
||||||
.chain([SlashCommandEntry::Advert {
|
.chain([SlashCommandEntry::Advert {
|
||||||
name: "create-your-command".into(),
|
name: "create-your-command".into(),
|
||||||
renderer: |_, cx| {
|
renderer: |cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
@@ -299,7 +293,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
|||||||
)
|
)
|
||||||
.into_any_element()
|
.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<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -310,9 +304,8 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
|||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let picker_view = cx.new(|cx| {
|
let picker_view = cx.new_view(|cx| {
|
||||||
let picker =
|
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
|
||||||
Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()));
|
|
||||||
picker
|
picker
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -321,7 +314,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
|||||||
.update(cx, |this, _| this.slash_menu_handle.clone())
|
.update(cx, |this, _| this.slash_menu_handle.clone())
|
||||||
.ok();
|
.ok();
|
||||||
PopoverMenu::new("model-switcher")
|
PopoverMenu::new("model-switcher")
|
||||||
.menu(move |_window, _cx| Some(picker_view.clone()))
|
.menu(move |_cx| Some(picker_view.clone()))
|
||||||
.trigger(self.trigger)
|
.trigger(self.trigger)
|
||||||
.attach(gpui::Corner::TopLeft)
|
.attach(gpui::Corner::TopLeft)
|
||||||
.anchor(gpui::Corner::BottomLeft)
|
.anchor(gpui::Corner::BottomLeft)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ lmstudio = { workspace = true, features = ["schemars"] }
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
ollama = { workspace = true, features = ["schemars"] }
|
ollama = { workspace = true, features = ["schemars"] }
|
||||||
open_ai = { workspace = true, features = ["schemars"] }
|
open_ai = { workspace = true, features = ["schemars"] }
|
||||||
deepseek = { workspace = true, features = ["schemars"] }
|
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use ::open_ai::Model as OpenAiModel;
|
use ::open_ai::Model as OpenAiModel;
|
||||||
use anthropic::Model as AnthropicModel;
|
use anthropic::Model as AnthropicModel;
|
||||||
use deepseek::Model as DeepseekModel;
|
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use gpui::{App, Pixels};
|
use gpui::{AppContext, Pixels};
|
||||||
use language_model::{CloudModel, LanguageModel};
|
use language_model::{CloudModel, LanguageModel};
|
||||||
use lmstudio::Model as LmStudioModel;
|
use lmstudio::Model as LmStudioModel;
|
||||||
use ollama::Model as OllamaModel;
|
use ollama::Model as OllamaModel;
|
||||||
@@ -47,11 +46,6 @@ pub enum AssistantProviderContentV1 {
|
|||||||
default_model: Option<LmStudioModel>,
|
default_model: Option<LmStudioModel>,
|
||||||
api_url: Option<String>,
|
api_url: Option<String>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "deepseek")]
|
|
||||||
DeepSeek {
|
|
||||||
default_model: Option<DeepseekModel>,
|
|
||||||
api_url: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -68,7 +62,7 @@ pub struct AssistantSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
cx.is_staff() || self.enable_experimental_live_diffs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,12 +149,6 @@ impl AssistantSettingsContent {
|
|||||||
model: model.id().to_string(),
|
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,
|
inline_alternatives: None,
|
||||||
enable_experimental_live_diffs: None,
|
enable_experimental_live_diffs: None,
|
||||||
@@ -265,18 +253,6 @@ impl AssistantSettingsContent {
|
|||||||
available_models,
|
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) => {
|
VersionedAssistantSettingsContent::V2(settings) => {
|
||||||
@@ -365,7 +341,6 @@ fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema:
|
|||||||
"openai".into(),
|
"openai".into(),
|
||||||
"zed.dev".into(),
|
"zed.dev".into(),
|
||||||
"copilot_chat".into(),
|
"copilot_chat".into(),
|
||||||
"deepseek".into(),
|
|
||||||
]),
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@@ -405,7 +380,7 @@ pub struct AssistantSettingsContentV1 {
|
|||||||
default_height: Option<f32>,
|
default_height: Option<f32>,
|
||||||
/// The provider of the assistant service.
|
/// 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.
|
/// each with their respective default models and configurations.
|
||||||
provider: Option<AssistantProviderContentV1>,
|
provider: Option<AssistantProviderContentV1>,
|
||||||
}
|
}
|
||||||
@@ -434,7 +409,7 @@ pub struct LegacyAssistantSettingsContent {
|
|||||||
pub default_open_ai_model: Option<OpenAiModel>,
|
pub default_open_ai_model: Option<OpenAiModel>,
|
||||||
/// OpenAI API base URL to use when creating new chats.
|
/// 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>,
|
pub openai_api_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +422,7 @@ impl Settings for AssistantSettings {
|
|||||||
|
|
||||||
fn load(
|
fn load(
|
||||||
sources: SettingsSources<Self::FileContent>,
|
sources: SettingsSources<Self::FileContent>,
|
||||||
_: &mut gpui::App,
|
_: &mut gpui::AppContext,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let mut settings = AssistantSettings::default();
|
let mut settings = AssistantSettings::default();
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub use crate::slash_command_working_set::*;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::stream::{self, BoxStream};
|
use futures::stream::{self, BoxStream};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{App, SharedString, Task, WeakEntity, Window};
|
use gpui::{AppContext, SharedString, Task, WeakView, WindowContext};
|
||||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||||
pub use language_model::Role;
|
pub use language_model::Role;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -18,7 +18,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use workspace::{ui::IconName, Workspace};
|
use workspace::{ui::IconName, Workspace};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
SlashCommandRegistry::default_global(cx);
|
SlashCommandRegistry::default_global(cx);
|
||||||
extension_slash_command::init(cx);
|
extension_slash_command::init(cx);
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ pub trait SlashCommand: 'static + Send + Sync {
|
|||||||
fn icon(&self) -> IconName {
|
fn icon(&self) -> IconName {
|
||||||
IconName::Slash
|
IconName::Slash
|
||||||
}
|
}
|
||||||
fn label(&self, _cx: &App) -> CodeLabel {
|
fn label(&self, _cx: &AppContext) -> CodeLabel {
|
||||||
CodeLabel::plain(self.name(), None)
|
CodeLabel::plain(self.name(), None)
|
||||||
}
|
}
|
||||||
fn description(&self) -> String;
|
fn description(&self) -> String;
|
||||||
@@ -80,29 +80,26 @@ pub trait SlashCommand: 'static + Send + Sync {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
cancel: Arc<AtomicBool>,
|
cancel: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>>;
|
) -> Task<Result<Vec<ArgumentCompletion>>>;
|
||||||
fn requires_argument(&self) -> bool;
|
fn requires_argument(&self) -> bool;
|
||||||
fn accepts_arguments(&self) -> bool {
|
fn accepts_arguments(&self) -> bool {
|
||||||
self.requires_argument()
|
self.requires_argument()
|
||||||
}
|
}
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn run(
|
fn run(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
context_buffer: BufferSnapshot,
|
context_buffer: BufferSnapshot,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
// TODO: We're just using the `LspAdapterDelegate` here because that is
|
// TODO: We're just using the `LspAdapterDelegate` here because that is
|
||||||
// what the extension API is already expecting.
|
// what the extension API is already expecting.
|
||||||
//
|
//
|
||||||
// It may be that `LspAdapterDelegate` needs a more general name, or
|
// It may be that `LspAdapterDelegate` needs a more general name, or
|
||||||
// perhaps another kind of delegate is needed here.
|
// perhaps another kind of delegate is needed here.
|
||||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult>;
|
) -> Task<SlashCommandResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::{atomic::AtomicBool, Arc};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
|
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
|
||||||
use gpui::{App, Task, WeakEntity, Window};
|
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
SlashCommandRegistry, SlashCommandResult,
|
SlashCommandRegistry, SlashCommandResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
let proxy = ExtensionHostProxy::default_global(cx);
|
let proxy = ExtensionHostProxy::default_global(cx);
|
||||||
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
|
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
|
||||||
slash_command_registry: SlashCommandRegistry::global(cx),
|
slash_command_registry: SlashCommandRegistry::global(cx),
|
||||||
@@ -97,9 +97,8 @@ impl SlashCommand for ExtensionSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakEntity<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
let command = self.command.clone();
|
let command = self.command.clone();
|
||||||
let arguments = arguments.to_owned();
|
let arguments = arguments.to_owned();
|
||||||
@@ -128,10 +127,9 @@ impl SlashCommand for ExtensionSlashCommand {
|
|||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: BufferSnapshot,
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakEntity<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let command = self.command.clone();
|
let command = self.command.clone();
|
||||||
let arguments = arguments.to_owned();
|
let arguments = arguments.to_owned();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use collections::{BTreeSet, HashMap};
|
use collections::{BTreeSet, HashMap};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use gpui::Global;
|
use gpui::Global;
|
||||||
use gpui::{App, ReadGlobal};
|
use gpui::{AppContext, ReadGlobal};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use crate::SlashCommand;
|
use crate::SlashCommand;
|
||||||
@@ -26,14 +26,14 @@ pub struct SlashCommandRegistry {
|
|||||||
|
|
||||||
impl SlashCommandRegistry {
|
impl SlashCommandRegistry {
|
||||||
/// Returns the global [`SlashCommandRegistry`].
|
/// Returns the global [`SlashCommandRegistry`].
|
||||||
pub fn global(cx: &App) -> Arc<Self> {
|
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||||
GlobalSlashCommandRegistry::global(cx).0.clone()
|
GlobalSlashCommandRegistry::global(cx).0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the global [`SlashCommandRegistry`].
|
/// Returns the global [`SlashCommandRegistry`].
|
||||||
///
|
///
|
||||||
/// Inserts a default [`SlashCommandRegistry`] if one does not yet exist.
|
/// 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()
|
cx.default_global::<GlobalSlashCommandRegistry>().0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::App;
|
use gpui::AppContext;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::{SlashCommand, SlashCommandRegistry};
|
use crate::{SlashCommand, SlashCommandRegistry};
|
||||||
@@ -23,7 +23,7 @@ struct WorkingSetState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SlashCommandWorkingSet {
|
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
|
self.state
|
||||||
.lock()
|
.lock()
|
||||||
.context_server_commands_by_name
|
.context_server_commands_by_name
|
||||||
@@ -32,7 +32,7 @@ impl SlashCommandWorkingSet {
|
|||||||
.or_else(|| SlashCommandRegistry::global(cx).command(name))
|
.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();
|
let mut command_names = SlashCommandRegistry::global(cx).command_names();
|
||||||
command_names.extend(
|
command_names.extend(
|
||||||
self.state
|
self.state
|
||||||
@@ -45,7 +45,7 @@ impl SlashCommandWorkingSet {
|
|||||||
command_names
|
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()
|
SlashCommandRegistry::global(cx).featured_command_names()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ toml.workspace = true
|
|||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
worktree.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ mod symbols_command;
|
|||||||
mod tab_command;
|
mod tab_command;
|
||||||
mod terminal_command;
|
mod terminal_command;
|
||||||
|
|
||||||
use gpui::App;
|
use gpui::AppContext;
|
||||||
use language::{CodeLabel, HighlightId};
|
use language::{CodeLabel, HighlightId};
|
||||||
use ui::ActiveTheme as _;
|
use ui::ActiveTheme as _;
|
||||||
|
|
||||||
@@ -40,7 +40,11 @@ pub use crate::symbols_command::*;
|
|||||||
pub use crate::tab_command::*;
|
pub use crate::tab_command::*;
|
||||||
pub use crate::terminal_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();
|
let mut label = CodeLabel::default();
|
||||||
label.push_str(command_name, None);
|
label.push_str(command_name, None);
|
||||||
label.push_str(" ", None);
|
label.push_str(" ", None);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use assistant_slash_command::{
|
|||||||
};
|
};
|
||||||
use feature_flags::FeatureFlag;
|
use feature_flags::FeatureFlag;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{App, AsyncApp, Task, WeakEntity, Window};
|
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||||
@@ -45,7 +45,7 @@ impl SlashCommand for AutoCommand {
|
|||||||
self.description()
|
self.description()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label(&self, cx: &App) -> CodeLabel {
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
create_label_for_command("auto", &["--prompt"], cx)
|
create_label_for_command("auto", &["--prompt"], cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +53,8 @@ impl SlashCommand for AutoCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
// There's no autocomplete for a prompt, since it's arbitrary text.
|
// 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.
|
// 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")));
|
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| {
|
let task = project_index.read_with(&cx, |project_index, cx| {
|
||||||
project_index.flush_summary_backlogs(cx)
|
project_index.flush_summary_backlogs(cx)
|
||||||
})?;
|
})?;
|
||||||
@@ -97,10 +96,9 @@ impl SlashCommand for AutoCommand {
|
|||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: language::BufferSnapshot,
|
_context_buffer: language::BufferSnapshot,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
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")));
|
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
|
let summaries = project_index
|
||||||
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
|
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
|
||||||
.await?;
|
.await?;
|
||||||
@@ -189,7 +187,7 @@ struct CommandToRun {
|
|||||||
async fn commands_for_summaries(
|
async fn commands_for_summaries(
|
||||||
summaries: &[FileSummary],
|
summaries: &[FileSummary],
|
||||||
original_prompt: &str,
|
original_prompt: &str,
|
||||||
cx: &AsyncApp,
|
cx: &AsyncAppContext,
|
||||||
) -> Result<Vec<CommandToRun>> {
|
) -> Result<Vec<CommandToRun>> {
|
||||||
if summaries.is_empty() {
|
if summaries.is_empty() {
|
||||||
log::warn!("Inferring no context because there were no summaries available.");
|
log::warn!("Inferring no context because there were no summaries available.");
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use assistant_slash_command::{
|
use assistant_slash_command::{
|
||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
SlashCommandResult,
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, Entity, Task, WeakEntity};
|
use gpui::{AppContext, Model, Task, WeakView};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -76,7 +76,7 @@ impl CargoWorkspaceSlashCommand {
|
|||||||
Ok(message)
|
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 = project.read(cx).worktrees(cx).next()?;
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||||
@@ -107,9 +107,8 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakEntity<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
_cx: &mut WindowContext,
|
||||||
_cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
}
|
}
|
||||||
@@ -123,10 +122,9 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
|
|||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: BufferSnapshot,
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let output = workspace.update(cx, |workspace, cx| {
|
let output = workspace.update(cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use context_server::{
|
|||||||
manager::{ContextServer, ContextServerManager},
|
manager::{ContextServer, ContextServerManager},
|
||||||
types::Prompt,
|
types::Prompt,
|
||||||
};
|
};
|
||||||
use gpui::{App, Entity, Task, WeakEntity, Window};
|
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
|
||||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -19,14 +19,14 @@ use workspace::Workspace;
|
|||||||
use crate::create_label_for_command;
|
use crate::create_label_for_command;
|
||||||
|
|
||||||
pub struct ContextServerSlashCommand {
|
pub struct ContextServerSlashCommand {
|
||||||
server_manager: Entity<ContextServerManager>,
|
server_manager: Model<ContextServerManager>,
|
||||||
server_id: Arc<str>,
|
server_id: Arc<str>,
|
||||||
prompt: Prompt,
|
prompt: Prompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextServerSlashCommand {
|
impl ContextServerSlashCommand {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
server_manager: Entity<ContextServerManager>,
|
server_manager: Model<ContextServerManager>,
|
||||||
server: &Arc<ContextServer>,
|
server: &Arc<ContextServer>,
|
||||||
prompt: Prompt,
|
prompt: Prompt,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -43,7 +43,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
self.prompt.name.clone()
|
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()];
|
let mut parts = vec![self.prompt.name.as_str()];
|
||||||
if let Some(args) = &self.prompt.arguments {
|
if let Some(args) = &self.prompt.arguments {
|
||||||
if let Some(arg) = args.first() {
|
if let Some(arg) = args.first() {
|
||||||
@@ -77,9 +77,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakEntity<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
|
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
|
||||||
return Task::ready(Err(anyhow!("Failed to complete argument")));
|
return Task::ready(Err(anyhow!("Failed to complete argument")));
|
||||||
@@ -129,10 +128,9 @@ impl SlashCommand for ContextServerSlashCommand {
|
|||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: BufferSnapshot,
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakEntity<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let server_id = self.server_id.clone();
|
let server_id = self.server_id.clone();
|
||||||
let prompt_name = self.prompt.name.clone();
|
let prompt_name = self.prompt.name.clone();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use assistant_slash_command::{
|
|||||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
SlashCommandResult,
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use gpui::{Task, WeakEntity};
|
use gpui::{Task, WeakView};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use prompt_library::PromptStore;
|
use prompt_library::PromptStore;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -36,9 +36,8 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_cancellation_flag: Arc<AtomicBool>,
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakEntity<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
_cx: &mut WindowContext,
|
||||||
_cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
}
|
}
|
||||||
@@ -48,10 +47,9 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: BufferSnapshot,
|
_context_buffer: BufferSnapshot,
|
||||||
_workspace: WeakEntity<Workspace>,
|
_workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
_window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let store = PromptStore::global(cx);
|
let store = PromptStore::global(cx);
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use assistant_slash_command::{
|
|||||||
};
|
};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use gpui::{App, Task, WeakEntity, Window};
|
use gpui::{Task, WeakView, WindowContext};
|
||||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use text::OffsetRangeExt;
|
use text::OffsetRangeExt;
|
||||||
@@ -40,9 +40,8 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
_cancellation_flag: Arc<AtomicBool>,
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakEntity<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_window: &mut Window,
|
_cx: &mut WindowContext,
|
||||||
_cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||||
}
|
}
|
||||||
@@ -52,10 +51,9 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
_arguments: &[String],
|
_arguments: &[String],
|
||||||
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
context_buffer: BufferSnapshot,
|
context_buffer: BufferSnapshot,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let mut paths = HashSet::default();
|
let mut paths = HashSet::default();
|
||||||
let mut file_command_old_outputs = Vec::new();
|
let mut file_command_old_outputs = Vec::new();
|
||||||
@@ -79,7 +77,6 @@ impl SlashCommand for DeltaSlashCommand {
|
|||||||
context_buffer.clone(),
|
context_buffer.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
delegate.clone(),
|
delegate.clone(),
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use assistant_slash_command::{
|
|||||||
SlashCommandResult,
|
SlashCommandResult,
|
||||||
};
|
};
|
||||||
use fuzzy::{PathMatch, StringMatchCandidate};
|
use fuzzy::{PathMatch, StringMatchCandidate};
|
||||||
use gpui::{App, Entity, Task, WeakEntity};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
||||||
OffsetRangeExt, ToOffset,
|
OffsetRangeExt, ToOffset,
|
||||||
@@ -30,8 +30,8 @@ impl DiagnosticsSlashCommand {
|
|||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: &Entity<Workspace>,
|
workspace: &View<Workspace>,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) -> Task<Vec<PathMatch>> {
|
) -> Task<Vec<PathMatch>> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
let workspace = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
@@ -90,7 +90,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
"diagnostics".into()
|
"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)
|
create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +118,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakEntity<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
_: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||||
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
|
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
|
||||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
return Task::ready(Err(anyhow!("workspace was dropped")));
|
||||||
@@ -173,10 +172,9 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
|||||||
arguments: &[String],
|
arguments: &[String],
|
||||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||||
_context_buffer: BufferSnapshot,
|
_context_buffer: BufferSnapshot,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||||
window: &mut Window,
|
cx: &mut WindowContext,
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<SlashCommandResult> {
|
) -> Task<SlashCommandResult> {
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
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);
|
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
||||||
|
|
||||||
window.spawn(cx, move |_| async move {
|
cx.spawn(move |_| async move {
|
||||||
task.await?
|
task.await?
|
||||||
.map(|output| output.to_event_stream())
|
.map(|output| output.to_event_stream())
|
||||||
.ok_or_else(|| anyhow!("No diagnostics found"))
|
.ok_or_else(|| anyhow!("No diagnostics found"))
|
||||||
@@ -225,9 +223,9 @@ impl Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn collect_diagnostics(
|
fn collect_diagnostics(
|
||||||
project: Entity<Project>,
|
project: Model<Project>,
|
||||||
options: Options,
|
options: Options,
|
||||||
cx: &mut App,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<Option<SlashCommandOutput>>> {
|
) -> Task<Result<Option<SlashCommandOutput>>> {
|
||||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||||
@@ -303,7 +301,7 @@ fn collect_diagnostics(
|
|||||||
.await
|
.await
|
||||||
.log_err()
|
.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);
|
collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||