Compare commits
1 Commits
nathan
...
revert-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee53b805f0 |
@@ -1,55 +0,0 @@
|
|||||||
# Phase 2: Explore Repository
|
|
||||||
|
|
||||||
You are analyzing a codebase to understand its structure before reviewing documentation impact.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Produce a structured overview of the repository to inform subsequent documentation analysis.
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. **Identify Primary Languages and Frameworks**
|
|
||||||
- Scan for Cargo.toml, package.json, or other manifest files
|
|
||||||
- Note the primary language(s) and key dependencies
|
|
||||||
|
|
||||||
2. **Map Documentation Structure**
|
|
||||||
- This project uses **mdBook** (https://rust-lang.github.io/mdBook/)
|
|
||||||
- Documentation is in `docs/src/`
|
|
||||||
- Table of contents: `docs/src/SUMMARY.md` (mdBook format: https://rust-lang.github.io/mdBook/format/summary.html)
|
|
||||||
- Style guide: `docs/.rules`
|
|
||||||
- Agent guidelines: `docs/AGENTS.md`
|
|
||||||
- Formatting: Prettier (config in `docs/.prettierrc`)
|
|
||||||
|
|
||||||
3. **Identify Build and Tooling**
|
|
||||||
- Note build systems (cargo, npm, etc.)
|
|
||||||
- Identify documentation tooling (mdbook, etc.)
|
|
||||||
|
|
||||||
4. **Output Format**
|
|
||||||
Produce a JSON summary:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"primary_language": "Rust",
|
|
||||||
"frameworks": ["GPUI"],
|
|
||||||
"documentation": {
|
|
||||||
"system": "mdBook",
|
|
||||||
"location": "docs/src/",
|
|
||||||
"toc_file": "docs/src/SUMMARY.md",
|
|
||||||
"toc_format": "https://rust-lang.github.io/mdBook/format/summary.html",
|
|
||||||
"style_guide": "docs/.rules",
|
|
||||||
"agent_guidelines": "docs/AGENTS.md",
|
|
||||||
"formatter": "prettier",
|
|
||||||
"formatter_config": "docs/.prettierrc",
|
|
||||||
"custom_preprocessor": "docs_preprocessor (handles {#kb action::Name} syntax)"
|
|
||||||
},
|
|
||||||
"key_directories": {
|
|
||||||
"source": "crates/",
|
|
||||||
"docs": "docs/src/",
|
|
||||||
"extensions": "extensions/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Read-only: Do not modify any files
|
|
||||||
- Focus on structure, not content details
|
|
||||||
- Complete within 2 minutes
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Phase 3: Analyze Changes
|
|
||||||
|
|
||||||
You are analyzing code changes to understand their nature and scope.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Produce a clear, neutral summary of what changed in the codebase.
|
|
||||||
|
|
||||||
## Input
|
|
||||||
You will receive:
|
|
||||||
- List of changed files from the triggering commit/PR
|
|
||||||
- Repository structure from Phase 2
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. **Categorize Changed Files**
|
|
||||||
- Source code (which crates/modules)
|
|
||||||
- Configuration
|
|
||||||
- Tests
|
|
||||||
- Documentation (already existing)
|
|
||||||
- Other
|
|
||||||
|
|
||||||
2. **Analyze Each Change**
|
|
||||||
- Review diffs for files likely to impact documentation
|
|
||||||
- Focus on: public APIs, settings, keybindings, commands, user-visible behavior
|
|
||||||
|
|
||||||
3. **Identify What Did NOT Change**
|
|
||||||
- Note stable interfaces or behaviors
|
|
||||||
- Important for avoiding unnecessary documentation updates
|
|
||||||
|
|
||||||
4. **Output Format**
|
|
||||||
Produce a markdown summary:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Change Analysis
|
|
||||||
|
|
||||||
### Changed Files Summary
|
|
||||||
| Category | Files | Impact Level |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Source - [crate] | file1.rs, file2.rs | High/Medium/Low |
|
|
||||||
| Settings | settings.json | Medium |
|
|
||||||
| Tests | test_*.rs | None |
|
|
||||||
|
|
||||||
### Behavioral Changes
|
|
||||||
- **[Feature/Area]**: Description of what changed from user perspective
|
|
||||||
- **[Feature/Area]**: Description...
|
|
||||||
|
|
||||||
### Unchanged Areas
|
|
||||||
- [Area]: Confirmed no changes to [specific behavior]
|
|
||||||
|
|
||||||
### Files Requiring Deeper Review
|
|
||||||
- `path/to/file.rs`: Reason for deeper review
|
|
||||||
```
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Read-only: Do not modify any files
|
|
||||||
- Neutral tone: Describe what changed, not whether it's good/bad
|
|
||||||
- Do not propose documentation changes yet
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Phase 4: Plan Documentation Impact
|
|
||||||
|
|
||||||
You are determining whether and how documentation should be updated based on code changes.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Produce a structured documentation plan that will guide Phase 5 execution.
|
|
||||||
|
|
||||||
## Documentation System
|
|
||||||
This is an **mdBook** site (https://rust-lang.github.io/mdBook/):
|
|
||||||
- `docs/src/SUMMARY.md` defines book structure per https://rust-lang.github.io/mdBook/format/summary.html
|
|
||||||
- If adding new pages, they MUST be added to SUMMARY.md
|
|
||||||
- Use `{#kb action::ActionName}` syntax for keybindings (custom preprocessor expands these)
|
|
||||||
- Prettier formatting (80 char width) will be applied automatically
|
|
||||||
|
|
||||||
## Input
|
|
||||||
You will receive:
|
|
||||||
- Change analysis from Phase 3
|
|
||||||
- Repository structure from Phase 2
|
|
||||||
- Documentation guidelines from `docs/AGENTS.md`
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. **Review AGENTS.md**
|
|
||||||
- Load and apply all rules from `docs/AGENTS.md`
|
|
||||||
- Respect scope boundaries (in-scope vs out-of-scope)
|
|
||||||
|
|
||||||
2. **Evaluate Documentation Impact**
|
|
||||||
For each behavioral change from Phase 3:
|
|
||||||
- Does existing documentation cover this area?
|
|
||||||
- Is the documentation now inaccurate or incomplete?
|
|
||||||
- Classify per AGENTS.md "Change Classification" section
|
|
||||||
|
|
||||||
3. **Identify Specific Updates**
|
|
||||||
For each required update:
|
|
||||||
- Exact file path
|
|
||||||
- Specific section or heading
|
|
||||||
- Type of change (update existing, add new, deprecate)
|
|
||||||
- Description of the change
|
|
||||||
|
|
||||||
4. **Flag Uncertainty**
|
|
||||||
Explicitly mark:
|
|
||||||
- Assumptions you're making
|
|
||||||
- Areas where human confirmation is needed
|
|
||||||
- Ambiguous requirements
|
|
||||||
|
|
||||||
5. **Output Format**
|
|
||||||
Use the exact format specified in `docs/AGENTS.md` Phase 4 section:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Documentation Impact Assessment
|
|
||||||
|
|
||||||
### Summary
|
|
||||||
Brief description of code changes analyzed.
|
|
||||||
|
|
||||||
### Documentation Updates Required: [Yes/No]
|
|
||||||
|
|
||||||
### Planned Changes
|
|
||||||
|
|
||||||
#### 1. [File Path]
|
|
||||||
- **Section**: [Section name or "New section"]
|
|
||||||
- **Change Type**: [Update/Add/Deprecate]
|
|
||||||
- **Reason**: Why this change is needed
|
|
||||||
- **Description**: What will be added/modified
|
|
||||||
|
|
||||||
### Uncertainty Flags
|
|
||||||
- [ ] [Description of any assumptions or areas needing confirmation]
|
|
||||||
|
|
||||||
### No Changes Needed
|
|
||||||
- [List files reviewed but not requiring updates, with brief reason]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Read-only: Do not modify any files
|
|
||||||
- Conservative: When uncertain, flag for human review rather than planning changes
|
|
||||||
- Scoped: Only plan changes that trace directly to code changes from Phase 3
|
|
||||||
- No scope expansion: Do not plan "improvements" unrelated to triggering changes
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Phase 5: Apply Documentation Plan
|
|
||||||
|
|
||||||
You are executing a pre-approved documentation plan for an **mdBook** documentation site.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Implement exactly the changes specified in the documentation plan from Phase 4.
|
|
||||||
|
|
||||||
## Documentation System
|
|
||||||
- **mdBook**: https://rust-lang.github.io/mdBook/
|
|
||||||
- **SUMMARY.md**: Follows mdBook format (https://rust-lang.github.io/mdBook/format/summary.html)
|
|
||||||
- **Prettier**: Will be run automatically after this phase (80 char line width)
|
|
||||||
- **Custom preprocessor**: Use `{#kb action::ActionName}` for keybindings instead of hardcoding
|
|
||||||
|
|
||||||
## Input
|
|
||||||
You will receive:
|
|
||||||
- Documentation plan from Phase 4
|
|
||||||
- Documentation guidelines from `docs/AGENTS.md`
|
|
||||||
- Style rules from `docs/.rules`
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. **Validate Plan**
|
|
||||||
- Confirm all planned files are within scope per AGENTS.md
|
|
||||||
- Verify no out-of-scope files are targeted
|
|
||||||
|
|
||||||
2. **Execute Each Planned Change**
|
|
||||||
For each item in "Planned Changes":
|
|
||||||
- Navigate to the specified file
|
|
||||||
- Locate the specified section
|
|
||||||
- Apply the described change
|
|
||||||
- Follow style rules from `docs/.rules`
|
|
||||||
|
|
||||||
3. **Style Compliance**
|
|
||||||
Every edit must follow `docs/.rules`:
|
|
||||||
- Second person, present tense
|
|
||||||
- No hedging words ("simply", "just", "easily")
|
|
||||||
- Proper keybinding format (`Cmd+Shift+P`)
|
|
||||||
- Settings Editor first, JSON second
|
|
||||||
- Correct terminology (folder not directory, etc.)
|
|
||||||
|
|
||||||
4. **Preserve Context**
|
|
||||||
- Maintain surrounding content structure
|
|
||||||
- Keep consistent heading levels
|
|
||||||
- Preserve existing cross-references
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Execute ONLY changes listed in the plan
|
|
||||||
- Do not discover new documentation targets
|
|
||||||
- Do not make stylistic improvements outside planned sections
|
|
||||||
- Do not expand scope beyond what Phase 4 specified
|
|
||||||
- If a planned change cannot be applied (file missing, section not found), skip and note it
|
|
||||||
|
|
||||||
## Output
|
|
||||||
After applying changes, output a summary:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Applied Changes
|
|
||||||
|
|
||||||
### Successfully Applied
|
|
||||||
- `path/to/file.md`: [Brief description of change]
|
|
||||||
|
|
||||||
### Skipped (Could Not Apply)
|
|
||||||
- `path/to/file.md`: [Reason - e.g., "Section not found"]
|
|
||||||
|
|
||||||
### Warnings
|
|
||||||
- [Any issues encountered during application]
|
|
||||||
```
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Phase 6: Summarize Changes
|
|
||||||
|
|
||||||
You are generating a summary of documentation updates for PR review.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Create a clear, reviewable summary of all documentation changes made.
|
|
||||||
|
|
||||||
## Input
|
|
||||||
You will receive:
|
|
||||||
- Applied changes report from Phase 5
|
|
||||||
- Original change analysis from Phase 3
|
|
||||||
- Git diff of documentation changes
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. **Gather Change Information**
|
|
||||||
- List all modified documentation files
|
|
||||||
- Identify the corresponding code changes that triggered each update
|
|
||||||
|
|
||||||
2. **Generate Summary**
|
|
||||||
Use the format specified in `docs/AGENTS.md` Phase 6 section:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Documentation Update Summary
|
|
||||||
|
|
||||||
### Changes Made
|
|
||||||
| File | Change | Related Code |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| docs/src/path.md | Brief description | PR #123 or commit SHA |
|
|
||||||
|
|
||||||
### Rationale
|
|
||||||
Brief explanation of why these updates were made, linking back to the triggering code changes.
|
|
||||||
|
|
||||||
### Review Notes
|
|
||||||
- Items reviewers should pay special attention to
|
|
||||||
- Any uncertainty flags from Phase 4 that were addressed
|
|
||||||
- Assumptions made during documentation
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Add Context for Reviewers**
|
|
||||||
- Highlight any changes that might be controversial
|
|
||||||
- Note if any planned changes were skipped and why
|
|
||||||
- Flag areas where reviewer expertise is especially needed
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
The summary should be suitable for:
|
|
||||||
- PR description body
|
|
||||||
- Commit message (condensed version)
|
|
||||||
- Team communication
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Read-only (documentation changes already applied in Phase 5)
|
|
||||||
- Factual: Describe what was done, not justify why it's good
|
|
||||||
- Complete: Account for all changes, including skipped items
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Phase 7: Commit and Open PR
|
|
||||||
|
|
||||||
You are creating a git branch, committing documentation changes, and opening a PR.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Package documentation updates into a reviewable pull request.
|
|
||||||
|
|
||||||
## Input
|
|
||||||
You will receive:
|
|
||||||
- Summary from Phase 6
|
|
||||||
- List of modified files
|
|
||||||
|
|
||||||
## Instructions
|
|
||||||
|
|
||||||
1. **Create Branch**
|
|
||||||
```sh
|
|
||||||
git checkout -b docs/auto-update-{date}
|
|
||||||
```
|
|
||||||
Use format: `docs/auto-update-YYYY-MM-DD` or `docs/auto-update-{short-sha}`
|
|
||||||
|
|
||||||
2. **Stage and Commit**
|
|
||||||
- Stage only documentation files in `docs/src/`
|
|
||||||
- Do not stage any other files
|
|
||||||
|
|
||||||
Commit message format:
|
|
||||||
```
|
|
||||||
docs: auto-update documentation for [brief description]
|
|
||||||
|
|
||||||
[Summary from Phase 6, condensed]
|
|
||||||
|
|
||||||
Triggered by: [commit SHA or PR reference]
|
|
||||||
|
|
||||||
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Push Branch**
|
|
||||||
```sh
|
|
||||||
git push -u origin docs/auto-update-{date}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Create Pull Request**
|
|
||||||
Use the Phase 6 summary as the PR body.
|
|
||||||
|
|
||||||
PR Title: `docs: [Brief description of documentation updates]`
|
|
||||||
|
|
||||||
Labels (if available): `documentation`, `automated`
|
|
||||||
|
|
||||||
Base branch: `main`
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- Do NOT auto-merge
|
|
||||||
- Do NOT request specific reviewers (let CODEOWNERS handle it)
|
|
||||||
- Do NOT modify files outside `docs/src/`
|
|
||||||
- If no changes to commit, exit gracefully with message "No documentation changes to commit"
|
|
||||||
|
|
||||||
## Output
|
|
||||||
```markdown
|
|
||||||
## PR Created
|
|
||||||
|
|
||||||
- **Branch**: docs/auto-update-{date}
|
|
||||||
- **PR URL**: https://github.com/zed-industries/zed/pull/XXXX
|
|
||||||
- **Status**: Ready for review
|
|
||||||
|
|
||||||
### Commit
|
|
||||||
- SHA: {commit-sha}
|
|
||||||
- Files: {count} documentation files modified
|
|
||||||
```
|
|
||||||
16
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
16
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
@@ -75,22 +75,6 @@ body:
|
|||||||
</details>
|
</details>
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Relevant Keymap
|
|
||||||
description: |
|
|
||||||
Open the command palette in Zed, then type “zed: open keymap file” and copy/paste the file's contents.
|
|
||||||
value: |
|
|
||||||
<details><summary>keymap.json</summary>
|
|
||||||
|
|
||||||
<!-- Paste your keymap file inside the code block. -->
|
|
||||||
```json
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: (for AI issues) Model provider details
|
label: (for AI issues) Model provider details
|
||||||
|
|||||||
1
.github/actionlint.yml
vendored
1
.github/actionlint.yml
vendored
@@ -25,7 +25,6 @@ self-hosted-runner:
|
|||||||
- namespace-profile-32x64-ubuntu-2204
|
- namespace-profile-32x64-ubuntu-2204
|
||||||
# Namespace Ubuntu 24.04 (like ubuntu-latest)
|
# Namespace Ubuntu 24.04 (like ubuntu-latest)
|
||||||
- namespace-profile-2x4-ubuntu-2404
|
- namespace-profile-2x4-ubuntu-2404
|
||||||
- namespace-profile-8x32-ubuntu-2404
|
|
||||||
# Namespace Limited Preview
|
# Namespace Limited Preview
|
||||||
- namespace-profile-8x16-ubuntu-2004-arm-m4
|
- namespace-profile-8x16-ubuntu-2004-arm-m4
|
||||||
- namespace-profile-8x32-ubuntu-2004-arm-m4
|
- namespace-profile-8x32-ubuntu-2004-arm-m4
|
||||||
|
|||||||
12
.github/actions/build_docs/action.yml
vendored
12
.github/actions/build_docs/action.yml
vendored
@@ -19,18 +19,6 @@ runs:
|
|||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
run: ./script/linux
|
run: ./script/linux
|
||||||
|
|
||||||
- name: Install mold linker
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
run: ./script/install-mold
|
|
||||||
|
|
||||||
- name: Download WASI SDK
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
run: ./script/download-wasi-sdk
|
|
||||||
|
|
||||||
- name: Generate action metadata
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
run: ./script/generate-action-metadata
|
|
||||||
|
|
||||||
- name: Check for broken links (in MD)
|
- name: Check for broken links (in MD)
|
||||||
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
||||||
with:
|
with:
|
||||||
|
|||||||
25
.github/workflows/after_release.yml
vendored
25
.github/workflows/after_release.yml
vendored
@@ -5,27 +5,13 @@ on:
|
|||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag_name:
|
|
||||||
description: tag_name
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
prerelease:
|
|
||||||
description: prerelease
|
|
||||||
required: true
|
|
||||||
type: boolean
|
|
||||||
body:
|
|
||||||
description: body
|
|
||||||
type: string
|
|
||||||
default: ''
|
|
||||||
jobs:
|
jobs:
|
||||||
rebuild_releases_page:
|
rebuild_releases_page:
|
||||||
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
|
||||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: after_release::rebuild_releases_page::refresh_cloud_releases
|
- name: after_release::rebuild_releases_page::refresh_cloud_releases
|
||||||
run: curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name || inputs.tag_name }}
|
run: curl -fX POST https://cloud.zed.dev/releases/refresh?expect_tag=${{ github.event.release.tag_name }}
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
- name: after_release::rebuild_releases_page::redeploy_zed_dev
|
- name: after_release::rebuild_releases_page::redeploy_zed_dev
|
||||||
run: npm exec --yes -- vercel@37 --token="$VERCEL_TOKEN" --scope zed-industries redeploy https://zed.dev
|
run: npm exec --yes -- vercel@37 --token="$VERCEL_TOKEN" --scope zed-industries redeploy https://zed.dev
|
||||||
@@ -41,7 +27,7 @@ jobs:
|
|||||||
- id: get-release-url
|
- id: get-release-url
|
||||||
name: after_release::post_to_discord::get_release_url
|
name: after_release::post_to_discord::get_release_url
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.event.release.prerelease || inputs.prerelease }}" == "true" ]; then
|
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||||
URL="https://zed.dev/releases/preview"
|
URL="https://zed.dev/releases/preview"
|
||||||
else
|
else
|
||||||
URL="https://zed.dev/releases/stable"
|
URL="https://zed.dev/releases/stable"
|
||||||
@@ -54,9 +40,9 @@ jobs:
|
|||||||
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
|
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
|
||||||
with:
|
with:
|
||||||
stringToTruncate: |
|
stringToTruncate: |
|
||||||
📣 Zed [${{ github.event.release.tag_name || inputs.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
|
📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
|
||||||
|
|
||||||
${{ github.event.release.body || inputs.body }}
|
${{ github.event.release.body }}
|
||||||
maxLength: 2000
|
maxLength: 2000
|
||||||
truncationSymbol: '...'
|
truncationSymbol: '...'
|
||||||
- name: after_release::post_to_discord::discord_webhook_action
|
- name: after_release::post_to_discord::discord_webhook_action
|
||||||
@@ -70,7 +56,7 @@ jobs:
|
|||||||
- id: set-package-name
|
- id: set-package-name
|
||||||
name: after_release::publish_winget::set_package_name
|
name: after_release::publish_winget::set_package_name
|
||||||
run: |
|
run: |
|
||||||
if ("${{ github.event.release.prerelease || inputs.prerelease }}" -eq "true") {
|
if ("${{ github.event.release.prerelease }}" -eq "true") {
|
||||||
$PACKAGE_NAME = "ZedIndustries.Zed.Preview"
|
$PACKAGE_NAME = "ZedIndustries.Zed.Preview"
|
||||||
} else {
|
} else {
|
||||||
$PACKAGE_NAME = "ZedIndustries.Zed"
|
$PACKAGE_NAME = "ZedIndustries.Zed"
|
||||||
@@ -82,7 +68,6 @@ jobs:
|
|||||||
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
|
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
|
||||||
with:
|
with:
|
||||||
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
|
identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
|
||||||
release-tag: ${{ github.event.release.tag_name || inputs.tag_name }}
|
|
||||||
max-versions-to-keep: 5
|
max-versions-to-keep: 5
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
create_sentry_release:
|
create_sentry_release:
|
||||||
|
|||||||
132
.github/workflows/autofix_pr.yml
vendored
132
.github/workflows/autofix_pr.yml
vendored
@@ -1,132 +0,0 @@
|
|||||||
# Generated from xtask::workflows::autofix_pr
|
|
||||||
# Rebuild with `cargo xtask workflows`.
|
|
||||||
name: autofix_pr
|
|
||||||
run-name: 'autofix PR #${{ inputs.pr_number }}'
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
pr_number:
|
|
||||||
description: pr_number
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
run_clippy:
|
|
||||||
description: run_clippy
|
|
||||||
type: boolean
|
|
||||||
default: 'true'
|
|
||||||
jobs:
|
|
||||||
run_autofix:
|
|
||||||
runs-on: namespace-profile-16x32-ubuntu-2204
|
|
||||||
steps:
|
|
||||||
- name: steps::checkout_repo
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
with:
|
|
||||||
clean: false
|
|
||||||
- name: autofix_pr::run_autofix::checkout_pr
|
|
||||||
run: gh pr checkout ${{ inputs.pr_number }}
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: steps::setup_cargo_config
|
|
||||||
run: |
|
|
||||||
mkdir -p ./../.cargo
|
|
||||||
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: steps::cache_rust_dependencies_namespace
|
|
||||||
uses: namespacelabs/nscloud-cache-action@v1
|
|
||||||
with:
|
|
||||||
cache: rust
|
|
||||||
- name: steps::setup_linux
|
|
||||||
run: ./script/linux
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: steps::install_mold
|
|
||||||
run: ./script/install-mold
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: steps::download_wasi_sdk
|
|
||||||
run: ./script/download-wasi-sdk
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: steps::setup_pnpm
|
|
||||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
|
||||||
with:
|
|
||||||
version: '9'
|
|
||||||
- name: autofix_pr::run_autofix::run_prettier_fix
|
|
||||||
run: ./script/prettier --write
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: autofix_pr::run_autofix::run_cargo_fmt
|
|
||||||
run: cargo fmt --all
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: autofix_pr::run_autofix::run_cargo_fix
|
|
||||||
if: ${{ inputs.run_clippy }}
|
|
||||||
run: cargo fix --workspace --release --all-targets --all-features --allow-dirty --allow-staged
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: autofix_pr::run_autofix::run_clippy_fix
|
|
||||||
if: ${{ inputs.run_clippy }}
|
|
||||||
run: cargo clippy --workspace --release --all-targets --all-features --fix --allow-dirty --allow-staged
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- id: create-patch
|
|
||||||
name: autofix_pr::run_autofix::create_patch
|
|
||||||
run: |
|
|
||||||
if git diff --quiet; then
|
|
||||||
echo "No changes to commit"
|
|
||||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
git diff > autofix.patch
|
|
||||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: upload artifact autofix-patch
|
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
|
||||||
with:
|
|
||||||
name: autofix-patch
|
|
||||||
path: autofix.patch
|
|
||||||
if-no-files-found: ignore
|
|
||||||
retention-days: '1'
|
|
||||||
- name: steps::cleanup_cargo_config
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
rm -rf ./../.cargo
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
outputs:
|
|
||||||
has_changes: ${{ steps.create-patch.outputs.has_changes }}
|
|
||||||
commit_changes:
|
|
||||||
needs:
|
|
||||||
- run_autofix
|
|
||||||
if: needs.run_autofix.outputs.has_changes == 'true'
|
|
||||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
|
||||||
steps:
|
|
||||||
- id: get-app-token
|
|
||||||
name: steps::authenticate_as_zippy
|
|
||||||
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
|
|
||||||
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
|
|
||||||
- name: steps::checkout_repo_with_token
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
with:
|
|
||||||
clean: false
|
|
||||||
token: ${{ steps.get-app-token.outputs.token }}
|
|
||||||
- name: autofix_pr::commit_changes::checkout_pr
|
|
||||||
run: gh pr checkout ${{ inputs.pr_number }}
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
|
|
||||||
- name: autofix_pr::download_patch_artifact
|
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
|
|
||||||
with:
|
|
||||||
name: autofix-patch
|
|
||||||
- name: autofix_pr::commit_changes::apply_patch
|
|
||||||
run: git apply autofix.patch
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: autofix_pr::commit_changes::commit_and_push
|
|
||||||
run: |
|
|
||||||
git commit -am "Autofix"
|
|
||||||
git push
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
env:
|
|
||||||
GIT_COMMITTER_NAME: Zed Zippy
|
|
||||||
GIT_COMMITTER_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
|
|
||||||
GIT_AUTHOR_NAME: Zed Zippy
|
|
||||||
GIT_AUTHOR_EMAIL: 234243425+zed-zippy[bot]@users.noreply.github.com
|
|
||||||
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ inputs.pr_number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
2
.github/workflows/cherry_pick.yml
vendored
2
.github/workflows/cherry_pick.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
- id: get-app-token
|
- id: get-app-token
|
||||||
name: steps::authenticate_as_zippy
|
name: cherry_pick::run_cherry_pick::authenticate_as_zippy
|
||||||
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
|
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
|
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ jobs:
|
|||||||
CharlesChen0823
|
CharlesChen0823
|
||||||
chbk
|
chbk
|
||||||
cppcoffee
|
cppcoffee
|
||||||
davidbarsky
|
|
||||||
davewa
|
davewa
|
||||||
ddoemonn
|
ddoemonn
|
||||||
djsauble
|
djsauble
|
||||||
|
|||||||
@@ -1,40 +1,29 @@
|
|||||||
name: "Close Stale Issues"
|
name: "Close Stale Issues"
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 2 * * 5"
|
- cron: "0 8 31 DEC *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
debug-only:
|
|
||||||
description: "Run in dry-run mode (no changes made)"
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
operations-per-run:
|
|
||||||
description: "Max number of issues to process (default: 1000)"
|
|
||||||
type: number
|
|
||||||
default: 1000
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
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@997185467fa4f803885201cee163a9f38240193d # v10
|
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
Hi there!
|
Hi there! 👋
|
||||||
Zed development moves fast and a significant number of bugs become outdated.
|
|
||||||
If you can reproduce this bug on the latest stable Zed, please let us know by leaving a comment with the Zed version.
|
We're working to clean up our issue tracker by closing older bugs that might not be relevant anymore. If you are able to reproduce this issue in the latest version of Zed, please let us know by commenting on this issue, and it will be kept open. If you can't reproduce it, feel free to close the issue yourself. Otherwise, it will close automatically in 14 days.
|
||||||
If the bug doesn't appear for you anymore, feel free to close the issue yourself; otherwise, the bot will close it in a couple of weeks.
|
|
||||||
|
|
||||||
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 leave a comment with your Zed version so that we can reopen the 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: 60
|
days-before-stale: 60
|
||||||
days-before-close: 14
|
days-before-close: 14
|
||||||
only-issue-types: "Bug,Crash"
|
only-issue-types: "Bug,Crash"
|
||||||
operations-per-run: ${{ inputs.operations-per-run || 1000 }}
|
operations-per-run: 1000
|
||||||
ascending: true
|
ascending: true
|
||||||
enable-statistics: true
|
enable-statistics: true
|
||||||
debug-only: ${{ inputs.debug-only }}
|
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
exempt-issue-labels: "never stale"
|
exempt-issue-labels: "never stale"
|
||||||
|
|||||||
264
.github/workflows/docs_automation.yml
vendored
264
.github/workflows/docs_automation.yml
vendored
@@ -1,264 +0,0 @@
|
|||||||
name: Documentation Automation
|
|
||||||
|
|
||||||
on:
|
|
||||||
# push:
|
|
||||||
# branches: [main]
|
|
||||||
# paths:
|
|
||||||
# - 'crates/**'
|
|
||||||
# - 'extensions/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
pr_number:
|
|
||||||
description: 'PR number to analyze (gets full PR diff)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
trigger_sha:
|
|
||||||
description: 'Commit SHA to analyze (ignored if pr_number is set)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
|
|
||||||
DROID_MODEL: claude-opus-4-5-20251101
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docs-automation:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Droid CLI
|
|
||||||
id: install-droid
|
|
||||||
run: |
|
|
||||||
curl -fsSL https://app.factory.ai/cli | sh
|
|
||||||
echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV"
|
|
||||||
# Verify installation
|
|
||||||
"${HOME}/.local/bin/droid" --version
|
|
||||||
|
|
||||||
- name: Setup Node.js (for Prettier)
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
|
|
||||||
- name: Install Prettier
|
|
||||||
run: npm install -g prettier
|
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed
|
|
||||||
run: |
|
|
||||||
if [ -n "${{ inputs.pr_number }}" ]; then
|
|
||||||
# Get full PR diff
|
|
||||||
echo "Analyzing PR #${{ inputs.pr_number }}"
|
|
||||||
echo "source=pr" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ref=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
|
|
||||||
gh pr diff "${{ inputs.pr_number }}" --name-only > /tmp/changed_files.txt
|
|
||||||
elif [ -n "${{ inputs.trigger_sha }}" ]; then
|
|
||||||
# Get single commit diff
|
|
||||||
SHA="${{ inputs.trigger_sha }}"
|
|
||||||
echo "Analyzing commit $SHA"
|
|
||||||
echo "source=commit" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ref=$SHA" >> "$GITHUB_OUTPUT"
|
|
||||||
git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt
|
|
||||||
else
|
|
||||||
# Default to current commit
|
|
||||||
SHA="${{ github.sha }}"
|
|
||||||
echo "Analyzing commit $SHA"
|
|
||||||
echo "source=commit" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ref=$SHA" >> "$GITHUB_OUTPUT"
|
|
||||||
git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt || git diff --name-only HEAD~1 HEAD > /tmp/changed_files.txt
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Changed files:"
|
|
||||||
cat /tmp/changed_files.txt
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
|
|
||||||
# Phase 0: Guardrails are loaded via AGENTS.md in each phase
|
|
||||||
|
|
||||||
# Phase 2: Explore Repository (Read-Only - default)
|
|
||||||
- name: "Phase 2: Explore Repository"
|
|
||||||
id: phase2
|
|
||||||
run: |
|
|
||||||
"$DROID_BIN" exec \
|
|
||||||
-m "$DROID_MODEL" \
|
|
||||||
-f .factory/prompts/docs-automation/phase2-explore.md \
|
|
||||||
> /tmp/phase2-output.txt 2>&1 || true
|
|
||||||
echo "Repository exploration complete"
|
|
||||||
cat /tmp/phase2-output.txt
|
|
||||||
|
|
||||||
# Phase 3: Analyze Changes (Read-Only - default)
|
|
||||||
- name: "Phase 3: Analyze Changes"
|
|
||||||
id: phase3
|
|
||||||
run: |
|
|
||||||
CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
|
|
||||||
echo "Analyzing changes in: $CHANGED_FILES"
|
|
||||||
|
|
||||||
# Build prompt with context
|
|
||||||
cat > /tmp/phase3-prompt.md << 'EOF'
|
|
||||||
$(cat .factory/prompts/docs-automation/phase3-analyze.md)
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
### Changed Files
|
|
||||||
$CHANGED_FILES
|
|
||||||
|
|
||||||
### Phase 2 Output
|
|
||||||
$(cat /tmp/phase2-output.txt)
|
|
||||||
EOF
|
|
||||||
|
|
||||||
"$DROID_BIN" exec \
|
|
||||||
-m "$DROID_MODEL" \
|
|
||||||
"$(cat .factory/prompts/docs-automation/phase3-analyze.md)
|
|
||||||
|
|
||||||
Changed files: $CHANGED_FILES" \
|
|
||||||
> /tmp/phase3-output.md 2>&1 || true
|
|
||||||
echo "Change analysis complete"
|
|
||||||
cat /tmp/phase3-output.md
|
|
||||||
|
|
||||||
# Phase 4: Plan Documentation Impact (Read-Only - default)
|
|
||||||
- name: "Phase 4: Plan Documentation Impact"
|
|
||||||
id: phase4
|
|
||||||
run: |
|
|
||||||
"$DROID_BIN" exec \
|
|
||||||
-m "$DROID_MODEL" \
|
|
||||||
-f .factory/prompts/docs-automation/phase4-plan.md \
|
|
||||||
> /tmp/phase4-plan.md 2>&1 || true
|
|
||||||
echo "Documentation plan complete"
|
|
||||||
cat /tmp/phase4-plan.md
|
|
||||||
|
|
||||||
# Check if updates are required
|
|
||||||
if grep -q "NO_UPDATES_REQUIRED" /tmp/phase4-plan.md; then
|
|
||||||
echo "updates_required=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "updates_required=true" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Phase 5: Apply Plan (Write-Enabled with --auto medium)
|
|
||||||
- name: "Phase 5: Apply Documentation Plan"
|
|
||||||
id: phase5
|
|
||||||
if: steps.phase4.outputs.updates_required == 'true'
|
|
||||||
run: |
|
|
||||||
"$DROID_BIN" exec \
|
|
||||||
-m "$DROID_MODEL" \
|
|
||||||
--auto medium \
|
|
||||||
-f .factory/prompts/docs-automation/phase5-apply.md \
|
|
||||||
> /tmp/phase5-report.md 2>&1 || true
|
|
||||||
echo "Documentation updates applied"
|
|
||||||
cat /tmp/phase5-report.md
|
|
||||||
|
|
||||||
# Phase 5b: Format with Prettier
|
|
||||||
- name: "Phase 5b: Format with Prettier"
|
|
||||||
id: phase5b
|
|
||||||
if: steps.phase4.outputs.updates_required == 'true'
|
|
||||||
run: |
|
|
||||||
echo "Formatting documentation with Prettier..."
|
|
||||||
cd docs && prettier --write src/
|
|
||||||
|
|
||||||
echo "Verifying Prettier formatting passes..."
|
|
||||||
cd docs && prettier --check src/
|
|
||||||
|
|
||||||
echo "Prettier formatting complete"
|
|
||||||
|
|
||||||
# Phase 6: Summarize Changes (Read-Only - default)
|
|
||||||
- name: "Phase 6: Summarize Changes"
|
|
||||||
id: phase6
|
|
||||||
if: steps.phase4.outputs.updates_required == 'true'
|
|
||||||
run: |
|
|
||||||
# Get git diff of docs
|
|
||||||
git diff docs/src/ > /tmp/docs-diff.txt || true
|
|
||||||
|
|
||||||
"$DROID_BIN" exec \
|
|
||||||
-m "$DROID_MODEL" \
|
|
||||||
-f .factory/prompts/docs-automation/phase6-summarize.md \
|
|
||||||
> /tmp/phase6-summary.md 2>&1 || true
|
|
||||||
echo "Summary generated"
|
|
||||||
cat /tmp/phase6-summary.md
|
|
||||||
|
|
||||||
# Phase 7: Commit and Open PR
|
|
||||||
- name: "Phase 7: Create PR"
|
|
||||||
id: phase7
|
|
||||||
if: steps.phase4.outputs.updates_required == 'true'
|
|
||||||
run: |
|
|
||||||
# Check if there are actual changes
|
|
||||||
if git diff --quiet docs/src/; then
|
|
||||||
echo "No documentation changes detected"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configure git
|
|
||||||
git config user.name "factory-droid[bot]"
|
|
||||||
git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
# Daily batch branch - one branch per day, multiple commits accumulate
|
|
||||||
BRANCH_NAME="docs/auto-update-$(date +%Y-%m-%d)"
|
|
||||||
|
|
||||||
# Stash local changes from phase 5
|
|
||||||
git stash push -m "docs-automation-changes" -- docs/src/
|
|
||||||
|
|
||||||
# Check if branch already exists on remote
|
|
||||||
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
|
|
||||||
echo "Branch $BRANCH_NAME exists, checking out and updating..."
|
|
||||||
git fetch origin "$BRANCH_NAME"
|
|
||||||
git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
|
|
||||||
else
|
|
||||||
echo "Creating new branch $BRANCH_NAME..."
|
|
||||||
git checkout -b "$BRANCH_NAME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Apply stashed changes
|
|
||||||
git stash pop || true
|
|
||||||
|
|
||||||
# Stage and commit
|
|
||||||
git add docs/src/
|
|
||||||
SUMMARY=$(head -50 < /tmp/phase6-summary.md)
|
|
||||||
git commit -m "docs: auto-update documentation
|
|
||||||
|
|
||||||
${SUMMARY}
|
|
||||||
|
|
||||||
Triggered by: ${{ steps.changed.outputs.source }} ${{ steps.changed.outputs.ref }}
|
|
||||||
|
|
||||||
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>"
|
|
||||||
|
|
||||||
# Push
|
|
||||||
git push -u origin "$BRANCH_NAME"
|
|
||||||
|
|
||||||
# Check if PR already exists for this branch
|
|
||||||
EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number' || echo "")
|
|
||||||
|
|
||||||
if [ -n "$EXISTING_PR" ]; then
|
|
||||||
echo "PR #$EXISTING_PR already exists for branch $BRANCH_NAME, updated with new commit"
|
|
||||||
else
|
|
||||||
# Create new PR
|
|
||||||
gh pr create \
|
|
||||||
--title "docs: automated documentation update ($(date +%Y-%m-%d))" \
|
|
||||||
--body-file /tmp/phase6-summary.md \
|
|
||||||
--base main || true
|
|
||||||
echo "PR created on branch: $BRANCH_NAME"
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
|
|
||||||
# Summary output
|
|
||||||
- name: "Summary"
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "## Documentation Automation Summary" >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
|
|
||||||
if [ "${{ steps.phase4.outputs.updates_required }}" == "false" ]; then
|
|
||||||
echo "No documentation updates required for this change." >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
elif [ -f /tmp/phase6-summary.md ]; then
|
|
||||||
cat /tmp/phase6-summary.md >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
else
|
|
||||||
echo "Workflow completed. Check individual phase outputs for details." >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
fi
|
|
||||||
1
.github/workflows/extension_bump.yml
vendored
1
.github/workflows/extension_bump.yml
vendored
@@ -113,7 +113,6 @@ jobs:
|
|||||||
delete-branch: true
|
delete-branch: true
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
sign-commits: true
|
sign-commits: true
|
||||||
assignees: ${{ github.actor }}
|
|
||||||
timeout-minutes: 1
|
timeout-minutes: 1
|
||||||
create_version_label:
|
create_version_label:
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
4
.github/workflows/extension_tests.yml
vendored
4
.github/workflows/extension_tests.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- orchestrate
|
- orchestrate
|
||||||
if: needs.orchestrate.outputs.check_rust == 'true'
|
if: needs.orchestrate.outputs.check_rust == 'true'
|
||||||
runs-on: namespace-profile-4x8-ubuntu-2204
|
runs-on: namespace-profile-16x32-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: steps::checkout_repo
|
- name: steps::checkout_repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- orchestrate
|
- orchestrate
|
||||||
if: needs.orchestrate.outputs.check_extension == 'true'
|
if: needs.orchestrate.outputs.check_extension == 'true'
|
||||||
runs-on: namespace-profile-8x32-ubuntu-2404
|
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: steps::checkout_repo
|
- name: steps::checkout_repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -472,17 +472,11 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
|
||||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- id: get-app-token
|
|
||||||
name: steps::authenticate_as_zippy
|
|
||||||
uses: actions/create-github-app-token@bef1eaf1c0ac2b148ee2a0a74c65fbe6db0631f1
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
|
|
||||||
private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
|
|
||||||
- name: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
|
- name: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
|
||||||
run: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
|
run: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
notify_on_failure:
|
notify_on_failure:
|
||||||
needs:
|
needs:
|
||||||
- upload_release_assets
|
- upload_release_assets
|
||||||
|
|||||||
13
.github/workflows/run_tests.yml
vendored
13
.github/workflows/run_tests.yml
vendored
@@ -74,12 +74,9 @@ jobs:
|
|||||||
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2
|
||||||
with:
|
with:
|
||||||
version: '9'
|
version: '9'
|
||||||
- name: steps::prettier
|
- name: ./script/prettier
|
||||||
run: ./script/prettier
|
run: ./script/prettier
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
- name: steps::cargo_fmt
|
|
||||||
run: cargo fmt --all -- --check
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: ./script/check-todos
|
- name: ./script/check-todos
|
||||||
run: ./script/check-todos
|
run: ./script/check-todos
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
@@ -90,6 +87,9 @@ jobs:
|
|||||||
uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06
|
uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06
|
||||||
with:
|
with:
|
||||||
config: ./typos.toml
|
config: ./typos.toml
|
||||||
|
- name: steps::cargo_fmt
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
shell: bash -euxo pipefail {0}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
run_tests_windows:
|
run_tests_windows:
|
||||||
needs:
|
needs:
|
||||||
@@ -353,9 +353,6 @@ jobs:
|
|||||||
- name: steps::download_wasi_sdk
|
- name: steps::download_wasi_sdk
|
||||||
run: ./script/download-wasi-sdk
|
run: ./script/download-wasi-sdk
|
||||||
shell: bash -euxo pipefail {0}
|
shell: bash -euxo pipefail {0}
|
||||||
- name: ./script/generate-action-metadata
|
|
||||||
run: ./script/generate-action-metadata
|
|
||||||
shell: bash -euxo pipefail {0}
|
|
||||||
- name: run_tests::check_docs::install_mdbook
|
- name: run_tests::check_docs::install_mdbook
|
||||||
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
|
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
|
||||||
with:
|
with:
|
||||||
@@ -500,8 +497,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GIT_AUTHOR_NAME: Protobuf Action
|
GIT_AUTHOR_NAME: Protobuf Action
|
||||||
GIT_AUTHOR_EMAIL: ci@zed.dev
|
GIT_AUTHOR_EMAIL: ci@zed.dev
|
||||||
GIT_COMMITTER_NAME: Protobuf Action
|
|
||||||
GIT_COMMITTER_EMAIL: ci@zed.dev
|
|
||||||
steps:
|
steps:
|
||||||
- name: steps::checkout_repo
|
- name: steps::checkout_repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,7 +8,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.blob_store
|
.blob_store
|
||||||
.build
|
.build
|
||||||
.claude/settings.local.json
|
|
||||||
.envrc
|
.envrc
|
||||||
.flatpak-builder
|
.flatpak-builder
|
||||||
.idea
|
.idea
|
||||||
@@ -36,11 +35,10 @@
|
|||||||
DerivedData/
|
DerivedData/
|
||||||
Packages
|
Packages
|
||||||
xcuserdata/
|
xcuserdata/
|
||||||
crates/docs_preprocessor/actions.json
|
|
||||||
|
|
||||||
# Don't commit any secrets to the repo.
|
# Don't commit any secrets to the repo.
|
||||||
.env
|
.env
|
||||||
.env.secret.toml
|
.env.secret.toml
|
||||||
|
|
||||||
# `nix build` output
|
# `nix build` output
|
||||||
/result
|
/result
|
||||||
|
|||||||
3
.mailmap
3
.mailmap
@@ -141,9 +141,6 @@ Uladzislau Kaminski <i@uladkaminski.com>
|
|||||||
Uladzislau Kaminski <i@uladkaminski.com> <uladzislau_kaminski@epam.com>
|
Uladzislau Kaminski <i@uladkaminski.com> <uladzislau_kaminski@epam.com>
|
||||||
Vitaly Slobodin <vitaliy.slobodin@gmail.com>
|
Vitaly Slobodin <vitaliy.slobodin@gmail.com>
|
||||||
Vitaly Slobodin <vitaliy.slobodin@gmail.com> <vitaly_slobodin@fastmail.com>
|
Vitaly Slobodin <vitaliy.slobodin@gmail.com> <vitaly_slobodin@fastmail.com>
|
||||||
Yara <davidsk@zed.dev>
|
|
||||||
Yara <git@davidsk.dev>
|
|
||||||
Yara <git@yara.blue>
|
|
||||||
Will Bradley <williambbradley@gmail.com>
|
Will Bradley <williambbradley@gmail.com>
|
||||||
Will Bradley <williambbradley@gmail.com> <will@zed.dev>
|
Will Bradley <williambbradley@gmail.com> <will@zed.dev>
|
||||||
WindSoilder <WindSoilder@outlook.com>
|
WindSoilder <WindSoilder@outlook.com>
|
||||||
|
|||||||
6
.rules
6
.rules
@@ -26,12 +26,6 @@
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
# Timers in tests
|
|
||||||
|
|
||||||
* In GPUI tests, prefer GPUI executor timers over `smol::Timer::after(...)` when you need timeouts, delays, or to drive `run_until_parked()`:
|
|
||||||
- Use `cx.background_executor().timer(duration).await` (or `cx.background_executor.timer(duration).await` in `TestAppContext`) so the work is scheduled on GPUI's dispatcher.
|
|
||||||
- Avoid `smol::Timer::after(...)` for test timeouts when you rely on `run_until_parked()`, because it may not be tracked by GPUI's scheduler and can lead to "nothing left to run" when pumping.
|
|
||||||
|
|
||||||
# GPUI
|
# GPUI
|
||||||
|
|
||||||
GPUI is a UI framework which also provides primitives for state and concurrency management.
|
GPUI is a UI framework which also provides primitives for state and concurrency management.
|
||||||
|
|||||||
@@ -15,16 +15,15 @@ with the community to improve the product in ways we haven't thought of (or had
|
|||||||
|
|
||||||
In particular we love PRs that are:
|
In particular we love PRs that are:
|
||||||
|
|
||||||
- Fixing or extending the docs.
|
- Fixes to existing bugs and issues.
|
||||||
- Fixing bugs.
|
- Small enhancements to existing features, particularly to make them work for more people.
|
||||||
- Small enhancements to existing features to make them work for more people (making things work on more platforms/modes/whatever).
|
|
||||||
- Small extra features, like keybindings or actions you miss from other editors or extensions.
|
- Small extra features, like keybindings or actions you miss from other editors or extensions.
|
||||||
- Part of a Community Program like [Let's Git Together](https://github.com/zed-industries/zed/issues/41541).
|
- Work towards shipping larger features on our roadmap.
|
||||||
|
|
||||||
If you're looking for concrete ideas:
|
If you're looking for concrete ideas:
|
||||||
|
|
||||||
- [Triaged bugs with confirmed steps to reproduce](https://github.com/zed-industries/zed/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3Astate%3Areproducible).
|
- Our [top-ranking issues](https://github.com/zed-industries/zed/issues/5393) based on votes by the community.
|
||||||
- [Area labels](https://github.com/zed-industries/zed/labels?q=area%3A*) to browse bugs in a specific part of the product you care about (after clicking on an area label, add type:Bug to the search).
|
- Our [public roadmap](https://zed.dev/roadmap) contains a rough outline of our near-term priorities for Zed.
|
||||||
|
|
||||||
## Sending changes
|
## Sending changes
|
||||||
|
|
||||||
@@ -38,17 +37,9 @@ like, sorry).
|
|||||||
Although we will take a look, we tend to only merge about half the PRs that are
|
Although we will take a look, we tend to only merge about half the PRs that are
|
||||||
submitted. If you'd like your PR to have the best chance of being merged:
|
submitted. If you'd like your PR to have the best chance of being merged:
|
||||||
|
|
||||||
- Make sure the change is **desired**: we're always happy to accept bugfixes,
|
- Include a clear description of what you're solving, and why it's important to you.
|
||||||
but features should be confirmed with us first if you aim to avoid wasted
|
- Include tests.
|
||||||
effort. If there isn't already a GitHub issue for your feature with staff
|
- If it changes the UI, attach screenshots or screen recordings.
|
||||||
confirmation that we want it, start with a GitHub discussion rather than a PR.
|
|
||||||
- Include a clear description of **what you're solving**, and why it's important.
|
|
||||||
- Include **tests**.
|
|
||||||
- If it changes the UI, attach **screenshots** or screen recordings.
|
|
||||||
- Make the PR about **one thing only**, e.g. if it's a bugfix, don't add two
|
|
||||||
features and a refactoring on top of that.
|
|
||||||
- Keep AI assistance under your judgement and responsibility: it's unlikely
|
|
||||||
we'll merge a vibe-coded PR that the author doesn't understand.
|
|
||||||
|
|
||||||
The internal advice for reviewers is as follows:
|
The internal advice for reviewers is as follows:
|
||||||
|
|
||||||
@@ -59,9 +50,10 @@ The internal advice for reviewers is as follows:
|
|||||||
If you need more feedback from us: the best way is to be responsive to
|
If you need more feedback from us: the best way is to be responsive to
|
||||||
Github comments, or to offer up time to pair with us.
|
Github comments, or to offer up time to pair with us.
|
||||||
|
|
||||||
If you need help deciding how to fix a bug, or finish implementing a feature
|
If you are making a larger change, or need advice on how to finish the change
|
||||||
that we've agreed we want, please open a PR early so we can discuss how to make
|
you're making, please open the PR early. We would love to help you get
|
||||||
the change with code in hand.
|
things right, and it's often easier to see how to solve a problem before the
|
||||||
|
diff gets too big.
|
||||||
|
|
||||||
## Things we will (probably) not merge
|
## Things we will (probably) not merge
|
||||||
|
|
||||||
@@ -69,11 +61,11 @@ Although there are few hard and fast rules, typically we don't merge:
|
|||||||
|
|
||||||
- Anything that can be provided by an extension. For example a new language, or theme. For adding themes or support for a new language to Zed, check out our [docs on developing extensions](https://zed.dev/docs/extensions/developing-extensions).
|
- Anything that can be provided by an extension. For example a new language, or theme. For adding themes or support for a new language to Zed, check out our [docs on developing extensions](https://zed.dev/docs/extensions/developing-extensions).
|
||||||
- New file icons. Zed's default icon theme consists of icons that are hand-designed to fit together in a cohesive manner, please don't submit PRs with off-the-shelf SVGs.
|
- New file icons. Zed's default icon theme consists of icons that are hand-designed to fit together in a cohesive manner, please don't submit PRs with off-the-shelf SVGs.
|
||||||
- Features where (in our subjective opinion) the extra complexity isn't worth it for the number of people who will benefit.
|
|
||||||
- Giant refactorings.
|
- Giant refactorings.
|
||||||
- Non-trivial changes with no tests.
|
- Non-trivial changes with no tests.
|
||||||
- Stylistic code changes that do not alter any app logic. Reducing allocations, removing `.unwrap()`s, fixing typos is great; making code "more readable" — maybe not so much.
|
- Stylistic code changes that do not alter any app logic. Reducing allocations, removing `.unwrap()`s, fixing typos is great; making code "more readable" — maybe not so much.
|
||||||
- Anything that seems AI-generated without understanding the output.
|
- Features where (in our subjective opinion) the extra complexity isn't worth it for the number of people who will benefit.
|
||||||
|
- Anything that seems completely AI generated.
|
||||||
|
|
||||||
## Bird's-eye view of Zed
|
## Bird's-eye view of Zed
|
||||||
|
|
||||||
|
|||||||
601
Cargo.lock
generated
601
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
36
Cargo.toml
36
Cargo.toml
@@ -9,7 +9,6 @@ members = [
|
|||||||
"crates/agent_servers",
|
"crates/agent_servers",
|
||||||
"crates/agent_settings",
|
"crates/agent_settings",
|
||||||
"crates/agent_ui",
|
"crates/agent_ui",
|
||||||
"crates/agent_ui_v2",
|
|
||||||
"crates/ai_onboarding",
|
"crates/ai_onboarding",
|
||||||
"crates/anthropic",
|
"crates/anthropic",
|
||||||
"crates/askpass",
|
"crates/askpass",
|
||||||
@@ -33,13 +32,13 @@ members = [
|
|||||||
"crates/cloud_api_client",
|
"crates/cloud_api_client",
|
||||||
"crates/cloud_api_types",
|
"crates/cloud_api_types",
|
||||||
"crates/cloud_llm_client",
|
"crates/cloud_llm_client",
|
||||||
|
"crates/cloud_zeta2_prompt",
|
||||||
"crates/collab",
|
"crates/collab",
|
||||||
"crates/collab_ui",
|
"crates/collab_ui",
|
||||||
"crates/collections",
|
"crates/collections",
|
||||||
"crates/command_palette",
|
"crates/command_palette",
|
||||||
"crates/command_palette_hooks",
|
"crates/command_palette_hooks",
|
||||||
"crates/component",
|
"crates/component",
|
||||||
"crates/component_preview",
|
|
||||||
"crates/context_server",
|
"crates/context_server",
|
||||||
"crates/copilot",
|
"crates/copilot",
|
||||||
"crates/crashes",
|
"crates/crashes",
|
||||||
@@ -193,19 +192,16 @@ members = [
|
|||||||
"crates/vercel",
|
"crates/vercel",
|
||||||
"crates/vim",
|
"crates/vim",
|
||||||
"crates/vim_mode_setting",
|
"crates/vim_mode_setting",
|
||||||
"crates/which_key",
|
|
||||||
"crates/watch",
|
"crates/watch",
|
||||||
"crates/web_search",
|
"crates/web_search",
|
||||||
"crates/web_search_providers",
|
"crates/web_search_providers",
|
||||||
"crates/workspace",
|
"crates/workspace",
|
||||||
"crates/worktree",
|
"crates/worktree",
|
||||||
"crates/worktree_benchmarks",
|
|
||||||
"crates/x_ai",
|
"crates/x_ai",
|
||||||
"crates/zed",
|
"crates/zed",
|
||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
"crates/zed_env_vars",
|
"crates/zed_env_vars",
|
||||||
"crates/edit_prediction_cli",
|
"crates/edit_prediction_cli",
|
||||||
"crates/zeta_prompt",
|
|
||||||
"crates/zlog",
|
"crates/zlog",
|
||||||
"crates/zlog_settings",
|
"crates/zlog_settings",
|
||||||
"crates/ztracing",
|
"crates/ztracing",
|
||||||
@@ -246,7 +242,6 @@ action_log = { path = "crates/action_log" }
|
|||||||
agent = { path = "crates/agent" }
|
agent = { path = "crates/agent" }
|
||||||
activity_indicator = { path = "crates/activity_indicator" }
|
activity_indicator = { path = "crates/activity_indicator" }
|
||||||
agent_ui = { path = "crates/agent_ui" }
|
agent_ui = { path = "crates/agent_ui" }
|
||||||
agent_ui_v2 = { path = "crates/agent_ui_v2" }
|
|
||||||
agent_settings = { path = "crates/agent_settings" }
|
agent_settings = { path = "crates/agent_settings" }
|
||||||
agent_servers = { path = "crates/agent_servers" }
|
agent_servers = { path = "crates/agent_servers" }
|
||||||
ai_onboarding = { path = "crates/ai_onboarding" }
|
ai_onboarding = { path = "crates/ai_onboarding" }
|
||||||
@@ -271,12 +266,12 @@ clock = { path = "crates/clock" }
|
|||||||
cloud_api_client = { path = "crates/cloud_api_client" }
|
cloud_api_client = { path = "crates/cloud_api_client" }
|
||||||
cloud_api_types = { path = "crates/cloud_api_types" }
|
cloud_api_types = { path = "crates/cloud_api_types" }
|
||||||
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
||||||
|
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
|
||||||
collab_ui = { path = "crates/collab_ui" }
|
collab_ui = { path = "crates/collab_ui" }
|
||||||
collections = { path = "crates/collections", version = "0.1.0" }
|
collections = { path = "crates/collections", version = "0.1.0" }
|
||||||
command_palette = { path = "crates/command_palette" }
|
command_palette = { path = "crates/command_palette" }
|
||||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||||
component = { path = "crates/component" }
|
component = { path = "crates/component" }
|
||||||
component_preview = { path = "crates/component_preview" }
|
|
||||||
context_server = { path = "crates/context_server" }
|
context_server = { path = "crates/context_server" }
|
||||||
copilot = { path = "crates/copilot" }
|
copilot = { path = "crates/copilot" }
|
||||||
crashes = { path = "crates/crashes" }
|
crashes = { path = "crates/crashes" }
|
||||||
@@ -419,7 +414,6 @@ util_macros = { path = "crates/util_macros" }
|
|||||||
vercel = { path = "crates/vercel" }
|
vercel = { path = "crates/vercel" }
|
||||||
vim = { path = "crates/vim" }
|
vim = { path = "crates/vim" }
|
||||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||||
which_key = { path = "crates/which_key" }
|
|
||||||
|
|
||||||
watch = { path = "crates/watch" }
|
watch = { path = "crates/watch" }
|
||||||
web_search = { path = "crates/web_search" }
|
web_search = { path = "crates/web_search" }
|
||||||
@@ -431,7 +425,6 @@ zed = { path = "crates/zed" }
|
|||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
zed_env_vars = { path = "crates/zed_env_vars" }
|
zed_env_vars = { path = "crates/zed_env_vars" }
|
||||||
edit_prediction = { path = "crates/edit_prediction" }
|
edit_prediction = { path = "crates/edit_prediction" }
|
||||||
zeta_prompt = { path = "crates/zeta_prompt" }
|
|
||||||
zlog = { path = "crates/zlog" }
|
zlog = { path = "crates/zlog" }
|
||||||
zlog_settings = { path = "crates/zlog_settings" }
|
zlog_settings = { path = "crates/zlog_settings" }
|
||||||
ztracing = { path = "crates/ztracing" }
|
ztracing = { path = "crates/ztracing" }
|
||||||
@@ -441,7 +434,7 @@ ztracing_macro = { path = "crates/ztracing_macro" }
|
|||||||
# External crates
|
# External crates
|
||||||
#
|
#
|
||||||
|
|
||||||
agent-client-protocol = { version = "=0.9.2", features = ["unstable"] }
|
agent-client-protocol = { version = "=0.9.0", features = ["unstable"] }
|
||||||
aho-corasick = "1.1"
|
aho-corasick = "1.1"
|
||||||
alacritty_terminal = "0.25.1-rc1"
|
alacritty_terminal = "0.25.1-rc1"
|
||||||
any_vec = "0.14"
|
any_vec = "0.14"
|
||||||
@@ -460,15 +453,15 @@ async-task = "4.7"
|
|||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
async-tungstenite = "0.31.0"
|
async-tungstenite = "0.31.0"
|
||||||
async_zip = { version = "0.0.18", features = ["deflate", "deflate64"] }
|
async_zip = { version = "0.0.18", features = ["deflate", "deflate64"] }
|
||||||
aws-config = { version = "1.8.10", features = ["behavior-version-latest"] }
|
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
|
||||||
aws-credential-types = { version = "1.2.8", features = [
|
aws-credential-types = { version = "1.2.2", features = [
|
||||||
"hardcoded-credentials",
|
"hardcoded-credentials",
|
||||||
] }
|
] }
|
||||||
aws-sdk-bedrockruntime = { version = "1.112.0", features = [
|
aws-sdk-bedrockruntime = { version = "1.80.0", features = [
|
||||||
"behavior-version-latest",
|
"behavior-version-latest",
|
||||||
] }
|
] }
|
||||||
aws-smithy-runtime-api = { version = "1.9.2", features = ["http-1x", "client"] }
|
aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
|
||||||
aws-smithy-types = { version = "1.3.4", features = ["http-body-1-x"] }
|
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
@@ -481,7 +474,6 @@ bytes = "1.0"
|
|||||||
cargo_metadata = "0.19"
|
cargo_metadata = "0.19"
|
||||||
cargo_toml = "0.21"
|
cargo_toml = "0.21"
|
||||||
cfg-if = "1.0.3"
|
cfg-if = "1.0.3"
|
||||||
chardetng = "0.1"
|
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
ciborium = "0.2"
|
ciborium = "0.2"
|
||||||
circular-buffer = "1.0"
|
circular-buffer = "1.0"
|
||||||
@@ -505,7 +497,6 @@ dotenvy = "0.15.0"
|
|||||||
ec4rs = "1.1"
|
ec4rs = "1.1"
|
||||||
emojis = "0.6.1"
|
emojis = "0.6.1"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
encoding_rs = "0.8"
|
|
||||||
exec = "0.3.1"
|
exec = "0.3.1"
|
||||||
fancy-regex = "0.16.0"
|
fancy-regex = "0.16.0"
|
||||||
fork = "0.4.0"
|
fork = "0.4.0"
|
||||||
@@ -640,7 +631,7 @@ shellexpand = "2.1.0"
|
|||||||
shlex = "1.3.0"
|
shlex = "1.3.0"
|
||||||
simplelog = "0.12.2"
|
simplelog = "0.12.2"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
smallvec = { version = "1.6", features = ["union", "const_new"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
smol = "2.0"
|
smol = "2.0"
|
||||||
sqlformat = "0.2"
|
sqlformat = "0.2"
|
||||||
stacksafe = "0.1"
|
stacksafe = "0.1"
|
||||||
@@ -666,11 +657,10 @@ time = { version = "0.3", features = [
|
|||||||
tiny_http = "0.8"
|
tiny_http = "0.8"
|
||||||
tokio = { version = "1" }
|
tokio = { version = "1" }
|
||||||
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
|
||||||
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io", "tokio"] }
|
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
|
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
|
||||||
tower-http = "0.4.4"
|
tower-http = "0.4.4"
|
||||||
tree-sitter = { version = "0.26", features = ["wasm"] }
|
tree-sitter = { version = "0.25.10", features = ["wasm"] }
|
||||||
tree-sitter-bash = "0.25.1"
|
tree-sitter-bash = "0.25.1"
|
||||||
tree-sitter-c = "0.23"
|
tree-sitter-c = "0.23"
|
||||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
|
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
|
||||||
@@ -704,7 +694,7 @@ uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
|||||||
walkdir = "2.5"
|
walkdir = "2.5"
|
||||||
wasm-encoder = "0.221"
|
wasm-encoder = "0.221"
|
||||||
wasmparser = "0.221"
|
wasmparser = "0.221"
|
||||||
wasmtime = { version = "33", default-features = false, features = [
|
wasmtime = { version = "29", default-features = false, features = [
|
||||||
"async",
|
"async",
|
||||||
"demangle",
|
"demangle",
|
||||||
"runtime",
|
"runtime",
|
||||||
@@ -713,7 +703,7 @@ wasmtime = { version = "33", default-features = false, features = [
|
|||||||
"incremental-cache",
|
"incremental-cache",
|
||||||
"parallel-compilation",
|
"parallel-compilation",
|
||||||
] }
|
] }
|
||||||
wasmtime-wasi = "33"
|
wasmtime-wasi = "29"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
windows-core = "0.61"
|
windows-core = "0.61"
|
||||||
@@ -864,6 +854,8 @@ unexpected_cfgs = { level = "allow" }
|
|||||||
dbg_macro = "deny"
|
dbg_macro = "deny"
|
||||||
todo = "deny"
|
todo = "deny"
|
||||||
|
|
||||||
|
# This is not a style lint, see https://github.com/rust-lang/rust-clippy/pull/15454
|
||||||
|
# Remove when the lint gets promoted to `suspicious`.
|
||||||
declare_interior_mutable_const = "deny"
|
declare_interior_mutable_const = "deny"
|
||||||
|
|
||||||
redundant_clone = "deny"
|
redundant_clone = "deny"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax = docker/dockerfile:1.2
|
# syntax = docker/dockerfile:1.2
|
||||||
|
|
||||||
FROM rust:1.92-bookworm as builder
|
FROM rust:1.91.1-bookworm as builder
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Welcome to Zed, a high-performance, multiplayer code editor from the creators of
|
|||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
On macOS, Linux, and Windows you can [download Zed directly](https://zed.dev/download) or install Zed via your local package manager ([macOS](https://zed.dev/docs/installation#macos)/[Linux](https://zed.dev/docs/linux#installing-via-a-package-manager)/[Windows](https://zed.dev/docs/windows#package-managers)).
|
On macOS, Linux, and Windows you can [download Zed directly](https://zed.dev/download) or [install Zed via your local package manager](https://zed.dev/docs/linux#installing-via-a-package-manager).
|
||||||
|
|
||||||
Other platforms are not yet available:
|
Other platforms are not yet available:
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ Other platforms are not yet available:
|
|||||||
- [Building Zed for macOS](./docs/src/development/macos.md)
|
- [Building Zed for macOS](./docs/src/development/macos.md)
|
||||||
- [Building Zed for Linux](./docs/src/development/linux.md)
|
- [Building Zed for Linux](./docs/src/development/linux.md)
|
||||||
- [Building Zed for Windows](./docs/src/development/windows.md)
|
- [Building Zed for Windows](./docs/src/development/windows.md)
|
||||||
|
- [Running Collaboration Locally](./docs/src/development/local-collaboration.md)
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ ai
|
|||||||
= @rtfeldman
|
= @rtfeldman
|
||||||
|
|
||||||
audio
|
audio
|
||||||
= @yara-blue
|
= @dvdsk
|
||||||
|
|
||||||
crashes
|
crashes
|
||||||
= @p1n3appl3
|
= @p1n3appl3
|
||||||
@@ -53,7 +53,7 @@ extension
|
|||||||
git
|
git
|
||||||
= @cole-miller
|
= @cole-miller
|
||||||
= @danilo-leal
|
= @danilo-leal
|
||||||
= @yara-blue
|
= @dvdsk
|
||||||
= @kubkon
|
= @kubkon
|
||||||
= @Anthony-Eid
|
= @Anthony-Eid
|
||||||
= @cameron1024
|
= @cameron1024
|
||||||
@@ -76,7 +76,7 @@ languages
|
|||||||
|
|
||||||
linux
|
linux
|
||||||
= @cole-miller
|
= @cole-miller
|
||||||
= @yara-blue
|
= @dvdsk
|
||||||
= @p1n3appl3
|
= @p1n3appl3
|
||||||
= @probably-neb
|
= @probably-neb
|
||||||
= @smitbarmase
|
= @smitbarmase
|
||||||
@@ -92,7 +92,7 @@ multi_buffer
|
|||||||
= @SomeoneToIgnore
|
= @SomeoneToIgnore
|
||||||
|
|
||||||
pickers
|
pickers
|
||||||
= @yara-blue
|
= @dvdsk
|
||||||
= @p1n3appl3
|
= @p1n3appl3
|
||||||
= @SomeoneToIgnore
|
= @SomeoneToIgnore
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M6.2224 1.32129L5.2036 4.41875C5.15145 4.57727 5.06282 4.72134 4.94481 4.83934C4.82681 4.95735 4.68274 5.04598 4.52422 5.09813L1.42676 6.11693L4.52422 7.13574C4.68274 7.18788 4.82681 7.27652 4.94481 7.39453C5.06282 7.51253 5.15145 7.6566 5.2036 7.81512L6.2224 10.9126L7.24121 7.81512C7.29335 7.6566 7.38199 7.51253 7.5 7.39453C7.618 7.27652 7.76207 7.18788 7.9206 7.13574L11.018 6.11693L7.9206 5.09813C7.76207 5.04598 7.618 4.95735 7.5 4.83934C7.38199 4.72134 7.29335 4.57727 7.24121 4.41875L6.2224 1.32129Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M9.76681 13.9373C9.76681 13.6048 9.95997 13.3083 10.5126 12.7917L11.8872 11.4978C12.3545 11.0575 12.5612 10.77 12.5612 10.4735C12.5612 10.1411 12.3185 9.91643 11.9681 9.91643C11.6986 9.91643 11.5054 10.0242 11.2673 10.3208C10.9933 10.6622 10.7956 10.779 10.4946 10.779C10.0633 10.779 9.75781 10.4915 9.75781 10.0916C9.75781 9.21559 10.8136 8.44287 12.067 8.44287C13.3743 8.44287 14.3492 9.22907 14.3492 10.2848C14.3492 10.9452 13.9988 11.5742 13.2845 12.2077L12.2242 13.1511V13.223H13.7292C14.2503 13.223 14.5738 13.5015 14.5738 13.9552C14.5738 14.4089 14.2593 14.6785 13.7292 14.6785H10.5979C10.1037 14.6785 9.76681 14.3775 9.76681 13.9373Z" fill="black"/>
|
|
||||||
<path d="M12.8994 1.32129V4.00482M11.5576 2.66302H14.2412" stroke="black" stroke-opacity="0.75" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,12 +10,12 @@
|
|||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// "shift shift": "file_finder::Toggle"
|
// "shift shift": "file_finder::Toggle"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == insert",
|
"context": "Editor && vim_mode == insert",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// "j k": "vim::NormalBefore"
|
// "j k": "vim::NormalBefore"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-f5": "workspace::Reload", // window:reload
|
"ctrl-shift-f5": "workspace::Reload", // window:reload
|
||||||
"ctrl-k ctrl-n": "workspace::ActivatePreviousPane", // window:focus-next-pane
|
"ctrl-k ctrl-n": "workspace::ActivatePreviousPane", // window:focus-next-pane
|
||||||
"ctrl-k ctrl-p": "workspace::ActivateNextPane", // window:focus-previous-pane
|
"ctrl-k ctrl-p": "workspace::ActivateNextPane" // window:focus-previous-pane
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
|
||||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", // editor:lower-case
|
"ctrl-k ctrl-l": "editor::ConvertToLowerCase" // editor:lower-case
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
|
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
|
||||||
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide", // markdown-preview:toggle
|
"ctrl-shift-m": "markdown::OpenPreviewToTheSide", // markdown-preview:toggle
|
||||||
"ctrl-r": "outline::Toggle", // symbols-view:toggle-project-symbols
|
"ctrl-r": "outline::Toggle" // symbols-view:toggle-project-symbols
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
|
||||||
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
|
||||||
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
|
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
|
||||||
"ctrl-shift-f3": "search::SelectPreviousMatch", // find-and-replace:find-previous-selected
|
"ctrl-shift-f3": "search::SelectPreviousMatch" // find-and-replace:find-previous-selected
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||||
"ctrl-k ctrl-b": "workspace::ToggleLeftDock", // tree-view:toggle
|
"ctrl-k ctrl-b": "workspace::ToggleLeftDock", // tree-view:toggle
|
||||||
"ctrl-t": "file_finder::Toggle", // fuzzy-finder:toggle-file-finder
|
"ctrl-t": "file_finder::Toggle", // fuzzy-finder:toggle-file-finder
|
||||||
"ctrl-r": "project_symbols::Toggle", // symbols-view:toggle-project-symbols
|
"ctrl-r": "project_symbols::Toggle" // symbols-view:toggle-project-symbols
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
"ctrl-6": ["pane::ActivateItem", 5], // tree-view:open-selected-entry-in-pane-6
|
"ctrl-6": ["pane::ActivateItem", 5], // tree-view:open-selected-entry-in-pane-6
|
||||||
"ctrl-7": ["pane::ActivateItem", 6], // tree-view:open-selected-entry-in-pane-7
|
"ctrl-7": ["pane::ActivateItem", 6], // tree-view:open-selected-entry-in-pane-7
|
||||||
"ctrl-8": ["pane::ActivateItem", 7], // tree-view:open-selected-entry-in-pane-8
|
"ctrl-8": ["pane::ActivateItem", 7], // tree-view:open-selected-entry-in-pane-8
|
||||||
"ctrl-9": ["pane::ActivateItem", 8], // tree-view:open-selected-entry-in-pane-9
|
"ctrl-9": ["pane::ActivateItem", 8] // tree-view:open-selected-entry-in-pane-9
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
@@ -75,8 +75,8 @@
|
|||||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||||
"ctrl-x": "project_panel::Cut", // tree-view:cut
|
"ctrl-x": "project_panel::Cut", // tree-view:cut
|
||||||
"ctrl-c": "project_panel::Copy", // tree-view:copy
|
"ctrl-c": "project_panel::Copy", // tree-view:copy
|
||||||
"ctrl-v": "project_panel::Paste", // tree-view:paste
|
"ctrl-v": "project_panel::Paste" // tree-view:paste
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel && not_editing",
|
"context": "ProjectPanel && not_editing",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"d": "project_panel::Duplicate", // tree-view:duplicate
|
"d": "project_panel::Duplicate", // tree-view:duplicate
|
||||||
"home": "menu::SelectFirst", // core:move-to-top
|
"home": "menu::SelectFirst", // core:move-to-top
|
||||||
"end": "menu::SelectLast", // core:move-to-bottom
|
"end": "menu::SelectLast", // core:move-to-bottom
|
||||||
"shift-a": "project_panel::NewDirectory", // tree-view:add-folder
|
"shift-a": "project_panel::NewDirectory" // tree-view:add-folder
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
"ctrl-shift-i": "agent::ToggleFocus",
|
"ctrl-shift-i": "agent::ToggleFocus",
|
||||||
"ctrl-l": "agent::ToggleFocus",
|
"ctrl-l": "agent::ToggleFocus",
|
||||||
"ctrl-shift-l": "agent::ToggleFocus",
|
"ctrl-shift-l": "agent::ToggleFocus",
|
||||||
"ctrl-shift-j": "agent::OpenSettings",
|
"ctrl-shift-j": "agent::OpenSettings"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
@@ -20,18 +20,18 @@
|
|||||||
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||||
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||||
"ctrl-k": "assistant::InlineAssist",
|
"ctrl-k": "assistant::InlineAssist",
|
||||||
"ctrl-shift-k": "assistant::InsertIntoEditor",
|
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "InlineAssistant > Editor",
|
"context": "InlineAssistEditor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-backspace": "editor::Cancel",
|
"ctrl-shift-backspace": "editor::Cancel"
|
||||||
// "alt-enter": // Quick Question
|
// "alt-enter": // Quick Question
|
||||||
// "ctrl-shift-enter": // Full File Context
|
// "ctrl-shift-enter": // Full File Context
|
||||||
// "ctrl-shift-k": // Toggle input focus (editor <> inline assist)
|
// "ctrl-shift-k": // Toggle input focus (editor <> inline assist)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"ctrl-shift-backspace": "editor::Cancel",
|
"ctrl-shift-backspace": "editor::Cancel",
|
||||||
"ctrl-r": "agent::NewThread",
|
"ctrl-r": "agent::NewThread",
|
||||||
"ctrl-shift-v": "editor::Paste",
|
"ctrl-shift-v": "editor::Paste",
|
||||||
"ctrl-shift-k": "assistant::InsertIntoEditor",
|
"ctrl-shift-k": "assistant::InsertIntoEditor"
|
||||||
// "escape": "agent::ToggleFocus"
|
// "escape": "agent::ToggleFocus"
|
||||||
///// Enable when Zed supports multiple thread tabs
|
///// Enable when Zed supports multiple thread tabs
|
||||||
// "ctrl-t": // new thread tab
|
// "ctrl-t": // new thread tab
|
||||||
@@ -56,29 +56,28 @@
|
|||||||
///// Enable if Zed adds support for keyboard navigation of thread elements
|
///// Enable if Zed adds support for keyboard navigation of thread elements
|
||||||
// "tab": // cycle to next message
|
// "tab": // cycle to next message
|
||||||
// "shift-tab": // cycle to previous message
|
// "shift-tab": // cycle to previous message
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && editor_agent_diff",
|
"context": "Editor && editor_agent_diff",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "agent::KeepAll",
|
"ctrl-enter": "agent::KeepAll",
|
||||||
"ctrl-backspace": "agent::RejectAll",
|
"ctrl-backspace": "agent::RejectAll"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full && edit_prediction",
|
"context": "Editor && mode == full && edit_prediction",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-right": "editor::AcceptNextWordEditPrediction",
|
"ctrl-right": "editor::AcceptPartialEditPrediction"
|
||||||
"ctrl-down": "editor::AcceptNextLineEditPrediction",
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Terminal",
|
"context": "Terminal",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-k": "assistant::InlineAssist",
|
"ctrl-k": "assistant::InlineAssist"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-g": "menu::Cancel",
|
"ctrl-g": "menu::Cancel"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Workaround to avoid falling back to default bindings.
|
// Workaround to avoid falling back to default bindings.
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||||
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
|
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
|
||||||
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
|
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
|
||||||
"ctrl-n": null, // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
|
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||||
"ctrl-r": "buffer_search::Deploy", // isearch-backward
|
"ctrl-r": "buffer_search::Deploy", // isearch-backward
|
||||||
"alt-^": "editor::JoinLines", // join-line
|
"alt-^": "editor::JoinLines", // join-line
|
||||||
"alt-q": "editor::Rewrap", // fill-paragraph
|
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && selection_mode", // region selection
|
"context": "Editor && selection_mode", // region selection
|
||||||
@@ -119,22 +119,22 @@
|
|||||||
"alt->": "editor::SelectToEnd",
|
"alt->": "editor::SelectToEnd",
|
||||||
"ctrl-home": "editor::SelectToBeginning",
|
"ctrl-home": "editor::SelectToBeginning",
|
||||||
"ctrl-end": "editor::SelectToEnd",
|
"ctrl-end": "editor::SelectToEnd",
|
||||||
"ctrl-g": "editor::Cancel",
|
"ctrl-g": "editor::Cancel"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::ContextMenuPrevious",
|
"ctrl-p": "editor::ContextMenuPrevious",
|
||||||
"ctrl-n": "editor::ContextMenuNext",
|
"ctrl-n": "editor::ContextMenuNext"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && showing_signature_help && !showing_completions",
|
"context": "Editor && showing_signature_help && !showing_completions",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||||
"ctrl-n": "editor::SignatureHelpNext",
|
"ctrl-n": "editor::SignatureHelpNext"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
// Example setting for using emacs-style tab
|
// Example setting for using emacs-style tab
|
||||||
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
||||||
@@ -164,8 +164,8 @@
|
|||||||
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
||||||
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
||||||
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
||||||
"ctrl-x s": "workspace::SaveAll", // save-some-buffers
|
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Workaround to enable using native emacs from the Zed terminal.
|
// Workaround to enable using native emacs from the Zed terminal.
|
||||||
@@ -185,22 +185,22 @@
|
|||||||
"ctrl-x ctrl-f": null, // find-file
|
"ctrl-x ctrl-f": null, // find-file
|
||||||
"ctrl-x ctrl-s": null, // save-buffer
|
"ctrl-x ctrl-s": null, // save-buffer
|
||||||
"ctrl-x ctrl-w": null, // write-file
|
"ctrl-x ctrl-w": null, // write-file
|
||||||
"ctrl-x s": null, // save-some-buffers
|
"ctrl-x s": null // save-some-buffers
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar > Editor",
|
"context": "BufferSearchBar > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-s": "search::SelectNextMatch",
|
"ctrl-s": "search::SelectNextMatch",
|
||||||
"ctrl-r": "search::SelectPreviousMatch",
|
"ctrl-r": "search::SelectPreviousMatch",
|
||||||
"ctrl-g": "buffer_search::Dismiss",
|
"ctrl-g": "buffer_search::Dismiss"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-left": "pane::GoBack",
|
"ctrl-alt-left": "pane::GoBack",
|
||||||
"ctrl-alt-right": "pane::GoForward",
|
"ctrl-alt-right": "pane::GoForward"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
"shift-f8": "debugger::StepOut",
|
"shift-f8": "debugger::StepOut",
|
||||||
"f9": "debugger::Continue",
|
"f9": "debugger::Continue",
|
||||||
"shift-f9": "debugger::Start",
|
"shift-f9": "debugger::Start",
|
||||||
"alt-shift-f9": "debugger::Start",
|
"alt-shift-f9": "debugger::Start"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -62,30 +62,28 @@
|
|||||||
"ctrl-shift-end": "editor::SelectToEnd",
|
"ctrl-shift-end": "editor::SelectToEnd",
|
||||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||||
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||||
"ctrl-shift-u": "editor::ToggleCase",
|
"ctrl-shift-u": "editor::ToggleCase"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-f12": "outline::Toggle",
|
"ctrl-f12": "outline::Toggle",
|
||||||
"ctrl-r": ["buffer_search::Deploy", { "replace_enabled": true }],
|
"ctrl-r": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||||
"ctrl-e": "file_finder::Toggle",
|
|
||||||
"ctrl-shift-n": "file_finder::Toggle",
|
"ctrl-shift-n": "file_finder::Toggle",
|
||||||
"ctrl-alt-n": "file_finder::Toggle",
|
|
||||||
"ctrl-g": "go_to_line::Toggle",
|
"ctrl-g": "go_to_line::Toggle",
|
||||||
"alt-enter": "editor::ToggleCodeActions",
|
"alt-enter": "editor::ToggleCodeActions",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"ctrl-q": "editor::Hover",
|
"ctrl-q": "editor::Hover",
|
||||||
"ctrl-p": "editor::ShowSignatureHelp",
|
"ctrl-p": "editor::ShowSignatureHelp",
|
||||||
"ctrl-\\": "assistant::InlineAssist",
|
"ctrl-\\": "assistant::InlineAssist"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"shift-enter": "search::SelectPreviousMatch",
|
"shift-enter": "search::SelectPreviousMatch"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar || ProjectSearchBar",
|
"context": "BufferSearchBar || ProjectSearchBar",
|
||||||
@@ -93,8 +91,8 @@
|
|||||||
"alt-c": "search::ToggleCaseSensitive",
|
"alt-c": "search::ToggleCaseSensitive",
|
||||||
"alt-e": "search::ToggleSelection",
|
"alt-e": "search::ToggleSelection",
|
||||||
"alt-x": "search::ToggleRegex",
|
"alt-x": "search::ToggleRegex",
|
||||||
"alt-w": "search::ToggleWholeWord",
|
"alt-w": "search::ToggleWholeWord"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
@@ -107,8 +105,8 @@
|
|||||||
"ctrl-e": "file_finder::Toggle",
|
"ctrl-e": "file_finder::Toggle",
|
||||||
"ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
|
"ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
|
||||||
"ctrl-shift-n": "file_finder::Toggle",
|
"ctrl-shift-n": "file_finder::Toggle",
|
||||||
"ctrl-alt-n": "file_finder::Toggle",
|
|
||||||
"ctrl-n": "project_symbols::Toggle",
|
"ctrl-n": "project_symbols::Toggle",
|
||||||
|
"ctrl-alt-n": "file_finder::Toggle",
|
||||||
"ctrl-shift-a": "command_palette::Toggle",
|
"ctrl-shift-a": "command_palette::Toggle",
|
||||||
"shift shift": "command_palette::Toggle",
|
"shift shift": "command_palette::Toggle",
|
||||||
"ctrl-alt-shift-n": "project_symbols::Toggle",
|
"ctrl-alt-shift-n": "project_symbols::Toggle",
|
||||||
@@ -116,8 +114,8 @@
|
|||||||
"alt-1": "project_panel::ToggleFocus",
|
"alt-1": "project_panel::ToggleFocus",
|
||||||
"alt-5": "debug_panel::ToggleFocus",
|
"alt-5": "debug_panel::ToggleFocus",
|
||||||
"alt-6": "diagnostics::Deploy",
|
"alt-6": "diagnostics::Deploy",
|
||||||
"alt-7": "outline_panel::ToggleFocus",
|
"alt-7": "outline_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane", // this is to override the default Pane mappings to switch tabs
|
"context": "Pane", // this is to override the default Pane mappings to switch tabs
|
||||||
@@ -131,15 +129,15 @@
|
|||||||
"alt-7": "outline_panel::ToggleFocus",
|
"alt-7": "outline_panel::ToggleFocus",
|
||||||
"alt-8": null, // Services (bottom dock)
|
"alt-8": null, // Services (bottom dock)
|
||||||
"alt-9": null, // Git History (bottom dock)
|
"alt-9": null, // Git History (bottom dock)
|
||||||
"alt-0": "git_panel::ToggleFocus",
|
"alt-0": "git_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace || Editor",
|
"context": "Workspace || Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-f12": "terminal_panel::Toggle",
|
"alt-f12": "terminal_panel::Toggle",
|
||||||
"ctrl-shift-k": "git::Push",
|
"ctrl-shift-k": "git::Push"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
@@ -147,8 +145,8 @@
|
|||||||
"ctrl-alt-left": "pane::GoBack",
|
"ctrl-alt-left": "pane::GoBack",
|
||||||
"ctrl-alt-right": "pane::GoForward",
|
"ctrl-alt-right": "pane::GoForward",
|
||||||
"alt-left": "pane::ActivatePreviousItem",
|
"alt-left": "pane::ActivatePreviousItem",
|
||||||
"alt-right": "pane::ActivateNextItem",
|
"alt-right": "pane::ActivateNextItem"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
@@ -158,8 +156,8 @@
|
|||||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"shift-f6": "project_panel::Rename",
|
"shift-f6": "project_panel::Rename"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Terminal",
|
"context": "Terminal",
|
||||||
@@ -169,8 +167,8 @@
|
|||||||
"ctrl-up": "terminal::ScrollLineUp",
|
"ctrl-up": "terminal::ScrollLineUp",
|
||||||
"ctrl-down": "terminal::ScrollLineDown",
|
"ctrl-down": "terminal::ScrollLineDown",
|
||||||
"shift-pageup": "terminal::ScrollPageUp",
|
"shift-pageup": "terminal::ScrollPageUp",
|
||||||
"shift-pagedown": "terminal::ScrollPageDown",
|
"shift-pagedown": "terminal::ScrollPageDown"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{ "context": "GitPanel", "bindings": { "alt-0": "workspace::CloseActiveDock" } },
|
{ "context": "GitPanel", "bindings": { "alt-0": "workspace::CloseActiveDock" } },
|
||||||
{ "context": "ProjectPanel", "bindings": { "alt-1": "workspace::CloseActiveDock" } },
|
{ "context": "ProjectPanel", "bindings": { "alt-1": "workspace::CloseActiveDock" } },
|
||||||
@@ -181,7 +179,7 @@
|
|||||||
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
|
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "editor::ToggleFocus",
|
"escape": "editor::ToggleFocus",
|
||||||
"shift-escape": "workspace::CloseActiveDock",
|
"shift-escape": "workspace::CloseActiveDock"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||||
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||||
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }],
|
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -55,20 +55,20 @@
|
|||||||
"alt-right": "editor::MoveToNextSubwordEnd",
|
"alt-right": "editor::MoveToNextSubwordEnd",
|
||||||
"alt-left": "editor::MoveToPreviousSubwordStart",
|
"alt-left": "editor::MoveToPreviousSubwordStart",
|
||||||
"alt-shift-right": "editor::SelectToNextSubwordEnd",
|
"alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||||
"alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
"alt-shift-left": "editor::SelectToPreviousSubwordStart"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-r": "outline::Toggle",
|
"ctrl-r": "outline::Toggle"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && !agent_diff",
|
"context": "Editor && !agent_diff",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-k ctrl-z": "git::Restore",
|
"ctrl-k ctrl-z": "git::Restore"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
@@ -83,15 +83,15 @@
|
|||||||
"alt-6": ["pane::ActivateItem", 5],
|
"alt-6": ["pane::ActivateItem", 5],
|
||||||
"alt-7": ["pane::ActivateItem", 6],
|
"alt-7": ["pane::ActivateItem", 6],
|
||||||
"alt-8": ["pane::ActivateItem", 7],
|
"alt-8": ["pane::ActivateItem", 7],
|
||||||
"alt-9": "pane::ActivateLastItem",
|
"alt-9": "pane::ActivateLastItem"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-k ctrl-b": "workspace::ToggleLeftDock",
|
"ctrl-k ctrl-b": "workspace::ToggleLeftDock",
|
||||||
// "ctrl-0": "project_panel::ToggleFocus", // normally resets zoom
|
// "ctrl-0": "project_panel::ToggleFocus", // normally resets zoom
|
||||||
"shift-ctrl-r": "project_symbols::Toggle",
|
"shift-ctrl-r": "project_symbols::Toggle"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,16 +4,16 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-cmd-l": "workspace::Reload",
|
"ctrl-alt-cmd-l": "workspace::Reload",
|
||||||
"cmd-k cmd-p": "workspace::ActivatePreviousPane",
|
"cmd-k cmd-p": "workspace::ActivatePreviousPane",
|
||||||
"cmd-k cmd-n": "workspace::ActivateNextPane",
|
"cmd-k cmd-n": "workspace::ActivateNextPane"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
||||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
"cmd-k cmd-l": "editor::ConvertToLowerCase"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
"ctrl-cmd-down": "editor::MoveLineDown",
|
"ctrl-cmd-down": "editor::MoveLineDown",
|
||||||
"cmd-\\": "workspace::ToggleLeftDock",
|
"cmd-\\": "workspace::ToggleLeftDock",
|
||||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide",
|
"ctrl-shift-m": "markdown::OpenPreviewToTheSide",
|
||||||
"cmd-r": "outline::Toggle",
|
"cmd-r": "outline::Toggle"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
|
||||||
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
|
||||||
"cmd-f3": "search::SelectNextMatch",
|
"cmd-f3": "search::SelectNextMatch",
|
||||||
"cmd-shift-f3": "search::SelectPreviousMatch",
|
"cmd-shift-f3": "search::SelectPreviousMatch"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
"cmd-\\": "workspace::ToggleLeftDock",
|
"cmd-\\": "workspace::ToggleLeftDock",
|
||||||
"cmd-k cmd-b": "workspace::ToggleLeftDock",
|
"cmd-k cmd-b": "workspace::ToggleLeftDock",
|
||||||
"cmd-t": "file_finder::Toggle",
|
"cmd-t": "file_finder::Toggle",
|
||||||
"cmd-shift-r": "project_symbols::Toggle",
|
"cmd-shift-r": "project_symbols::Toggle"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
"cmd-6": ["pane::ActivateItem", 5],
|
"cmd-6": ["pane::ActivateItem", 5],
|
||||||
"cmd-7": ["pane::ActivateItem", 6],
|
"cmd-7": ["pane::ActivateItem", 6],
|
||||||
"cmd-8": ["pane::ActivateItem", 7],
|
"cmd-8": ["pane::ActivateItem", 7],
|
||||||
"cmd-9": "pane::ActivateLastItem",
|
"cmd-9": "pane::ActivateLastItem"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
@@ -77,8 +77,8 @@
|
|||||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||||
"cmd-x": "project_panel::Cut",
|
"cmd-x": "project_panel::Cut",
|
||||||
"cmd-c": "project_panel::Copy",
|
"cmd-c": "project_panel::Copy",
|
||||||
"cmd-v": "project_panel::Paste",
|
"cmd-v": "project_panel::Paste"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel && not_editing",
|
"context": "ProjectPanel && not_editing",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"d": "project_panel::Duplicate",
|
"d": "project_panel::Duplicate",
|
||||||
"home": "menu::SelectFirst",
|
"home": "menu::SelectFirst",
|
||||||
"end": "menu::SelectLast",
|
"end": "menu::SelectLast",
|
||||||
"shift-a": "project_panel::NewDirectory",
|
"shift-a": "project_panel::NewDirectory"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
"cmd-shift-i": "agent::ToggleFocus",
|
"cmd-shift-i": "agent::ToggleFocus",
|
||||||
"cmd-l": "agent::ToggleFocus",
|
"cmd-l": "agent::ToggleFocus",
|
||||||
"cmd-shift-l": "agent::ToggleFocus",
|
"cmd-shift-l": "agent::ToggleFocus",
|
||||||
"cmd-shift-j": "agent::OpenSettings",
|
"cmd-shift-j": "agent::OpenSettings"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
@@ -20,19 +20,19 @@
|
|||||||
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
|
||||||
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
|
||||||
"cmd-k": "assistant::InlineAssist",
|
"cmd-k": "assistant::InlineAssist",
|
||||||
"cmd-shift-k": "assistant::InsertIntoEditor",
|
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "InlineAssistant > Editor",
|
"context": "InlineAssistEditor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-backspace": "editor::Cancel",
|
"cmd-shift-backspace": "editor::Cancel",
|
||||||
"cmd-enter": "menu::Confirm",
|
"cmd-enter": "menu::Confirm"
|
||||||
// "alt-enter": // Quick Question
|
// "alt-enter": // Quick Question
|
||||||
// "cmd-shift-enter": // Full File Context
|
// "cmd-shift-enter": // Full File Context
|
||||||
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
|
// "cmd-shift-k": // Toggle input focus (editor <> inline assist)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
"context": "AgentPanel || ContextEditor || (MessageEditor > Editor)",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"cmd-shift-backspace": "editor::Cancel",
|
"cmd-shift-backspace": "editor::Cancel",
|
||||||
"cmd-r": "agent::NewThread",
|
"cmd-r": "agent::NewThread",
|
||||||
"cmd-shift-v": "editor::Paste",
|
"cmd-shift-v": "editor::Paste",
|
||||||
"cmd-shift-k": "assistant::InsertIntoEditor",
|
"cmd-shift-k": "assistant::InsertIntoEditor"
|
||||||
// "escape": "agent::ToggleFocus"
|
// "escape": "agent::ToggleFocus"
|
||||||
///// Enable when Zed supports multiple thread tabs
|
///// Enable when Zed supports multiple thread tabs
|
||||||
// "cmd-t": // new thread tab
|
// "cmd-t": // new thread tab
|
||||||
@@ -57,29 +57,28 @@
|
|||||||
///// Enable if Zed adds support for keyboard navigation of thread elements
|
///// Enable if Zed adds support for keyboard navigation of thread elements
|
||||||
// "tab": // cycle to next message
|
// "tab": // cycle to next message
|
||||||
// "shift-tab": // cycle to previous message
|
// "shift-tab": // cycle to previous message
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && editor_agent_diff",
|
"context": "Editor && editor_agent_diff",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-enter": "agent::KeepAll",
|
"cmd-enter": "agent::KeepAll",
|
||||||
"cmd-backspace": "agent::RejectAll",
|
"cmd-backspace": "agent::RejectAll"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full && edit_prediction",
|
"context": "Editor && mode == full && edit_prediction",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-right": "editor::AcceptNextWordEditPrediction",
|
"cmd-right": "editor::AcceptPartialEditPrediction"
|
||||||
"cmd-down": "editor::AcceptNextLineEditPrediction",
|
}
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Terminal",
|
"context": "Terminal",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-k": "assistant::InlineAssist",
|
"cmd-k": "assistant::InlineAssist"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
{
|
{
|
||||||
"context": "!GitPanel",
|
"context": "!GitPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-g": "menu::Cancel",
|
"ctrl-g": "menu::Cancel"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Workaround to avoid falling back to default bindings.
|
// Workaround to avoid falling back to default bindings.
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
// NOTE: must be declared before the `Editor` override.
|
// NOTE: must be declared before the `Editor` override.
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||||
"ctrl-r": "buffer_search::Deploy", // isearch-backward
|
"ctrl-r": "buffer_search::Deploy", // isearch-backward
|
||||||
"alt-^": "editor::JoinLines", // join-line
|
"alt-^": "editor::JoinLines", // join-line
|
||||||
"alt-q": "editor::Rewrap", // fill-paragraph
|
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && selection_mode", // region selection
|
"context": "Editor && selection_mode", // region selection
|
||||||
@@ -116,22 +116,22 @@
|
|||||||
"alt->": "editor::SelectToEnd",
|
"alt->": "editor::SelectToEnd",
|
||||||
"ctrl-home": "editor::SelectToBeginning",
|
"ctrl-home": "editor::SelectToBeginning",
|
||||||
"ctrl-end": "editor::SelectToEnd",
|
"ctrl-end": "editor::SelectToEnd",
|
||||||
"ctrl-g": "editor::Cancel",
|
"ctrl-g": "editor::Cancel"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::ContextMenuPrevious",
|
"ctrl-p": "editor::ContextMenuPrevious",
|
||||||
"ctrl-n": "editor::ContextMenuNext",
|
"ctrl-n": "editor::ContextMenuNext"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && showing_signature_help && !showing_completions",
|
"context": "Editor && showing_signature_help && !showing_completions",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||||
"ctrl-n": "editor::SignatureHelpNext",
|
"ctrl-n": "editor::SignatureHelpNext"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
// Example setting for using emacs-style tab
|
// Example setting for using emacs-style tab
|
||||||
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
// (i.e. indent the current line / selection or perform symbol completion depending on context)
|
||||||
@@ -161,8 +161,8 @@
|
|||||||
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
||||||
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
||||||
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
||||||
"ctrl-x s": "workspace::SaveAll", // save-some-buffers
|
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Workaround to enable using native emacs from the Zed terminal.
|
// Workaround to enable using native emacs from the Zed terminal.
|
||||||
@@ -182,22 +182,22 @@
|
|||||||
"ctrl-x ctrl-f": null, // find-file
|
"ctrl-x ctrl-f": null, // find-file
|
||||||
"ctrl-x ctrl-s": null, // save-buffer
|
"ctrl-x ctrl-s": null, // save-buffer
|
||||||
"ctrl-x ctrl-w": null, // write-file
|
"ctrl-x ctrl-w": null, // write-file
|
||||||
"ctrl-x s": null, // save-some-buffers
|
"ctrl-x s": null // save-some-buffers
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar > Editor",
|
"context": "BufferSearchBar > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-s": "search::SelectNextMatch",
|
"ctrl-s": "search::SelectNextMatch",
|
||||||
"ctrl-r": "search::SelectPreviousMatch",
|
"ctrl-r": "search::SelectPreviousMatch",
|
||||||
"ctrl-g": "buffer_search::Dismiss",
|
"ctrl-g": "buffer_search::Dismiss"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-alt-left": "pane::GoBack",
|
"ctrl-alt-left": "pane::GoBack",
|
||||||
"ctrl-alt-right": "pane::GoForward",
|
"ctrl-alt-right": "pane::GoForward"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
"shift-f8": "debugger::StepOut",
|
"shift-f8": "debugger::StepOut",
|
||||||
"f9": "debugger::Continue",
|
"f9": "debugger::Continue",
|
||||||
"shift-f9": "debugger::Start",
|
"shift-f9": "debugger::Start",
|
||||||
"alt-shift-f9": "debugger::Start",
|
"alt-shift-f9": "debugger::Start"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -60,30 +60,28 @@
|
|||||||
"cmd-shift-end": "editor::SelectToEnd",
|
"cmd-shift-end": "editor::SelectToEnd",
|
||||||
"ctrl-f8": "editor::ToggleBreakpoint",
|
"ctrl-f8": "editor::ToggleBreakpoint",
|
||||||
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
"ctrl-shift-f8": "editor::EditLogBreakpoint",
|
||||||
"cmd-shift-u": "editor::ToggleCase",
|
"cmd-shift-u": "editor::ToggleCase"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-f12": "outline::Toggle",
|
"cmd-f12": "outline::Toggle",
|
||||||
"cmd-r": ["buffer_search::Deploy", { "replace_enabled": true }],
|
"cmd-r": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||||
"cmd-l": "go_to_line::Toggle",
|
|
||||||
"cmd-e": "file_finder::Toggle",
|
|
||||||
"cmd-shift-o": "file_finder::Toggle",
|
"cmd-shift-o": "file_finder::Toggle",
|
||||||
"cmd-shift-n": "file_finder::Toggle",
|
"cmd-l": "go_to_line::Toggle",
|
||||||
"alt-enter": "editor::ToggleCodeActions",
|
"alt-enter": "editor::ToggleCodeActions",
|
||||||
"ctrl-space": "editor::ShowCompletions",
|
"ctrl-space": "editor::ShowCompletions",
|
||||||
"cmd-j": "editor::Hover",
|
"cmd-j": "editor::Hover",
|
||||||
"cmd-p": "editor::ShowSignatureHelp",
|
"cmd-p": "editor::ShowSignatureHelp",
|
||||||
"cmd-\\": "assistant::InlineAssist",
|
"cmd-\\": "assistant::InlineAssist"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"shift-enter": "search::SelectPreviousMatch",
|
"shift-enter": "search::SelectPreviousMatch"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar || ProjectSearchBar",
|
"context": "BufferSearchBar || ProjectSearchBar",
|
||||||
@@ -95,8 +93,8 @@
|
|||||||
"ctrl-alt-c": "search::ToggleCaseSensitive",
|
"ctrl-alt-c": "search::ToggleCaseSensitive",
|
||||||
"ctrl-alt-e": "search::ToggleSelection",
|
"ctrl-alt-e": "search::ToggleSelection",
|
||||||
"ctrl-alt-w": "search::ToggleWholeWord",
|
"ctrl-alt-w": "search::ToggleWholeWord",
|
||||||
"ctrl-alt-x": "search::ToggleRegex",
|
"ctrl-alt-x": "search::ToggleRegex"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
@@ -118,8 +116,8 @@
|
|||||||
"cmd-1": "project_panel::ToggleFocus",
|
"cmd-1": "project_panel::ToggleFocus",
|
||||||
"cmd-5": "debug_panel::ToggleFocus",
|
"cmd-5": "debug_panel::ToggleFocus",
|
||||||
"cmd-6": "diagnostics::Deploy",
|
"cmd-6": "diagnostics::Deploy",
|
||||||
"cmd-7": "outline_panel::ToggleFocus",
|
"cmd-7": "outline_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane", // this is to override the default Pane mappings to switch tabs
|
"context": "Pane", // this is to override the default Pane mappings to switch tabs
|
||||||
@@ -133,15 +131,15 @@
|
|||||||
"cmd-7": "outline_panel::ToggleFocus",
|
"cmd-7": "outline_panel::ToggleFocus",
|
||||||
"cmd-8": null, // Services (bottom dock)
|
"cmd-8": null, // Services (bottom dock)
|
||||||
"cmd-9": null, // Git History (bottom dock)
|
"cmd-9": null, // Git History (bottom dock)
|
||||||
"cmd-0": "git_panel::ToggleFocus",
|
"cmd-0": "git_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace || Editor",
|
"context": "Workspace || Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-f12": "terminal_panel::Toggle",
|
"alt-f12": "terminal_panel::Toggle",
|
||||||
"cmd-shift-k": "git::Push",
|
"cmd-shift-k": "git::Push"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
@@ -149,8 +147,8 @@
|
|||||||
"cmd-alt-left": "pane::GoBack",
|
"cmd-alt-left": "pane::GoBack",
|
||||||
"cmd-alt-right": "pane::GoForward",
|
"cmd-alt-right": "pane::GoForward",
|
||||||
"alt-left": "pane::ActivatePreviousItem",
|
"alt-left": "pane::ActivatePreviousItem",
|
||||||
"alt-right": "pane::ActivateNextItem",
|
"alt-right": "pane::ActivateNextItem"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
@@ -161,8 +159,8 @@
|
|||||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"shift-f6": "project_panel::Rename",
|
"shift-f6": "project_panel::Rename"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Terminal",
|
"context": "Terminal",
|
||||||
@@ -172,8 +170,8 @@
|
|||||||
"cmd-up": "terminal::ScrollLineUp",
|
"cmd-up": "terminal::ScrollLineUp",
|
||||||
"cmd-down": "terminal::ScrollLineDown",
|
"cmd-down": "terminal::ScrollLineDown",
|
||||||
"shift-pageup": "terminal::ScrollPageUp",
|
"shift-pageup": "terminal::ScrollPageUp",
|
||||||
"shift-pagedown": "terminal::ScrollPageDown",
|
"shift-pagedown": "terminal::ScrollPageDown"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{ "context": "GitPanel", "bindings": { "cmd-0": "workspace::CloseActiveDock" } },
|
{ "context": "GitPanel", "bindings": { "cmd-0": "workspace::CloseActiveDock" } },
|
||||||
{ "context": "ProjectPanel", "bindings": { "cmd-1": "workspace::CloseActiveDock" } },
|
{ "context": "ProjectPanel", "bindings": { "cmd-1": "workspace::CloseActiveDock" } },
|
||||||
@@ -184,7 +182,7 @@
|
|||||||
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
|
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "editor::ToggleFocus",
|
"escape": "editor::ToggleFocus",
|
||||||
"shift-escape": "workspace::CloseActiveDock",
|
"shift-escape": "workspace::CloseActiveDock"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||||
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||||
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }],
|
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -57,20 +57,20 @@
|
|||||||
"ctrl-right": "editor::MoveToNextSubwordEnd",
|
"ctrl-right": "editor::MoveToNextSubwordEnd",
|
||||||
"ctrl-left": "editor::MoveToPreviousSubwordStart",
|
"ctrl-left": "editor::MoveToPreviousSubwordStart",
|
||||||
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
|
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
|
||||||
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart",
|
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-r": "outline::Toggle",
|
"cmd-r": "outline::Toggle"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && !agent_diff",
|
"context": "Editor && !agent_diff",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-k cmd-z": "git::Restore",
|
"cmd-k cmd-z": "git::Restore"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
@@ -85,8 +85,8 @@
|
|||||||
"cmd-6": ["pane::ActivateItem", 5],
|
"cmd-6": ["pane::ActivateItem", 5],
|
||||||
"cmd-7": ["pane::ActivateItem", 6],
|
"cmd-7": ["pane::ActivateItem", 6],
|
||||||
"cmd-8": ["pane::ActivateItem", 7],
|
"cmd-8": ["pane::ActivateItem", 7],
|
||||||
"cmd-9": "pane::ActivateLastItem",
|
"cmd-9": "pane::ActivateLastItem"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"cmd-t": "file_finder::Toggle",
|
"cmd-t": "file_finder::Toggle",
|
||||||
"shift-cmd-r": "project_symbols::Toggle",
|
"shift-cmd-r": "project_symbols::Toggle",
|
||||||
// Currently busted: https://github.com/zed-industries/feedback/issues/898
|
// Currently busted: https://github.com/zed-industries/feedback/issues/898
|
||||||
"ctrl-0": "project_panel::ToggleFocus",
|
"ctrl-0": "project_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-o": "projects::OpenRecent",
|
"cmd-shift-o": "projects::OpenRecent",
|
||||||
"cmd-alt-tab": "project_panel::ToggleFocus",
|
"cmd-alt-tab": "project_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
"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"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -41,30 +41,30 @@
|
|||||||
"ctrl-u": "editor::ConvertToUpperCase",
|
"ctrl-u": "editor::ConvertToUpperCase",
|
||||||
"ctrl-shift-u": "editor::ConvertToLowerCase",
|
"ctrl-shift-u": "editor::ConvertToLowerCase",
|
||||||
"ctrl-alt-u": "editor::ConvertToUpperCamelCase",
|
"ctrl-alt-u": "editor::ConvertToUpperCamelCase",
|
||||||
"ctrl-_": "editor::ConvertToSnakeCase",
|
"ctrl-_": "editor::ConvertToSnakeCase"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar",
|
"context": "BufferSearchBar",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-s": "search::SelectNextMatch",
|
"ctrl-s": "search::SelectNextMatch",
|
||||||
"ctrl-shift-s": "search::SelectPreviousMatch",
|
"ctrl-shift-s": "search::SelectPreviousMatch"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-alt-ctrl-d": "workspace::ToggleLeftDock",
|
"cmd-alt-ctrl-d": "workspace::ToggleLeftDock",
|
||||||
"cmd-t": "file_finder::Toggle",
|
"cmd-t": "file_finder::Toggle",
|
||||||
"cmd-shift-t": "project_symbols::Toggle",
|
"cmd-shift-t": "project_symbols::Toggle"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Pane",
|
"context": "Pane",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-cmd-r": "search::ToggleRegex",
|
"alt-cmd-r": "search::ToggleRegex",
|
||||||
"ctrl-tab": "project_panel::ToggleFocus",
|
"ctrl-tab": "project_panel::ToggleFocus"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
@@ -75,11 +75,11 @@
|
|||||||
"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",
|
||||||
"cmd-alt-c": "project_panel::CopyPath",
|
"cmd-alt-c": "project_panel::CopyPath"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Dock",
|
"context": "Dock",
|
||||||
"bindings": {},
|
"bindings": {}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"backspace": "editor::Backspace",
|
"backspace": "editor::Backspace",
|
||||||
"delete": "editor::Delete",
|
"delete": "editor::Delete",
|
||||||
"left": "editor::MoveLeft",
|
"left": "editor::MoveLeft",
|
||||||
"right": "editor::MoveRight",
|
"right": "editor::MoveRight"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -181,8 +181,8 @@
|
|||||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||||
"ctrl-^": "pane::AlternateFile",
|
"ctrl-^": "pane::AlternateFile",
|
||||||
".": "vim::Repeat",
|
".": "vim::Repeat"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == normal || vim_mode == visual || vim_mode == operator",
|
"context": "vim_mode == normal || vim_mode == visual || vim_mode == operator",
|
||||||
@@ -223,8 +223,8 @@
|
|||||||
"] r": "vim::GoToNextReference",
|
"] r": "vim::GoToNextReference",
|
||||||
// tree-sitter related commands
|
// tree-sitter related commands
|
||||||
"[ x": "vim::SelectLargerSyntaxNode",
|
"[ x": "vim::SelectLargerSyntaxNode",
|
||||||
"] x": "vim::SelectSmallerSyntaxNode",
|
"] x": "vim::SelectSmallerSyntaxNode"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == normal",
|
"context": "vim_mode == normal",
|
||||||
@@ -261,16 +261,16 @@
|
|||||||
"[ d": "editor::GoToPreviousDiagnostic",
|
"[ d": "editor::GoToPreviousDiagnostic",
|
||||||
"] c": "editor::GoToHunk",
|
"] c": "editor::GoToHunk",
|
||||||
"[ c": "editor::GoToPreviousHunk",
|
"[ c": "editor::GoToPreviousHunk",
|
||||||
"g c": "vim::PushToggleComments",
|
"g c": "vim::PushToggleComments"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "VimControl && VimCount",
|
"context": "VimControl && VimCount",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"0": ["vim::Number", 0],
|
"0": ["vim::Number", 0],
|
||||||
":": "vim::CountCommand",
|
":": "vim::CountCommand",
|
||||||
"%": "vim::GoToPercentage",
|
"%": "vim::GoToPercentage"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == visual",
|
"context": "vim_mode == visual",
|
||||||
@@ -322,8 +322,8 @@
|
|||||||
"g w": "vim::Rewrap",
|
"g w": "vim::Rewrap",
|
||||||
"g ?": "vim::ConvertToRot13",
|
"g ?": "vim::ConvertToRot13",
|
||||||
// "g ?": "vim::ConvertToRot47",
|
// "g ?": "vim::ConvertToRot47",
|
||||||
"\"": "vim::PushRegister",
|
"\"": "vim::PushRegister"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == helix_select",
|
"context": "vim_mode == helix_select",
|
||||||
@@ -343,8 +343,8 @@
|
|||||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
"ctrl-pageup": "pane::ActivatePreviousItem",
|
||||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||||
".": "vim::Repeat",
|
".": "vim::Repeat",
|
||||||
"alt-.": "vim::RepeatFind",
|
"alt-.": "vim::RepeatFind"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == insert",
|
"context": "vim_mode == insert",
|
||||||
@@ -374,8 +374,8 @@
|
|||||||
"ctrl-r": "vim::PushRegister",
|
"ctrl-r": "vim::PushRegister",
|
||||||
"insert": "vim::ToggleReplace",
|
"insert": "vim::ToggleReplace",
|
||||||
"ctrl-o": "vim::TemporaryNormal",
|
"ctrl-o": "vim::TemporaryNormal",
|
||||||
"ctrl-s": "editor::ShowSignatureHelp",
|
"ctrl-s": "editor::ShowSignatureHelp"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "showing_completions",
|
"context": "showing_completions",
|
||||||
@@ -383,8 +383,8 @@
|
|||||||
"ctrl-d": "vim::ScrollDown",
|
"ctrl-d": "vim::ScrollDown",
|
||||||
"ctrl-u": "vim::ScrollUp",
|
"ctrl-u": "vim::ScrollUp",
|
||||||
"ctrl-e": "vim::LineDown",
|
"ctrl-e": "vim::LineDown",
|
||||||
"ctrl-y": "vim::LineUp",
|
"ctrl-y": "vim::LineUp"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "(vim_mode == normal || vim_mode == helix_normal) && !menu",
|
"context": "(vim_mode == normal || vim_mode == helix_normal) && !menu",
|
||||||
@@ -409,31 +409,23 @@
|
|||||||
"shift-s": "vim::SubstituteLine",
|
"shift-s": "vim::SubstituteLine",
|
||||||
"\"": "vim::PushRegister",
|
"\"": "vim::PushRegister",
|
||||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||||
"ctrl-pageup": "pane::ActivatePreviousItem",
|
"ctrl-pageup": "pane::ActivatePreviousItem"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "VimControl && vim_mode == helix_normal && !menu",
|
"context": "VimControl && vim_mode == helix_normal && !menu",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"j": ["vim::Down", { "display_lines": true }],
|
|
||||||
"down": ["vim::Down", { "display_lines": true }],
|
|
||||||
"k": ["vim::Up", { "display_lines": true }],
|
|
||||||
"up": ["vim::Up", { "display_lines": true }],
|
|
||||||
"g j": "vim::Down",
|
|
||||||
"g down": "vim::Down",
|
|
||||||
"g k": "vim::Up",
|
|
||||||
"g up": "vim::Up",
|
|
||||||
"escape": "vim::SwitchToHelixNormalMode",
|
"escape": "vim::SwitchToHelixNormalMode",
|
||||||
"i": "vim::HelixInsert",
|
"i": "vim::HelixInsert",
|
||||||
"a": "vim::HelixAppend",
|
"a": "vim::HelixAppend",
|
||||||
"ctrl-[": "editor::Cancel",
|
"ctrl-[": "editor::Cancel"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == helix_select && !menu",
|
"context": "vim_mode == helix_select && !menu",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "vim::SwitchToHelixNormalMode",
|
"escape": "vim::SwitchToHelixNormalMode"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
|
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
|
||||||
@@ -453,9 +445,9 @@
|
|||||||
"shift-r": "editor::Paste",
|
"shift-r": "editor::Paste",
|
||||||
"`": "vim::ConvertToLowerCase",
|
"`": "vim::ConvertToLowerCase",
|
||||||
"alt-`": "vim::ConvertToUpperCase",
|
"alt-`": "vim::ConvertToUpperCase",
|
||||||
"insert": "vim::InsertBefore", // not a helix default
|
"insert": "vim::InsertBefore",
|
||||||
"shift-u": "editor::Redo",
|
"shift-u": "editor::Redo",
|
||||||
"ctrl-r": "vim::Redo", // not a helix default
|
"ctrl-r": "vim::Redo",
|
||||||
"y": "vim::HelixYank",
|
"y": "vim::HelixYank",
|
||||||
"p": "vim::HelixPaste",
|
"p": "vim::HelixPaste",
|
||||||
"shift-p": ["vim::HelixPaste", { "before": true }],
|
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||||
@@ -484,7 +476,6 @@
|
|||||||
"alt-p": "editor::SelectPreviousSyntaxNode",
|
"alt-p": "editor::SelectPreviousSyntaxNode",
|
||||||
"alt-n": "editor::SelectNextSyntaxNode",
|
"alt-n": "editor::SelectNextSyntaxNode",
|
||||||
|
|
||||||
// Search
|
|
||||||
"n": "vim::HelixSelectNext",
|
"n": "vim::HelixSelectNext",
|
||||||
"shift-n": "vim::HelixSelectPrevious",
|
"shift-n": "vim::HelixSelectPrevious",
|
||||||
|
|
||||||
@@ -492,32 +483,27 @@
|
|||||||
"g e": "vim::EndOfDocument",
|
"g e": "vim::EndOfDocument",
|
||||||
"g h": "vim::StartOfLine",
|
"g h": "vim::StartOfLine",
|
||||||
"g l": "vim::EndOfLine",
|
"g l": "vim::EndOfLine",
|
||||||
"g s": "vim::FirstNonWhitespace",
|
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||||
"g t": "vim::WindowTop",
|
"g t": "vim::WindowTop",
|
||||||
"g c": "vim::WindowMiddle",
|
"g c": "vim::WindowMiddle",
|
||||||
"g b": "vim::WindowBottom",
|
"g b": "vim::WindowBottom",
|
||||||
"g r": "editor::FindAllReferences",
|
"g r": "editor::FindAllReferences", // zed specific
|
||||||
"g n": "pane::ActivateNextItem",
|
"g n": "pane::ActivateNextItem",
|
||||||
"shift-l": "pane::ActivateNextItem", // not a helix default
|
"shift-l": "pane::ActivateNextItem",
|
||||||
"g p": "pane::ActivatePreviousItem",
|
"g p": "pane::ActivatePreviousItem",
|
||||||
"shift-h": "pane::ActivatePreviousItem", // not a helix default
|
"shift-h": "pane::ActivatePreviousItem",
|
||||||
"g .": "vim::HelixGotoLastModification",
|
"g .": "vim::HelixGotoLastModification", // go to last modification
|
||||||
"g o": "editor::ToggleSelectedDiffHunks", // Zed specific
|
|
||||||
"g shift-o": "git::ToggleStaged", // Zed specific
|
|
||||||
"g shift-r": "git::Restore", // Zed specific
|
|
||||||
"g u": "git::StageAndNext", // Zed specific
|
|
||||||
"g shift-u": "git::UnstageAndNext", // Zed specific
|
|
||||||
|
|
||||||
// Window mode
|
// Window mode
|
||||||
"space w v": "pane::SplitDown",
|
|
||||||
"space w s": "pane::SplitRight",
|
|
||||||
"space w h": "workspace::ActivatePaneLeft",
|
"space w h": "workspace::ActivatePaneLeft",
|
||||||
"space w j": "workspace::ActivatePaneDown",
|
|
||||||
"space w k": "workspace::ActivatePaneUp",
|
|
||||||
"space w l": "workspace::ActivatePaneRight",
|
"space w l": "workspace::ActivatePaneRight",
|
||||||
|
"space w k": "workspace::ActivatePaneUp",
|
||||||
|
"space w j": "workspace::ActivatePaneDown",
|
||||||
"space w q": "pane::CloseActiveItem",
|
"space w q": "pane::CloseActiveItem",
|
||||||
"space w r": "pane::SplitRight", // not a helix default
|
"space w s": "pane::SplitRight",
|
||||||
"space w d": "pane::SplitDown", // not a helix default
|
"space w r": "pane::SplitRight",
|
||||||
|
"space w v": "pane::SplitDown",
|
||||||
|
"space w d": "pane::SplitDown",
|
||||||
|
|
||||||
// Space mode
|
// Space mode
|
||||||
"space f": "file_finder::Toggle",
|
"space f": "file_finder::Toggle",
|
||||||
@@ -531,7 +517,6 @@
|
|||||||
"space c": "editor::ToggleComments",
|
"space c": "editor::ToggleComments",
|
||||||
"space p": "editor::Paste",
|
"space p": "editor::Paste",
|
||||||
"space y": "editor::Copy",
|
"space y": "editor::Copy",
|
||||||
"space /": "pane::DeploySearch",
|
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
":": "command_palette::Toggle",
|
":": "command_palette::Toggle",
|
||||||
@@ -539,22 +524,24 @@
|
|||||||
"]": ["vim::PushHelixNext", { "around": true }],
|
"]": ["vim::PushHelixNext", { "around": true }],
|
||||||
"[": ["vim::PushHelixPrevious", { "around": true }],
|
"[": ["vim::PushHelixPrevious", { "around": true }],
|
||||||
"g q": "vim::PushRewrap",
|
"g q": "vim::PushRewrap",
|
||||||
"g w": "vim::PushRewrap", // not a helix default & clashes with helix `goto_word`
|
"g w": "vim::PushRewrap"
|
||||||
},
|
// "tab": "pane::ActivateNextItem",
|
||||||
|
// "shift-tab": "pane::ActivatePrevItem",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::ShowWordCompletions",
|
"ctrl-p": "editor::ShowWordCompletions",
|
||||||
"ctrl-n": "editor::ShowWordCompletions",
|
"ctrl-n": "editor::ShowWordCompletions"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "(vim_mode == insert || vim_mode == normal) && showing_signature_help && !showing_completions",
|
"context": "(vim_mode == insert || vim_mode == normal) && showing_signature_help && !showing_completions",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-p": "editor::SignatureHelpPrevious",
|
"ctrl-p": "editor::SignatureHelpPrevious",
|
||||||
"ctrl-n": "editor::SignatureHelpNext",
|
"ctrl-n": "editor::SignatureHelpNext"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == replace",
|
"context": "vim_mode == replace",
|
||||||
@@ -570,8 +557,8 @@
|
|||||||
"backspace": "vim::UndoReplace",
|
"backspace": "vim::UndoReplace",
|
||||||
"tab": "vim::Tab",
|
"tab": "vim::Tab",
|
||||||
"enter": "vim::Enter",
|
"enter": "vim::Enter",
|
||||||
"insert": "vim::InsertBefore",
|
"insert": "vim::InsertBefore"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == waiting",
|
"context": "vim_mode == waiting",
|
||||||
@@ -583,14 +570,14 @@
|
|||||||
"escape": "vim::ClearOperators",
|
"escape": "vim::ClearOperators",
|
||||||
"ctrl-k": ["vim::PushDigraph", {}],
|
"ctrl-k": ["vim::PushDigraph", {}],
|
||||||
"ctrl-v": ["vim::PushLiteral", {}],
|
"ctrl-v": ["vim::PushLiteral", {}],
|
||||||
"ctrl-q": ["vim::PushLiteral", {}],
|
"ctrl-q": ["vim::PushLiteral", {}]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == waiting && (vim_operator == ys || vim_operator == cs)",
|
"context": "Editor && vim_mode == waiting && (vim_operator == ys || vim_operator == cs)",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "vim::SwitchToNormalMode",
|
"escape": "vim::SwitchToNormalMode"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == operator",
|
"context": "vim_mode == operator",
|
||||||
@@ -598,8 +585,8 @@
|
|||||||
"ctrl-c": "vim::ClearOperators",
|
"ctrl-c": "vim::ClearOperators",
|
||||||
"ctrl-[": "vim::ClearOperators",
|
"ctrl-[": "vim::ClearOperators",
|
||||||
"escape": "vim::ClearOperators",
|
"escape": "vim::ClearOperators",
|
||||||
"g c": "vim::Comment",
|
"g c": "vim::Comment"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs || vim_operator == helix_next || vim_operator == helix_previous",
|
"context": "vim_operator == a || vim_operator == i || vim_operator == cs || vim_operator == helix_next || vim_operator == helix_previous",
|
||||||
@@ -636,14 +623,14 @@
|
|||||||
"shift-i": ["vim::IndentObj", { "include_below": true }],
|
"shift-i": ["vim::IndentObj", { "include_below": true }],
|
||||||
"f": "vim::Method",
|
"f": "vim::Method",
|
||||||
"c": "vim::Class",
|
"c": "vim::Class",
|
||||||
"e": "vim::EntireFile",
|
"e": "vim::EntireFile"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == helix_m",
|
"context": "vim_operator == helix_m",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"m": "vim::Matching",
|
"m": "vim::Matching"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == helix_next",
|
"context": "vim_operator == helix_next",
|
||||||
@@ -660,8 +647,8 @@
|
|||||||
"x": "editor::SelectSmallerSyntaxNode",
|
"x": "editor::SelectSmallerSyntaxNode",
|
||||||
"d": "editor::GoToDiagnostic",
|
"d": "editor::GoToDiagnostic",
|
||||||
"c": "editor::GoToHunk",
|
"c": "editor::GoToHunk",
|
||||||
"space": "vim::InsertEmptyLineBelow",
|
"space": "vim::InsertEmptyLineBelow"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == helix_previous",
|
"context": "vim_operator == helix_previous",
|
||||||
@@ -678,8 +665,8 @@
|
|||||||
"x": "editor::SelectLargerSyntaxNode",
|
"x": "editor::SelectLargerSyntaxNode",
|
||||||
"d": "editor::GoToPreviousDiagnostic",
|
"d": "editor::GoToPreviousDiagnostic",
|
||||||
"c": "editor::GoToPreviousHunk",
|
"c": "editor::GoToPreviousHunk",
|
||||||
"space": "vim::InsertEmptyLineAbove",
|
"space": "vim::InsertEmptyLineAbove"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == c",
|
"context": "vim_operator == c",
|
||||||
@@ -687,8 +674,8 @@
|
|||||||
"c": "vim::CurrentLine",
|
"c": "vim::CurrentLine",
|
||||||
"x": "vim::Exchange",
|
"x": "vim::Exchange",
|
||||||
"d": "editor::Rename", // zed specific
|
"d": "editor::Rename", // zed specific
|
||||||
"s": ["vim::PushChangeSurrounds", {}],
|
"s": ["vim::PushChangeSurrounds", {}]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == d",
|
"context": "vim_operator == d",
|
||||||
@@ -700,36 +687,36 @@
|
|||||||
"shift-o": "git::ToggleStaged",
|
"shift-o": "git::ToggleStaged",
|
||||||
"p": "git::Restore", // "d p"
|
"p": "git::Restore", // "d p"
|
||||||
"u": "git::StageAndNext", // "d u"
|
"u": "git::StageAndNext", // "d u"
|
||||||
"shift-u": "git::UnstageAndNext", // "d shift-u"
|
"shift-u": "git::UnstageAndNext" // "d shift-u"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gu",
|
"context": "vim_operator == gu",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g u": "vim::CurrentLine",
|
"g u": "vim::CurrentLine",
|
||||||
"u": "vim::CurrentLine",
|
"u": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gU",
|
"context": "vim_operator == gU",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g shift-u": "vim::CurrentLine",
|
"g shift-u": "vim::CurrentLine",
|
||||||
"shift-u": "vim::CurrentLine",
|
"shift-u": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == g~",
|
"context": "vim_operator == g~",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g ~": "vim::CurrentLine",
|
"g ~": "vim::CurrentLine",
|
||||||
"~": "vim::CurrentLine",
|
"~": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == g?",
|
"context": "vim_operator == g?",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g ?": "vim::CurrentLine",
|
"g ?": "vim::CurrentLine",
|
||||||
"?": "vim::CurrentLine",
|
"?": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gq",
|
"context": "vim_operator == gq",
|
||||||
@@ -737,66 +724,66 @@
|
|||||||
"g q": "vim::CurrentLine",
|
"g q": "vim::CurrentLine",
|
||||||
"q": "vim::CurrentLine",
|
"q": "vim::CurrentLine",
|
||||||
"g w": "vim::CurrentLine",
|
"g w": "vim::CurrentLine",
|
||||||
"w": "vim::CurrentLine",
|
"w": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == y",
|
"context": "vim_operator == y",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"y": "vim::CurrentLine",
|
"y": "vim::CurrentLine",
|
||||||
"v": "vim::PushForcedMotion",
|
"v": "vim::PushForcedMotion",
|
||||||
"s": ["vim::PushAddSurrounds", {}],
|
"s": ["vim::PushAddSurrounds", {}]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == ys",
|
"context": "vim_operator == ys",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"s": "vim::CurrentLine",
|
"s": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == >",
|
"context": "vim_operator == >",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
">": "vim::CurrentLine",
|
">": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == <",
|
"context": "vim_operator == <",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"<": "vim::CurrentLine",
|
"<": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == eq",
|
"context": "vim_operator == eq",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"=": "vim::CurrentLine",
|
"=": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == sh",
|
"context": "vim_operator == sh",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"!": "vim::CurrentLine",
|
"!": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gc",
|
"context": "vim_operator == gc",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"c": "vim::CurrentLine",
|
"c": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == gR",
|
"context": "vim_operator == gR",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"r": "vim::CurrentLine",
|
"r": "vim::CurrentLine",
|
||||||
"shift-r": "vim::CurrentLine",
|
"shift-r": "vim::CurrentLine"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_operator == cx",
|
"context": "vim_operator == cx",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"x": "vim::CurrentLine",
|
"x": "vim::CurrentLine",
|
||||||
"c": "vim::ClearExchange",
|
"c": "vim::ClearExchange"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "vim_mode == literal",
|
"context": "vim_mode == literal",
|
||||||
@@ -838,15 +825,15 @@
|
|||||||
"tab": ["vim::Literal", ["tab", "\u0009"]],
|
"tab": ["vim::Literal", ["tab", "\u0009"]],
|
||||||
// zed extensions:
|
// zed extensions:
|
||||||
"backspace": ["vim::Literal", ["backspace", "\u0008"]],
|
"backspace": ["vim::Literal", ["backspace", "\u0008"]],
|
||||||
"delete": ["vim::Literal", ["delete", "\u007F"]],
|
"delete": ["vim::Literal", ["delete", "\u007F"]]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && !in_replace",
|
"context": "BufferSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "vim::SearchSubmit",
|
"enter": "vim::SearchSubmit",
|
||||||
"escape": "buffer_search::Dismiss",
|
"escape": "buffer_search::Dismiss"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "VimControl && !menu || !Editor && !Terminal",
|
"context": "VimControl && !menu || !Editor && !Terminal",
|
||||||
@@ -907,8 +894,8 @@
|
|||||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||||
"g t": "vim::GoToTab",
|
"g t": "vim::GoToTab",
|
||||||
"g shift-t": "vim::GoToPreviousTab",
|
"g shift-t": "vim::GoToPreviousTab"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "!Editor && !Terminal",
|
"context": "!Editor && !Terminal",
|
||||||
@@ -918,8 +905,8 @@
|
|||||||
"] b": "pane::ActivateNextItem",
|
"] b": "pane::ActivateNextItem",
|
||||||
"[ b": "pane::ActivatePreviousItem",
|
"[ b": "pane::ActivatePreviousItem",
|
||||||
"] shift-b": "pane::ActivateLastItem",
|
"] shift-b": "pane::ActivateLastItem",
|
||||||
"[ shift-b": ["pane::ActivateItem", 0],
|
"[ shift-b": ["pane::ActivateItem", 0]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// netrw compatibility
|
// netrw compatibility
|
||||||
@@ -969,45 +956,17 @@
|
|||||||
"6": ["vim::Number", 6],
|
"6": ["vim::Number", 6],
|
||||||
"7": ["vim::Number", 7],
|
"7": ["vim::Number", 7],
|
||||||
"8": ["vim::Number", 8],
|
"8": ["vim::Number", 8],
|
||||||
"9": ["vim::Number", 9],
|
"9": ["vim::Number", 9]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "OutlinePanel && not_editing",
|
"context": "OutlinePanel && not_editing",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"h": "outline_panel::CollapseSelectedEntry",
|
"j": "menu::SelectNext",
|
||||||
"j": "vim::MenuSelectNext",
|
"k": "menu::SelectPrevious",
|
||||||
"k": "vim::MenuSelectPrevious",
|
|
||||||
"down": "vim::MenuSelectNext",
|
|
||||||
"up": "vim::MenuSelectPrevious",
|
|
||||||
"l": "outline_panel::ExpandSelectedEntry",
|
|
||||||
"shift-g": "menu::SelectLast",
|
"shift-g": "menu::SelectLast",
|
||||||
"g g": "menu::SelectFirst",
|
"g g": "menu::SelectFirst"
|
||||||
"-": "outline_panel::SelectParent",
|
}
|
||||||
"enter": "editor::ToggleFocus",
|
|
||||||
"/": "menu::Cancel",
|
|
||||||
"ctrl-u": "outline_panel::ScrollUp",
|
|
||||||
"ctrl-d": "outline_panel::ScrollDown",
|
|
||||||
"z t": "outline_panel::ScrollCursorTop",
|
|
||||||
"z z": "outline_panel::ScrollCursorCenter",
|
|
||||||
"z b": "outline_panel::ScrollCursorBottom",
|
|
||||||
"0": ["vim::Number", 0],
|
|
||||||
"1": ["vim::Number", 1],
|
|
||||||
"2": ["vim::Number", 2],
|
|
||||||
"3": ["vim::Number", 3],
|
|
||||||
"4": ["vim::Number", 4],
|
|
||||||
"5": ["vim::Number", 5],
|
|
||||||
"6": ["vim::Number", 6],
|
|
||||||
"7": ["vim::Number", 7],
|
|
||||||
"8": ["vim::Number", 8],
|
|
||||||
"9": ["vim::Number", 9],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "OutlinePanel && editing",
|
|
||||||
"bindings": {
|
|
||||||
"enter": "menu::Cancel",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitPanel && ChangesList",
|
"context": "GitPanel && ChangesList",
|
||||||
@@ -1022,8 +981,8 @@
|
|||||||
"x": "git::ToggleStaged",
|
"x": "git::ToggleStaged",
|
||||||
"shift-x": "git::StageAll",
|
"shift-x": "git::StageAll",
|
||||||
"g x": "git::StageRange",
|
"g x": "git::StageRange",
|
||||||
"shift-u": "git::UnstageAll",
|
"shift-u": "git::UnstageAll"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == auto_height && VimControl",
|
"context": "Editor && mode == auto_height && VimControl",
|
||||||
@@ -1034,8 +993,8 @@
|
|||||||
"#": null,
|
"#": null,
|
||||||
"*": null,
|
"*": null,
|
||||||
"n": null,
|
"n": null,
|
||||||
"shift-n": null,
|
"shift-n": null
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Picker > Editor",
|
"context": "Picker > Editor",
|
||||||
@@ -1044,29 +1003,29 @@
|
|||||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||||
"ctrl-p": "menu::SelectPrevious",
|
"ctrl-p": "menu::SelectPrevious",
|
||||||
"ctrl-n": "menu::SelectNext",
|
"ctrl-n": "menu::SelectNext"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
|
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-c": "menu::Cancel",
|
"ctrl-c": "menu::Cancel",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && edit_prediction",
|
"context": "Editor && edit_prediction",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
// This is identical to the binding in the base keymap, but the vim bindings above to
|
// This is identical to the binding in the base keymap, but the vim bindings above to
|
||||||
// "vim::Tab" shadow it, so it needs to be bound again.
|
// "vim::Tab" shadow it, so it needs to be bound again.
|
||||||
"tab": "editor::AcceptEditPrediction",
|
"tab": "editor::AcceptEditPrediction"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "MessageEditor > Editor && VimControl",
|
"context": "MessageEditor > Editor && VimControl",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "agent::Chat",
|
"enter": "agent::Chat"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "os != macos && Editor && edit_prediction_conflict",
|
"context": "os != macos && Editor && edit_prediction_conflict",
|
||||||
@@ -1074,8 +1033,8 @@
|
|||||||
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This
|
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This
|
||||||
// is because alt-tab may not be available, as it is often used for window switching on Linux
|
// is because alt-tab may not be available, as it is often used for window switching on Linux
|
||||||
// and Windows.
|
// and Windows.
|
||||||
"alt-l": "editor::AcceptEditPrediction",
|
"alt-l": "editor::AcceptEditPrediction"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "SettingsWindow > NavigationMenu && !search",
|
"context": "SettingsWindow > NavigationMenu && !search",
|
||||||
@@ -1085,16 +1044,7 @@
|
|||||||
"k": "settings_editor::FocusPreviousNavEntry",
|
"k": "settings_editor::FocusPreviousNavEntry",
|
||||||
"j": "settings_editor::FocusNextNavEntry",
|
"j": "settings_editor::FocusNextNavEntry",
|
||||||
"g g": "settings_editor::FocusFirstNavEntry",
|
"g g": "settings_editor::FocusFirstNavEntry",
|
||||||
"shift-g": "settings_editor::FocusLastNavEntry",
|
"shift-g": "settings_editor::FocusLastNavEntry"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"context": "MarkdownPreview",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-u": "markdown::ScrollPageUp",
|
|
||||||
"ctrl-d": "markdown::ScrollPageDown",
|
|
||||||
"ctrl-y": "markdown::ScrollUp",
|
|
||||||
"ctrl-e": "markdown::ScrollDown",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ The section you'll need to rewrite is marked with <rewrite_this></rewrite_this>
|
|||||||
The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.
|
The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if rewrite_section}}
|
||||||
And here's the section to rewrite based on that prompt again for reference:
|
And here's the section to rewrite based on that prompt again for reference:
|
||||||
|
|
||||||
<rewrite_this>
|
<rewrite_this>
|
||||||
@@ -32,9 +33,12 @@ Below are the diagnostic errors visible to the user. If the user requests probl
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
||||||
|
|
||||||
Start at the indentation level in the original file in the rewritten {{content_type}}.
|
Start at the indentation level in the original file in the rewritten {{content_type}}.
|
||||||
|
|
||||||
IMPORTANT: You MUST use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled. You MUST NOT send back unstructured text. If you need to make a statement or ask a question you MUST use one of the tools to do so.
|
You must use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled. It is an error if
|
||||||
|
you simply send back unstructured text. If you need to make a statement or ask a question you must use one of the tools to do so.
|
||||||
It is an error if you try to make a change that cannot be made simply by editing the rewrite_section.
|
It is an error if you try to make a change that cannot be made simply by editing the rewrite_section.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
|||||||
"adapter": "Debugpy",
|
"adapter": "Debugpy",
|
||||||
"program": "$ZED_FILE",
|
"program": "$ZED_FILE",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Debug active JavaScript file",
|
"label": "Debug active JavaScript file",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"program": "$ZED_FILE",
|
"program": "$ZED_FILE",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
"type": "pwa-node",
|
"type": "pwa-node"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "JavaScript debug terminal",
|
"label": "JavaScript debug terminal",
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "$ZED_WORKTREE_ROOT",
|
"cwd": "$ZED_WORKTREE_ROOT",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"type": "pwa-node",
|
"type": "pwa-node"
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
// For a full list of overridable settings, and general information on settings,
|
// For a full list of overridable settings, and general information on settings,
|
||||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||||
{
|
{
|
||||||
"lsp": {},
|
"lsp": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@
|
|||||||
// Whether to show the task line in the output of the spawned task, defaults to `true`.
|
// Whether to show the task line in the output of the spawned task, defaults to `true`.
|
||||||
"show_summary": true,
|
"show_summary": true,
|
||||||
// Whether to show the command line in the output of the spawned task, defaults to `true`.
|
// Whether to show the command line in the output of the spawned task, defaults to `true`.
|
||||||
"show_command": true,
|
"show_command": true
|
||||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||||
// "tags": []
|
// "tags": []
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
"theme": {
|
"theme": {
|
||||||
"mode": "system",
|
"mode": "system",
|
||||||
"light": "One Light",
|
"light": "One Light",
|
||||||
"dark": "One Dark",
|
"dark": "One Dark"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,33 +71,33 @@
|
|||||||
"editor.document_highlight.read_background": "#83a5981a",
|
"editor.document_highlight.read_background": "#83a5981a",
|
||||||
"editor.document_highlight.write_background": "#92847466",
|
"editor.document_highlight.write_background": "#92847466",
|
||||||
"terminal.background": "#282828ff",
|
"terminal.background": "#282828ff",
|
||||||
"terminal.foreground": "#ebdbb2ff",
|
"terminal.foreground": "#fbf1c7ff",
|
||||||
"terminal.bright_foreground": "#fbf1c7ff",
|
"terminal.bright_foreground": "#fbf1c7ff",
|
||||||
"terminal.dim_foreground": "#766b5dff",
|
"terminal.dim_foreground": "#282828ff",
|
||||||
"terminal.ansi.black": "#282828ff",
|
"terminal.ansi.black": "#282828ff",
|
||||||
"terminal.ansi.bright_black": "#928374ff",
|
"terminal.ansi.bright_black": "#73675eff",
|
||||||
"terminal.ansi.dim_black": "#fbf1c7ff",
|
"terminal.ansi.dim_black": "#fbf1c7ff",
|
||||||
"terminal.ansi.red": "#cc241dff",
|
"terminal.ansi.red": "#fb4a35ff",
|
||||||
"terminal.ansi.bright_red": "#fb4934ff",
|
"terminal.ansi.bright_red": "#93201dff",
|
||||||
"terminal.ansi.dim_red": "#8e1814ff",
|
"terminal.ansi.dim_red": "#ffaa95ff",
|
||||||
"terminal.ansi.green": "#98971aff",
|
"terminal.ansi.green": "#b7bb26ff",
|
||||||
"terminal.ansi.bright_green": "#b8bb26ff",
|
"terminal.ansi.bright_green": "#605c1bff",
|
||||||
"terminal.ansi.dim_green": "#6a6912ff",
|
"terminal.ansi.dim_green": "#e0dc98ff",
|
||||||
"terminal.ansi.yellow": "#d79921ff",
|
"terminal.ansi.yellow": "#f9bd2fff",
|
||||||
"terminal.ansi.bright_yellow": "#fabd2fff",
|
"terminal.ansi.bright_yellow": "#91611bff",
|
||||||
"terminal.ansi.dim_yellow": "#966a17ff",
|
"terminal.ansi.dim_yellow": "#fedc9bff",
|
||||||
"terminal.ansi.blue": "#458588ff",
|
"terminal.ansi.blue": "#83a598ff",
|
||||||
"terminal.ansi.bright_blue": "#83a598ff",
|
"terminal.ansi.bright_blue": "#414f4aff",
|
||||||
"terminal.ansi.dim_blue": "#305d5fff",
|
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||||
"terminal.ansi.magenta": "#b16286ff",
|
"terminal.ansi.magenta": "#d3869bff",
|
||||||
"terminal.ansi.bright_magenta": "#d3869bff",
|
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||||
"terminal.ansi.dim_magenta": "#7c455eff",
|
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||||
"terminal.ansi.cyan": "#689d6aff",
|
"terminal.ansi.cyan": "#8ec07cff",
|
||||||
"terminal.ansi.bright_cyan": "#8ec07cff",
|
"terminal.ansi.bright_cyan": "#45603eff",
|
||||||
"terminal.ansi.dim_cyan": "#496e4aff",
|
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||||
"terminal.ansi.white": "#a89984ff",
|
"terminal.ansi.white": "#fbf1c7ff",
|
||||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#766b5dff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#83a598ff",
|
"link_text.hover": "#83a598ff",
|
||||||
"version_control.added": "#b7bb26ff",
|
"version_control.added": "#b7bb26ff",
|
||||||
"version_control.modified": "#f9bd2fff",
|
"version_control.modified": "#f9bd2fff",
|
||||||
@@ -478,33 +478,33 @@
|
|||||||
"editor.document_highlight.read_background": "#83a5981a",
|
"editor.document_highlight.read_background": "#83a5981a",
|
||||||
"editor.document_highlight.write_background": "#92847466",
|
"editor.document_highlight.write_background": "#92847466",
|
||||||
"terminal.background": "#1d2021ff",
|
"terminal.background": "#1d2021ff",
|
||||||
"terminal.foreground": "#ebdbb2ff",
|
"terminal.foreground": "#fbf1c7ff",
|
||||||
"terminal.bright_foreground": "#fbf1c7ff",
|
"terminal.bright_foreground": "#fbf1c7ff",
|
||||||
"terminal.dim_foreground": "#766b5dff",
|
"terminal.dim_foreground": "#1d2021ff",
|
||||||
"terminal.ansi.black": "#282828ff",
|
"terminal.ansi.black": "#1d2021ff",
|
||||||
"terminal.ansi.bright_black": "#928374ff",
|
"terminal.ansi.bright_black": "#73675eff",
|
||||||
"terminal.ansi.dim_black": "#fbf1c7ff",
|
"terminal.ansi.dim_black": "#fbf1c7ff",
|
||||||
"terminal.ansi.red": "#cc241dff",
|
"terminal.ansi.red": "#fb4a35ff",
|
||||||
"terminal.ansi.bright_red": "#fb4934ff",
|
"terminal.ansi.bright_red": "#93201dff",
|
||||||
"terminal.ansi.dim_red": "#8e1814ff",
|
"terminal.ansi.dim_red": "#ffaa95ff",
|
||||||
"terminal.ansi.green": "#98971aff",
|
"terminal.ansi.green": "#b7bb26ff",
|
||||||
"terminal.ansi.bright_green": "#b8bb26ff",
|
"terminal.ansi.bright_green": "#605c1bff",
|
||||||
"terminal.ansi.dim_green": "#6a6912ff",
|
"terminal.ansi.dim_green": "#e0dc98ff",
|
||||||
"terminal.ansi.yellow": "#d79921ff",
|
"terminal.ansi.yellow": "#f9bd2fff",
|
||||||
"terminal.ansi.bright_yellow": "#fabd2fff",
|
"terminal.ansi.bright_yellow": "#91611bff",
|
||||||
"terminal.ansi.dim_yellow": "#966a17ff",
|
"terminal.ansi.dim_yellow": "#fedc9bff",
|
||||||
"terminal.ansi.blue": "#458588ff",
|
"terminal.ansi.blue": "#83a598ff",
|
||||||
"terminal.ansi.bright_blue": "#83a598ff",
|
"terminal.ansi.bright_blue": "#414f4aff",
|
||||||
"terminal.ansi.dim_blue": "#305d5fff",
|
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||||
"terminal.ansi.magenta": "#b16286ff",
|
"terminal.ansi.magenta": "#d3869bff",
|
||||||
"terminal.ansi.bright_magenta": "#d3869bff",
|
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||||
"terminal.ansi.dim_magenta": "#7c455eff",
|
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||||
"terminal.ansi.cyan": "#689d6aff",
|
"terminal.ansi.cyan": "#8ec07cff",
|
||||||
"terminal.ansi.bright_cyan": "#8ec07cff",
|
"terminal.ansi.bright_cyan": "#45603eff",
|
||||||
"terminal.ansi.dim_cyan": "#496e4aff",
|
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||||
"terminal.ansi.white": "#a89984ff",
|
"terminal.ansi.white": "#fbf1c7ff",
|
||||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#766b5dff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#83a598ff",
|
"link_text.hover": "#83a598ff",
|
||||||
"version_control.added": "#b7bb26ff",
|
"version_control.added": "#b7bb26ff",
|
||||||
"version_control.modified": "#f9bd2fff",
|
"version_control.modified": "#f9bd2fff",
|
||||||
@@ -885,33 +885,33 @@
|
|||||||
"editor.document_highlight.read_background": "#83a5981a",
|
"editor.document_highlight.read_background": "#83a5981a",
|
||||||
"editor.document_highlight.write_background": "#92847466",
|
"editor.document_highlight.write_background": "#92847466",
|
||||||
"terminal.background": "#32302fff",
|
"terminal.background": "#32302fff",
|
||||||
"terminal.foreground": "#ebdbb2ff",
|
"terminal.foreground": "#fbf1c7ff",
|
||||||
"terminal.bright_foreground": "#fbf1c7ff",
|
"terminal.bright_foreground": "#fbf1c7ff",
|
||||||
"terminal.dim_foreground": "#766b5dff",
|
"terminal.dim_foreground": "#32302fff",
|
||||||
"terminal.ansi.black": "#282828ff",
|
"terminal.ansi.black": "#32302fff",
|
||||||
"terminal.ansi.bright_black": "#928374ff",
|
"terminal.ansi.bright_black": "#73675eff",
|
||||||
"terminal.ansi.dim_black": "#fbf1c7ff",
|
"terminal.ansi.dim_black": "#fbf1c7ff",
|
||||||
"terminal.ansi.red": "#cc241dff",
|
"terminal.ansi.red": "#fb4a35ff",
|
||||||
"terminal.ansi.bright_red": "#fb4934ff",
|
"terminal.ansi.bright_red": "#93201dff",
|
||||||
"terminal.ansi.dim_red": "#8e1814ff",
|
"terminal.ansi.dim_red": "#ffaa95ff",
|
||||||
"terminal.ansi.green": "#98971aff",
|
"terminal.ansi.green": "#b7bb26ff",
|
||||||
"terminal.ansi.bright_green": "#b8bb26ff",
|
"terminal.ansi.bright_green": "#605c1bff",
|
||||||
"terminal.ansi.dim_green": "#6a6912ff",
|
"terminal.ansi.dim_green": "#e0dc98ff",
|
||||||
"terminal.ansi.yellow": "#d79921ff",
|
"terminal.ansi.yellow": "#f9bd2fff",
|
||||||
"terminal.ansi.bright_yellow": "#fabd2fff",
|
"terminal.ansi.bright_yellow": "#91611bff",
|
||||||
"terminal.ansi.dim_yellow": "#966a17ff",
|
"terminal.ansi.dim_yellow": "#fedc9bff",
|
||||||
"terminal.ansi.blue": "#458588ff",
|
"terminal.ansi.blue": "#83a598ff",
|
||||||
"terminal.ansi.bright_blue": "#83a598ff",
|
"terminal.ansi.bright_blue": "#414f4aff",
|
||||||
"terminal.ansi.dim_blue": "#305d5fff",
|
"terminal.ansi.dim_blue": "#c0d2cbff",
|
||||||
"terminal.ansi.magenta": "#b16286ff",
|
"terminal.ansi.magenta": "#d3869bff",
|
||||||
"terminal.ansi.bright_magenta": "#d3869bff",
|
"terminal.ansi.bright_magenta": "#8e5868ff",
|
||||||
"terminal.ansi.dim_magenta": "#7c455eff",
|
"terminal.ansi.dim_magenta": "#ff9ebbff",
|
||||||
"terminal.ansi.cyan": "#689d6aff",
|
"terminal.ansi.cyan": "#8ec07cff",
|
||||||
"terminal.ansi.bright_cyan": "#8ec07cff",
|
"terminal.ansi.bright_cyan": "#45603eff",
|
||||||
"terminal.ansi.dim_cyan": "#496e4aff",
|
"terminal.ansi.dim_cyan": "#c7dfbdff",
|
||||||
"terminal.ansi.white": "#a89984ff",
|
"terminal.ansi.white": "#fbf1c7ff",
|
||||||
"terminal.ansi.bright_white": "#fbf1c7ff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#766b5dff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#83a598ff",
|
"link_text.hover": "#83a598ff",
|
||||||
"version_control.added": "#b7bb26ff",
|
"version_control.added": "#b7bb26ff",
|
||||||
"version_control.modified": "#f9bd2fff",
|
"version_control.modified": "#f9bd2fff",
|
||||||
@@ -1295,30 +1295,30 @@
|
|||||||
"terminal.foreground": "#282828ff",
|
"terminal.foreground": "#282828ff",
|
||||||
"terminal.bright_foreground": "#282828ff",
|
"terminal.bright_foreground": "#282828ff",
|
||||||
"terminal.dim_foreground": "#fbf1c7ff",
|
"terminal.dim_foreground": "#fbf1c7ff",
|
||||||
"terminal.ansi.black": "#fbf1c7ff",
|
"terminal.ansi.black": "#282828ff",
|
||||||
"terminal.ansi.bright_black": "#928374ff",
|
"terminal.ansi.bright_black": "#0b6678ff",
|
||||||
"terminal.ansi.dim_black": "#7c6f64ff",
|
"terminal.ansi.dim_black": "#5f5650ff",
|
||||||
"terminal.ansi.red": "#cc241dff",
|
"terminal.ansi.red": "#9d0308ff",
|
||||||
"terminal.ansi.bright_red": "#9d0006ff",
|
"terminal.ansi.bright_red": "#db8b7aff",
|
||||||
"terminal.ansi.dim_red": "#c31c16ff",
|
"terminal.ansi.dim_red": "#4e1207ff",
|
||||||
"terminal.ansi.green": "#98971aff",
|
"terminal.ansi.green": "#797410ff",
|
||||||
"terminal.ansi.bright_green": "#79740eff",
|
"terminal.ansi.bright_green": "#bfb787ff",
|
||||||
"terminal.ansi.dim_green": "#929015ff",
|
"terminal.ansi.dim_green": "#3e3a11ff",
|
||||||
"terminal.ansi.yellow": "#d79921ff",
|
"terminal.ansi.yellow": "#b57615ff",
|
||||||
"terminal.ansi.bright_yellow": "#b57614ff",
|
"terminal.ansi.bright_yellow": "#e2b88bff",
|
||||||
"terminal.ansi.dim_yellow": "#cf8e1aff",
|
"terminal.ansi.dim_yellow": "#5c3a12ff",
|
||||||
"terminal.ansi.blue": "#458588ff",
|
"terminal.ansi.blue": "#0b6678ff",
|
||||||
"terminal.ansi.bright_blue": "#076678ff",
|
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||||
"terminal.ansi.dim_blue": "#356f77ff",
|
"terminal.ansi.dim_blue": "#14333bff",
|
||||||
"terminal.ansi.magenta": "#b16286ff",
|
"terminal.ansi.magenta": "#8f3e71ff",
|
||||||
"terminal.ansi.bright_magenta": "#8f3f71ff",
|
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||||
"terminal.ansi.dim_magenta": "#a85580ff",
|
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||||
"terminal.ansi.cyan": "#689d6aff",
|
"terminal.ansi.cyan": "#437b59ff",
|
||||||
"terminal.ansi.bright_cyan": "#427b58ff",
|
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||||
"terminal.ansi.dim_cyan": "#5f9166ff",
|
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||||
"terminal.ansi.white": "#7c6f64ff",
|
"terminal.ansi.white": "#fbf1c7ff",
|
||||||
"terminal.ansi.bright_white": "#282828ff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#282828ff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#0b6678ff",
|
"link_text.hover": "#0b6678ff",
|
||||||
"version_control.added": "#797410ff",
|
"version_control.added": "#797410ff",
|
||||||
"version_control.modified": "#b57615ff",
|
"version_control.modified": "#b57615ff",
|
||||||
@@ -1702,30 +1702,30 @@
|
|||||||
"terminal.foreground": "#282828ff",
|
"terminal.foreground": "#282828ff",
|
||||||
"terminal.bright_foreground": "#282828ff",
|
"terminal.bright_foreground": "#282828ff",
|
||||||
"terminal.dim_foreground": "#f9f5d7ff",
|
"terminal.dim_foreground": "#f9f5d7ff",
|
||||||
"terminal.ansi.black": "#fbf1c7ff",
|
"terminal.ansi.black": "#282828ff",
|
||||||
"terminal.ansi.bright_black": "#928374ff",
|
"terminal.ansi.bright_black": "#73675eff",
|
||||||
"terminal.ansi.dim_black": "#7c6f64ff",
|
"terminal.ansi.dim_black": "#f9f5d7ff",
|
||||||
"terminal.ansi.red": "#cc241dff",
|
"terminal.ansi.red": "#9d0308ff",
|
||||||
"terminal.ansi.bright_red": "#9d0006ff",
|
"terminal.ansi.bright_red": "#db8b7aff",
|
||||||
"terminal.ansi.dim_red": "#c31c16ff",
|
"terminal.ansi.dim_red": "#4e1207ff",
|
||||||
"terminal.ansi.green": "#98971aff",
|
"terminal.ansi.green": "#797410ff",
|
||||||
"terminal.ansi.bright_green": "#79740eff",
|
"terminal.ansi.bright_green": "#bfb787ff",
|
||||||
"terminal.ansi.dim_green": "#929015ff",
|
"terminal.ansi.dim_green": "#3e3a11ff",
|
||||||
"terminal.ansi.yellow": "#d79921ff",
|
"terminal.ansi.yellow": "#b57615ff",
|
||||||
"terminal.ansi.bright_yellow": "#b57614ff",
|
"terminal.ansi.bright_yellow": "#e2b88bff",
|
||||||
"terminal.ansi.dim_yellow": "#cf8e1aff",
|
"terminal.ansi.dim_yellow": "#5c3a12ff",
|
||||||
"terminal.ansi.blue": "#458588ff",
|
"terminal.ansi.blue": "#0b6678ff",
|
||||||
"terminal.ansi.bright_blue": "#076678ff",
|
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||||
"terminal.ansi.dim_blue": "#356f77ff",
|
"terminal.ansi.dim_blue": "#14333bff",
|
||||||
"terminal.ansi.magenta": "#b16286ff",
|
"terminal.ansi.magenta": "#8f3e71ff",
|
||||||
"terminal.ansi.bright_magenta": "#8f3f71ff",
|
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||||
"terminal.ansi.dim_magenta": "#a85580ff",
|
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||||
"terminal.ansi.cyan": "#689d6aff",
|
"terminal.ansi.cyan": "#437b59ff",
|
||||||
"terminal.ansi.bright_cyan": "#427b58ff",
|
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||||
"terminal.ansi.dim_cyan": "#5f9166ff",
|
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||||
"terminal.ansi.white": "#7c6f64ff",
|
"terminal.ansi.white": "#f9f5d7ff",
|
||||||
"terminal.ansi.bright_white": "#282828ff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#282828ff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#0b6678ff",
|
"link_text.hover": "#0b6678ff",
|
||||||
"version_control.added": "#797410ff",
|
"version_control.added": "#797410ff",
|
||||||
"version_control.modified": "#b57615ff",
|
"version_control.modified": "#b57615ff",
|
||||||
@@ -2109,30 +2109,30 @@
|
|||||||
"terminal.foreground": "#282828ff",
|
"terminal.foreground": "#282828ff",
|
||||||
"terminal.bright_foreground": "#282828ff",
|
"terminal.bright_foreground": "#282828ff",
|
||||||
"terminal.dim_foreground": "#f2e5bcff",
|
"terminal.dim_foreground": "#f2e5bcff",
|
||||||
"terminal.ansi.black": "#fbf1c7ff",
|
"terminal.ansi.black": "#282828ff",
|
||||||
"terminal.ansi.bright_black": "#928374ff",
|
"terminal.ansi.bright_black": "#73675eff",
|
||||||
"terminal.ansi.dim_black": "#7c6f64ff",
|
"terminal.ansi.dim_black": "#f2e5bcff",
|
||||||
"terminal.ansi.red": "#cc241dff",
|
"terminal.ansi.red": "#9d0308ff",
|
||||||
"terminal.ansi.bright_red": "#9d0006ff",
|
"terminal.ansi.bright_red": "#db8b7aff",
|
||||||
"terminal.ansi.dim_red": "#c31c16ff",
|
"terminal.ansi.dim_red": "#4e1207ff",
|
||||||
"terminal.ansi.green": "#98971aff",
|
"terminal.ansi.green": "#797410ff",
|
||||||
"terminal.ansi.bright_green": "#79740eff",
|
"terminal.ansi.bright_green": "#bfb787ff",
|
||||||
"terminal.ansi.dim_green": "#929015ff",
|
"terminal.ansi.dim_green": "#3e3a11ff",
|
||||||
"terminal.ansi.yellow": "#d79921ff",
|
"terminal.ansi.yellow": "#b57615ff",
|
||||||
"terminal.ansi.bright_yellow": "#b57614ff",
|
"terminal.ansi.bright_yellow": "#e2b88bff",
|
||||||
"terminal.ansi.dim_yellow": "#cf8e1aff",
|
"terminal.ansi.dim_yellow": "#5c3a12ff",
|
||||||
"terminal.ansi.blue": "#458588ff",
|
"terminal.ansi.blue": "#0b6678ff",
|
||||||
"terminal.ansi.bright_blue": "#076678ff",
|
"terminal.ansi.bright_blue": "#8fb0baff",
|
||||||
"terminal.ansi.dim_blue": "#356f77ff",
|
"terminal.ansi.dim_blue": "#14333bff",
|
||||||
"terminal.ansi.magenta": "#b16286ff",
|
"terminal.ansi.magenta": "#8f3e71ff",
|
||||||
"terminal.ansi.bright_magenta": "#8f3f71ff",
|
"terminal.ansi.bright_magenta": "#c76da0ff",
|
||||||
"terminal.ansi.dim_magenta": "#a85580ff",
|
"terminal.ansi.dim_magenta": "#5c2848ff",
|
||||||
"terminal.ansi.cyan": "#689d6aff",
|
"terminal.ansi.cyan": "#437b59ff",
|
||||||
"terminal.ansi.bright_cyan": "#427b58ff",
|
"terminal.ansi.bright_cyan": "#9fbca8ff",
|
||||||
"terminal.ansi.dim_cyan": "#5f9166ff",
|
"terminal.ansi.dim_cyan": "#253e2eff",
|
||||||
"terminal.ansi.white": "#7c6f64ff",
|
"terminal.ansi.white": "#f2e5bcff",
|
||||||
"terminal.ansi.bright_white": "#282828ff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#282828ff",
|
"terminal.ansi.dim_white": "#b0a189ff",
|
||||||
"link_text.hover": "#0b6678ff",
|
"link_text.hover": "#0b6678ff",
|
||||||
"version_control.added": "#797410ff",
|
"version_control.added": "#797410ff",
|
||||||
"version_control.modified": "#b57615ff",
|
"version_control.modified": "#b57615ff",
|
||||||
|
|||||||
@@ -68,34 +68,34 @@
|
|||||||
"editor.active_wrap_guide": "#c8ccd41a",
|
"editor.active_wrap_guide": "#c8ccd41a",
|
||||||
"editor.document_highlight.read_background": "#74ade81a",
|
"editor.document_highlight.read_background": "#74ade81a",
|
||||||
"editor.document_highlight.write_background": "#555a6366",
|
"editor.document_highlight.write_background": "#555a6366",
|
||||||
"terminal.background": "#282c34ff",
|
"terminal.background": "#282c33ff",
|
||||||
"terminal.foreground": "#abb2bfff",
|
"terminal.foreground": "#dce0e5ff",
|
||||||
"terminal.bright_foreground": "#dce0e5ff",
|
"terminal.bright_foreground": "#dce0e5ff",
|
||||||
"terminal.dim_foreground": "#636d83ff",
|
"terminal.dim_foreground": "#282c33ff",
|
||||||
"terminal.ansi.black": "#282c34ff",
|
"terminal.ansi.black": "#282c33ff",
|
||||||
"terminal.ansi.bright_black": "#636d83ff",
|
"terminal.ansi.bright_black": "#525561ff",
|
||||||
"terminal.ansi.dim_black": "#3b3f4aff",
|
"terminal.ansi.dim_black": "#dce0e5ff",
|
||||||
"terminal.ansi.red": "#e06c75ff",
|
"terminal.ansi.red": "#d07277ff",
|
||||||
"terminal.ansi.bright_red": "#EA858Bff",
|
"terminal.ansi.bright_red": "#673a3cff",
|
||||||
"terminal.ansi.dim_red": "#a7545aff",
|
"terminal.ansi.dim_red": "#eab7b9ff",
|
||||||
"terminal.ansi.green": "#98c379ff",
|
"terminal.ansi.green": "#a1c181ff",
|
||||||
"terminal.ansi.bright_green": "#AAD581ff",
|
"terminal.ansi.bright_green": "#4d6140ff",
|
||||||
"terminal.ansi.dim_green": "#6d8f59ff",
|
"terminal.ansi.dim_green": "#d1e0bfff",
|
||||||
"terminal.ansi.yellow": "#e5c07bff",
|
"terminal.ansi.yellow": "#dec184ff",
|
||||||
"terminal.ansi.bright_yellow": "#FFD885ff",
|
"terminal.ansi.bright_yellow": "#e5c07bff",
|
||||||
"terminal.ansi.dim_yellow": "#b8985bff",
|
"terminal.ansi.dim_yellow": "#f1dfc1ff",
|
||||||
"terminal.ansi.blue": "#61afefff",
|
"terminal.ansi.blue": "#74ade8ff",
|
||||||
"terminal.ansi.bright_blue": "#85C1FFff",
|
"terminal.ansi.bright_blue": "#385378ff",
|
||||||
"terminal.ansi.dim_blue": "#457cadff",
|
"terminal.ansi.dim_blue": "#bed5f4ff",
|
||||||
"terminal.ansi.magenta": "#c678ddff",
|
"terminal.ansi.magenta": "#b477cfff",
|
||||||
"terminal.ansi.bright_magenta": "#D398EBff",
|
"terminal.ansi.bright_magenta": "#d6b4e4ff",
|
||||||
"terminal.ansi.dim_magenta": "#8d54a0ff",
|
"terminal.ansi.dim_magenta": "#612a79ff",
|
||||||
"terminal.ansi.cyan": "#56b6c2ff",
|
"terminal.ansi.cyan": "#6eb4bfff",
|
||||||
"terminal.ansi.bright_cyan": "#6ED5DEff",
|
"terminal.ansi.bright_cyan": "#3a565bff",
|
||||||
"terminal.ansi.dim_cyan": "#3c818aff",
|
"terminal.ansi.dim_cyan": "#b9d9dfff",
|
||||||
"terminal.ansi.white": "#abb2bfff",
|
"terminal.ansi.white": "#dce0e5ff",
|
||||||
"terminal.ansi.bright_white": "#fafafaff",
|
"terminal.ansi.bright_white": "#fafafaff",
|
||||||
"terminal.ansi.dim_white": "#8f969bff",
|
"terminal.ansi.dim_white": "#575d65ff",
|
||||||
"link_text.hover": "#74ade8ff",
|
"link_text.hover": "#74ade8ff",
|
||||||
"version_control.added": "#27a657ff",
|
"version_control.added": "#27a657ff",
|
||||||
"version_control.modified": "#d3b020ff",
|
"version_control.modified": "#d3b020ff",
|
||||||
@@ -473,33 +473,33 @@
|
|||||||
"editor.document_highlight.read_background": "#5c78e225",
|
"editor.document_highlight.read_background": "#5c78e225",
|
||||||
"editor.document_highlight.write_background": "#a3a3a466",
|
"editor.document_highlight.write_background": "#a3a3a466",
|
||||||
"terminal.background": "#fafafaff",
|
"terminal.background": "#fafafaff",
|
||||||
"terminal.foreground": "#2a2c33ff",
|
"terminal.foreground": "#242529ff",
|
||||||
"terminal.bright_foreground": "#2a2c33ff",
|
"terminal.bright_foreground": "#242529ff",
|
||||||
"terminal.dim_foreground": "#bbbbbbff",
|
"terminal.dim_foreground": "#fafafaff",
|
||||||
"terminal.ansi.black": "#000000ff",
|
"terminal.ansi.black": "#242529ff",
|
||||||
"terminal.ansi.bright_black": "#000000ff",
|
"terminal.ansi.bright_black": "#747579ff",
|
||||||
"terminal.ansi.dim_black": "#555555ff",
|
"terminal.ansi.dim_black": "#97979aff",
|
||||||
"terminal.ansi.red": "#de3e35ff",
|
"terminal.ansi.red": "#d36151ff",
|
||||||
"terminal.ansi.bright_red": "#de3e35ff",
|
"terminal.ansi.bright_red": "#f0b0a4ff",
|
||||||
"terminal.ansi.dim_red": "#9c2b26ff",
|
"terminal.ansi.dim_red": "#6f312aff",
|
||||||
"terminal.ansi.green": "#3f953aff",
|
"terminal.ansi.green": "#669f59ff",
|
||||||
"terminal.ansi.bright_green": "#3f953aff",
|
"terminal.ansi.bright_green": "#b2cfa9ff",
|
||||||
"terminal.ansi.dim_green": "#2b6927ff",
|
"terminal.ansi.dim_green": "#354d2eff",
|
||||||
"terminal.ansi.yellow": "#d2b67cff",
|
"terminal.ansi.yellow": "#dec184ff",
|
||||||
"terminal.ansi.bright_yellow": "#d2b67cff",
|
"terminal.ansi.bright_yellow": "#826221ff",
|
||||||
"terminal.ansi.dim_yellow": "#a48c5aff",
|
"terminal.ansi.dim_yellow": "#786441ff",
|
||||||
"terminal.ansi.blue": "#2f5af3ff",
|
"terminal.ansi.blue": "#5c78e2ff",
|
||||||
"terminal.ansi.bright_blue": "#2f5af3ff",
|
"terminal.ansi.bright_blue": "#b5baf2ff",
|
||||||
"terminal.ansi.dim_blue": "#2140abff",
|
"terminal.ansi.dim_blue": "#2d3d75ff",
|
||||||
"terminal.ansi.magenta": "#950095ff",
|
"terminal.ansi.magenta": "#984ea5ff",
|
||||||
"terminal.ansi.bright_magenta": "#a00095ff",
|
"terminal.ansi.bright_magenta": "#cea6d3ff",
|
||||||
"terminal.ansi.dim_magenta": "#6a006aff",
|
"terminal.ansi.dim_magenta": "#4b2a50ff",
|
||||||
"terminal.ansi.cyan": "#3f953aff",
|
"terminal.ansi.cyan": "#3a82b7ff",
|
||||||
"terminal.ansi.bright_cyan": "#3f953aff",
|
"terminal.ansi.bright_cyan": "#a3bedaff",
|
||||||
"terminal.ansi.dim_cyan": "#2b6927ff",
|
"terminal.ansi.dim_cyan": "#254058ff",
|
||||||
"terminal.ansi.white": "#bbbbbbff",
|
"terminal.ansi.white": "#fafafaff",
|
||||||
"terminal.ansi.bright_white": "#ffffffff",
|
"terminal.ansi.bright_white": "#ffffffff",
|
||||||
"terminal.ansi.dim_white": "#888888ff",
|
"terminal.ansi.dim_white": "#aaaaaaff",
|
||||||
"link_text.hover": "#5c78e2ff",
|
"link_text.hover": "#5c78e2ff",
|
||||||
"version_control.added": "#27a657ff",
|
"version_control.added": "#27a657ff",
|
||||||
"version_control.modified": "#d3b020ff",
|
"version_control.modified": "#d3b020ff",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ disallowed-methods = [
|
|||||||
{ path = "std::process::Command::stderr", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stderr" },
|
{ path = "std::process::Command::stderr", reason = "`smol::process::Command::from()` does not preserve stdio configuration", replacement = "smol::process::Command::stderr" },
|
||||||
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
|
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
|
||||||
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
|
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
|
||||||
{ path = "cocoa::foundation::NSString::alloc", reason = "NSString must be autoreleased to avoid memory leaks. Use `ns_string()` helper instead." },
|
|
||||||
]
|
]
|
||||||
disallowed-types = [
|
disallowed-types = [
|
||||||
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },
|
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ url.workspace = true
|
|||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
watch.workspace = true
|
watch.workspace = true
|
||||||
urlencoding.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use language::language_settings::FormatOnSave;
|
|||||||
pub use mention::*;
|
pub use mention::*;
|
||||||
use project::lsp_store::{FormatTrigger, LspFormatTarget};
|
use project::lsp_store::{FormatTrigger, LspFormatTarget};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::to_string_pretty;
|
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use task::{Shell, ShellBuilder};
|
use task::{Shell, ShellBuilder};
|
||||||
pub use terminal::*;
|
pub use terminal::*;
|
||||||
@@ -44,7 +43,6 @@ pub struct UserMessage {
|
|||||||
pub content: ContentBlock,
|
pub content: ContentBlock,
|
||||||
pub chunks: Vec<acp::ContentBlock>,
|
pub chunks: Vec<acp::ContentBlock>,
|
||||||
pub checkpoint: Option<Checkpoint>,
|
pub checkpoint: Option<Checkpoint>,
|
||||||
pub indented: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -75,7 +73,6 @@ impl UserMessage {
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct AssistantMessage {
|
pub struct AssistantMessage {
|
||||||
pub chunks: Vec<AssistantMessageChunk>,
|
pub chunks: Vec<AssistantMessageChunk>,
|
||||||
pub indented: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantMessage {
|
impl AssistantMessage {
|
||||||
@@ -126,14 +123,6 @@ pub enum AgentThreadEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentThreadEntry {
|
impl AgentThreadEntry {
|
||||||
pub fn is_indented(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::UserMessage(message) => message.indented,
|
|
||||||
Self::AssistantMessage(message) => message.indented,
|
|
||||||
Self::ToolCall(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_markdown(&self, cx: &App) -> String {
|
pub fn to_markdown(&self, cx: &App) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::UserMessage(message) => message.to_markdown(cx),
|
Self::UserMessage(message) => message.to_markdown(cx),
|
||||||
@@ -193,7 +182,6 @@ pub struct ToolCall {
|
|||||||
pub locations: Vec<acp::ToolCallLocation>,
|
pub locations: Vec<acp::ToolCallLocation>,
|
||||||
pub resolved_locations: Vec<Option<AgentLocation>>,
|
pub resolved_locations: Vec<Option<AgentLocation>>,
|
||||||
pub raw_input: Option<serde_json::Value>,
|
pub raw_input: Option<serde_json::Value>,
|
||||||
pub raw_input_markdown: Option<Entity<Markdown>>,
|
|
||||||
pub raw_output: Option<serde_json::Value>,
|
pub raw_output: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,11 +212,6 @@ impl ToolCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_input_markdown = tool_call
|
|
||||||
.raw_input
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|input| markdown_for_raw_output(input, &language_registry, cx));
|
|
||||||
|
|
||||||
let result = Self {
|
let result = Self {
|
||||||
id: tool_call.tool_call_id,
|
id: tool_call.tool_call_id,
|
||||||
label: cx
|
label: cx
|
||||||
@@ -239,7 +222,6 @@ impl ToolCall {
|
|||||||
resolved_locations: Vec::default(),
|
resolved_locations: Vec::default(),
|
||||||
status,
|
status,
|
||||||
raw_input: tool_call.raw_input,
|
raw_input: tool_call.raw_input,
|
||||||
raw_input_markdown,
|
|
||||||
raw_output: tool_call.raw_output,
|
raw_output: tool_call.raw_output,
|
||||||
};
|
};
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@@ -315,7 +297,6 @@ impl ToolCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(raw_input) = raw_input {
|
if let Some(raw_input) = raw_input {
|
||||||
self.raw_input_markdown = markdown_for_raw_output(&raw_input, &language_registry, cx);
|
|
||||||
self.raw_input = Some(raw_input);
|
self.raw_input = Some(raw_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1203,16 +1184,6 @@ impl AcpThread {
|
|||||||
message_id: Option<UserMessageId>,
|
message_id: Option<UserMessageId>,
|
||||||
chunk: acp::ContentBlock,
|
chunk: acp::ContentBlock,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
|
||||||
self.push_user_content_block_with_indent(message_id, chunk, false, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_user_content_block_with_indent(
|
|
||||||
&mut self,
|
|
||||||
message_id: Option<UserMessageId>,
|
|
||||||
chunk: acp::ContentBlock,
|
|
||||||
indented: bool,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let language_registry = self.project.read(cx).languages().clone();
|
let language_registry = self.project.read(cx).languages().clone();
|
||||||
let path_style = self.project.read(cx).path_style(cx);
|
let path_style = self.project.read(cx).path_style(cx);
|
||||||
@@ -1223,10 +1194,8 @@ impl AcpThread {
|
|||||||
id,
|
id,
|
||||||
content,
|
content,
|
||||||
chunks,
|
chunks,
|
||||||
indented: existing_indented,
|
|
||||||
..
|
..
|
||||||
}) = last_entry
|
}) = last_entry
|
||||||
&& *existing_indented == indented
|
|
||||||
{
|
{
|
||||||
*id = message_id.or(id.take());
|
*id = message_id.or(id.take());
|
||||||
content.append(chunk.clone(), &language_registry, path_style, cx);
|
content.append(chunk.clone(), &language_registry, path_style, cx);
|
||||||
@@ -1241,7 +1210,6 @@ impl AcpThread {
|
|||||||
content,
|
content,
|
||||||
chunks: vec![chunk],
|
chunks: vec![chunk],
|
||||||
checkpoint: None,
|
checkpoint: None,
|
||||||
indented,
|
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -1253,26 +1221,12 @@ impl AcpThread {
|
|||||||
chunk: acp::ContentBlock,
|
chunk: acp::ContentBlock,
|
||||||
is_thought: bool,
|
is_thought: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
|
||||||
self.push_assistant_content_block_with_indent(chunk, is_thought, false, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_assistant_content_block_with_indent(
|
|
||||||
&mut self,
|
|
||||||
chunk: acp::ContentBlock,
|
|
||||||
is_thought: bool,
|
|
||||||
indented: bool,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
) {
|
||||||
let language_registry = self.project.read(cx).languages().clone();
|
let language_registry = self.project.read(cx).languages().clone();
|
||||||
let path_style = self.project.read(cx).path_style(cx);
|
let path_style = self.project.read(cx).path_style(cx);
|
||||||
let entries_len = self.entries.len();
|
let entries_len = self.entries.len();
|
||||||
if let Some(last_entry) = self.entries.last_mut()
|
if let Some(last_entry) = self.entries.last_mut()
|
||||||
&& let AgentThreadEntry::AssistantMessage(AssistantMessage {
|
&& let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry
|
||||||
chunks,
|
|
||||||
indented: existing_indented,
|
|
||||||
}) = last_entry
|
|
||||||
&& *existing_indented == indented
|
|
||||||
{
|
{
|
||||||
let idx = entries_len - 1;
|
let idx = entries_len - 1;
|
||||||
cx.emit(AcpThreadEvent::EntryUpdated(idx));
|
cx.emit(AcpThreadEvent::EntryUpdated(idx));
|
||||||
@@ -1301,7 +1255,6 @@ impl AcpThread {
|
|||||||
self.push_entry(
|
self.push_entry(
|
||||||
AgentThreadEntry::AssistantMessage(AssistantMessage {
|
AgentThreadEntry::AssistantMessage(AssistantMessage {
|
||||||
chunks: vec![chunk],
|
chunks: vec![chunk],
|
||||||
indented,
|
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -1364,7 +1317,6 @@ impl AcpThread {
|
|||||||
locations: Vec::new(),
|
locations: Vec::new(),
|
||||||
resolved_locations: Vec::new(),
|
resolved_locations: Vec::new(),
|
||||||
raw_input: None,
|
raw_input: None,
|
||||||
raw_input_markdown: None,
|
|
||||||
raw_output: None,
|
raw_output: None,
|
||||||
};
|
};
|
||||||
self.push_entry(AgentThreadEntry::ToolCall(failed_tool_call), cx);
|
self.push_entry(AgentThreadEntry::ToolCall(failed_tool_call), cx);
|
||||||
@@ -1752,7 +1704,6 @@ impl AcpThread {
|
|||||||
content: block,
|
content: block,
|
||||||
chunks: message,
|
chunks: message,
|
||||||
checkpoint: None,
|
checkpoint: None,
|
||||||
indented: false,
|
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -1993,42 +1944,37 @@ impl AcpThread {
|
|||||||
fn update_last_checkpoint(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
fn update_last_checkpoint(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||||
let git_store = self.project.read(cx).git_store().clone();
|
let git_store = self.project.read(cx).git_store().clone();
|
||||||
|
|
||||||
let Some((_, message)) = self.last_user_message() else {
|
let old_checkpoint = if let Some((_, message)) = self.last_user_message() {
|
||||||
|
if let Some(checkpoint) = message.checkpoint.as_ref() {
|
||||||
|
checkpoint.git_checkpoint.clone()
|
||||||
|
} else {
|
||||||
|
return Task::ready(Ok(()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return Task::ready(Ok(()));
|
return Task::ready(Ok(()));
|
||||||
};
|
};
|
||||||
let Some(user_message_id) = message.id.clone() else {
|
|
||||||
return Task::ready(Ok(()));
|
|
||||||
};
|
|
||||||
let Some(checkpoint) = message.checkpoint.as_ref() else {
|
|
||||||
return Task::ready(Ok(()));
|
|
||||||
};
|
|
||||||
let old_checkpoint = checkpoint.git_checkpoint.clone();
|
|
||||||
|
|
||||||
let new_checkpoint = git_store.update(cx, |git, cx| git.checkpoint(cx));
|
let new_checkpoint = git_store.update(cx, |git, cx| git.checkpoint(cx));
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let Some(new_checkpoint) = new_checkpoint
|
let new_checkpoint = new_checkpoint
|
||||||
.await
|
.await
|
||||||
.context("failed to get new checkpoint")
|
.context("failed to get new checkpoint")
|
||||||
.log_err()
|
.log_err();
|
||||||
else {
|
if let Some(new_checkpoint) = new_checkpoint {
|
||||||
return Ok(());
|
let equal = git_store
|
||||||
};
|
.update(cx, |git, cx| {
|
||||||
|
git.compare_checkpoints(old_checkpoint.clone(), new_checkpoint, cx)
|
||||||
let equal = git_store
|
})?
|
||||||
.update(cx, |git, cx| {
|
.await
|
||||||
git.compare_checkpoints(old_checkpoint.clone(), new_checkpoint, cx)
|
.unwrap_or(true);
|
||||||
})?
|
this.update(cx, |this, cx| {
|
||||||
.await
|
let (ix, message) = this.last_user_message().context("no user message")?;
|
||||||
.unwrap_or(true);
|
let checkpoint = message.checkpoint.as_mut().context("no checkpoint")?;
|
||||||
|
checkpoint.show = !equal;
|
||||||
this.update(cx, |this, cx| {
|
cx.emit(AcpThreadEvent::EntryUpdated(ix));
|
||||||
if let Some((ix, message)) = this.user_message_mut(&user_message_id) {
|
anyhow::Ok(())
|
||||||
if let Some(checkpoint) = message.checkpoint.as_mut() {
|
})??;
|
||||||
checkpoint.show = !equal;
|
}
|
||||||
cx.emit(AcpThreadEvent::EntryUpdated(ix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -2428,10 +2374,8 @@ fn markdown_for_raw_output(
|
|||||||
)
|
)
|
||||||
})),
|
})),
|
||||||
value => Some(cx.new(|cx| {
|
value => Some(cx.new(|cx| {
|
||||||
let pretty_json = to_string_pretty(value).unwrap_or_else(|_| value.to_string());
|
|
||||||
|
|
||||||
Markdown::new(
|
Markdown::new(
|
||||||
format!("```json\n{}\n```", pretty_json).into(),
|
format!("```json\n{}\n```", value).into(),
|
||||||
Some(language_registry.clone()),
|
Some(language_registry.clone()),
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
@@ -4074,67 +4018,4 @@ mod tests {
|
|||||||
"Should have exactly 2 terminals (the completed ones from before checkpoint)"
|
"Should have exactly 2 terminals (the completed ones from before checkpoint)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests that update_last_checkpoint correctly updates the original message's checkpoint
|
|
||||||
/// even when a new user message is added while the async checkpoint comparison is in progress.
|
|
||||||
///
|
|
||||||
/// This is a regression test for a bug where update_last_checkpoint would fail with
|
|
||||||
/// "no checkpoint" if a new user message (without a checkpoint) was added between when
|
|
||||||
/// update_last_checkpoint started and when its async closure ran.
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_update_last_checkpoint_with_new_message_added(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
|
||||||
fs.insert_tree(path!("/test"), json!({".git": {}, "file.txt": "content"}))
|
|
||||||
.await;
|
|
||||||
let project = Project::test(fs.clone(), [Path::new(path!("/test"))], cx).await;
|
|
||||||
|
|
||||||
let handler_done = Arc::new(AtomicBool::new(false));
|
|
||||||
let handler_done_clone = handler_done.clone();
|
|
||||||
let connection = Rc::new(FakeAgentConnection::new().on_user_message(
|
|
||||||
move |_, _thread, _cx| {
|
|
||||||
handler_done_clone.store(true, SeqCst);
|
|
||||||
async move { Ok(acp::PromptResponse::new(acp::StopReason::EndTurn)) }.boxed_local()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
let thread = cx
|
|
||||||
.update(|cx| connection.new_thread(project, Path::new(path!("/test")), cx))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let send_future = thread.update(cx, |thread, cx| thread.send_raw("First message", cx));
|
|
||||||
let send_task = cx.background_executor.spawn(send_future);
|
|
||||||
|
|
||||||
// Tick until handler completes, then a few more to let update_last_checkpoint start
|
|
||||||
while !handler_done.load(SeqCst) {
|
|
||||||
cx.executor().tick();
|
|
||||||
}
|
|
||||||
for _ in 0..5 {
|
|
||||||
cx.executor().tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
thread.update(cx, |thread, cx| {
|
|
||||||
thread.push_entry(
|
|
||||||
AgentThreadEntry::UserMessage(UserMessage {
|
|
||||||
id: Some(UserMessageId::new()),
|
|
||||||
content: ContentBlock::Empty,
|
|
||||||
chunks: vec!["Injected message (no checkpoint)".into()],
|
|
||||||
checkpoint: None,
|
|
||||||
indented: false,
|
|
||||||
}),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
|
||||||
let result = send_task.await;
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
result.is_ok(),
|
|
||||||
"send should succeed even when new message added during update_last_checkpoint: {:?}",
|
|
||||||
result.err()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,21 +204,12 @@ pub trait AgentModelSelector: 'static {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Icon for a model in the model selector.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum AgentModelIcon {
|
|
||||||
/// A built-in icon from Zed's icon set.
|
|
||||||
Named(IconName),
|
|
||||||
/// Path to a custom SVG icon file.
|
|
||||||
Path(SharedString),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AgentModelInfo {
|
pub struct AgentModelInfo {
|
||||||
pub id: acp::ModelId,
|
pub id: acp::ModelId,
|
||||||
pub name: SharedString,
|
pub name: SharedString,
|
||||||
pub description: Option<SharedString>,
|
pub description: Option<SharedString>,
|
||||||
pub icon: Option<AgentModelIcon>,
|
pub icon: Option<IconName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<acp::ModelInfo> for AgentModelInfo {
|
impl From<acp::ModelInfo> for AgentModelInfo {
|
||||||
@@ -248,10 +239,6 @@ impl AgentModelList {
|
|||||||
AgentModelList::Grouped(groups) => groups.is_empty(),
|
AgentModelList::Grouped(groups) => groups.is_empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_flat(&self) -> bool {
|
|
||||||
matches!(self, AgentModelList::Flat(_))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "test-support")]
|
#[cfg(feature = "test-support")]
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ impl Diff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_revealed_range(&self, cx: &App) -> bool {
|
pub fn has_revealed_range(&self, cx: &App) -> bool {
|
||||||
self.multibuffer().read(cx).paths().next().is_some()
|
self.multibuffer().read(cx).excerpt_paths().next().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
|
pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ use file_icons::FileIcons;
|
|||||||
use prompt_store::{PromptId, UserPromptId};
|
use prompt_store::{PromptId, UserPromptId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
fmt,
|
fmt,
|
||||||
ops::RangeInclusive,
|
ops::RangeInclusive,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use ui::{App, IconName, SharedString};
|
use ui::{App, IconName, SharedString};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use urlencoding::decode;
|
|
||||||
use util::paths::PathStyle;
|
use util::paths::PathStyle;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||||
@@ -76,13 +74,11 @@ impl MentionUri {
|
|||||||
let path = url.path();
|
let path = url.path();
|
||||||
match url.scheme() {
|
match url.scheme() {
|
||||||
"file" => {
|
"file" => {
|
||||||
let normalized = if path_style.is_windows() {
|
let path = if path_style.is_windows() {
|
||||||
path.trim_start_matches("/")
|
path.trim_start_matches("/")
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
};
|
};
|
||||||
let decoded = decode(normalized).unwrap_or(Cow::Borrowed(normalized));
|
|
||||||
let path = decoded.as_ref();
|
|
||||||
|
|
||||||
if let Some(fragment) = url.fragment() {
|
if let Some(fragment) = url.fragment() {
|
||||||
let line_range = parse_line_range(fragment)?;
|
let line_range = parse_line_range(fragment)?;
|
||||||
@@ -410,19 +406,6 @@ mod tests {
|
|||||||
assert_eq!(parsed.to_uri().to_string(), selection_uri);
|
assert_eq!(parsed.to_uri().to_string(), selection_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_file_uri_with_non_ascii() {
|
|
||||||
let file_uri = uri!("file:///path/to/%E6%97%A5%E6%9C%AC%E8%AA%9E.txt");
|
|
||||||
let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
|
|
||||||
match &parsed {
|
|
||||||
MentionUri::File { abs_path } => {
|
|
||||||
assert_eq!(abs_path, Path::new(path!("/path/to/日本語.txt")));
|
|
||||||
}
|
|
||||||
_ => panic!("Expected File variant"),
|
|
||||||
}
|
|
||||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_untitled_selection_uri() {
|
fn test_parse_untitled_selection_uri() {
|
||||||
let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10");
|
let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10");
|
||||||
|
|||||||
@@ -187,10 +187,8 @@ pub async fn create_terminal_entity(
|
|||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disable pagers so agent/terminal commands don't hang behind interactive UIs
|
// Disables paging for `git` and hopefully other commands
|
||||||
env.insert("PAGER".into(), "".into());
|
env.insert("PAGER".into(), "".into());
|
||||||
// Override user core.pager (e.g. delta) which Git prefers over PAGER
|
|
||||||
env.insert("GIT_PAGER".into(), "cat".into());
|
|
||||||
env.extend(env_vars);
|
env.extend(env_vars);
|
||||||
|
|
||||||
// Use remote shell or default system shell, as appropriate
|
// Use remote shell or default system shell, as appropriate
|
||||||
|
|||||||
@@ -371,13 +371,13 @@ impl AcpTools {
|
|||||||
syntax: cx.theme().syntax().clone(),
|
syntax: cx.theme().syntax().clone(),
|
||||||
code_block_overflow_x_scroll: true,
|
code_block_overflow_x_scroll: true,
|
||||||
code_block: StyleRefinement {
|
code_block: StyleRefinement {
|
||||||
text: TextStyleRefinement {
|
text: Some(TextStyleRefinement {
|
||||||
font_family: Some(
|
font_family: Some(
|
||||||
theme_settings.buffer_font.family.clone(),
|
theme_settings.buffer_font.family.clone(),
|
||||||
),
|
),
|
||||||
font_size: Some((base_size * 0.8).into()),
|
font_size: Some((base_size * 0.8).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use futures::{FutureExt, StreamExt, channel::mpsc};
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,
|
App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,
|
||||||
};
|
};
|
||||||
use language::{Anchor, Buffer, BufferEvent, Point, ToPoint};
|
use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
|
||||||
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
|
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
|
||||||
use std::{cmp, ops::Range, sync::Arc};
|
use std::{cmp, ops::Range, sync::Arc};
|
||||||
use text::{Edit, Patch, Rope};
|
use text::{Edit, Patch, Rope};
|
||||||
@@ -150,7 +150,7 @@ impl ActionLog {
|
|||||||
if buffer
|
if buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
.is_some_and(|file| file.disk_state().is_deleted())
|
.is_some_and(|file| file.disk_state() == DiskState::Deleted)
|
||||||
{
|
{
|
||||||
// If the buffer had been edited by a tool, but it got
|
// If the buffer had been edited by a tool, but it got
|
||||||
// deleted externally, we want to stop tracking it.
|
// deleted externally, we want to stop tracking it.
|
||||||
@@ -162,7 +162,7 @@ impl ActionLog {
|
|||||||
if buffer
|
if buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
.is_some_and(|file| !file.disk_state().is_deleted())
|
.is_some_and(|file| file.disk_state() != DiskState::Deleted)
|
||||||
{
|
{
|
||||||
// If the buffer had been deleted by a tool, but it got
|
// If the buffer had been deleted by a tool, but it got
|
||||||
// resurrected externally, we want to clear the edits we
|
// resurrected externally, we want to clear the edits we
|
||||||
@@ -769,7 +769,7 @@ impl ActionLog {
|
|||||||
tracked.version != buffer.version
|
tracked.version != buffer.version
|
||||||
&& buffer
|
&& buffer
|
||||||
.file()
|
.file()
|
||||||
.is_some_and(|file| !file.disk_state().is_deleted())
|
.is_some_and(|file| file.disk_state() != DiskState::Deleted)
|
||||||
})
|
})
|
||||||
.map(|(buffer, _)| buffer)
|
.map(|(buffer, _)| buffer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ mod legacy_thread;
|
|||||||
mod native_agent_server;
|
mod native_agent_server;
|
||||||
pub mod outline;
|
pub mod outline;
|
||||||
mod templates;
|
mod templates;
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
mod thread;
|
mod thread;
|
||||||
mod tools;
|
mod tools;
|
||||||
|
|
||||||
use context_server::ContextServerId;
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub use db::*;
|
pub use db::*;
|
||||||
pub use history_store::*;
|
pub use history_store::*;
|
||||||
pub use native_agent_server::NativeAgentServer;
|
pub use native_agent_server::NativeAgentServer;
|
||||||
@@ -18,11 +18,11 @@ pub use templates::*;
|
|||||||
pub use thread::*;
|
pub use thread::*;
|
||||||
pub use tools::*;
|
pub use tools::*;
|
||||||
|
|
||||||
use acp_thread::{AcpThread, AgentModelSelector, UserMessageId};
|
use acp_thread::{AcpThread, AgentModelSelector};
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::{HashMap, HashSet, IndexMap};
|
use collections::{HashSet, IndexMap};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::channel::{mpsc, oneshot};
|
use futures::channel::{mpsc, oneshot};
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
@@ -30,15 +30,15 @@ use futures::{StreamExt, future};
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,
|
App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity,
|
||||||
};
|
};
|
||||||
use language_model::{IconOrSvg, LanguageModel, LanguageModelProvider, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelProvider, LanguageModelRegistry};
|
||||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||||
use prompt_store::{
|
use prompt_store::{
|
||||||
ProjectContext, PromptStore, RULES_FILE_NAMES, RulesFileContext, UserRulesContext,
|
ProjectContext, PromptStore, RulesFileContext, UserRulesContext, WorktreeContext,
|
||||||
WorktreeContext,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{LanguageModelSelection, update_settings_file};
|
use settings::{LanguageModelSelection, update_settings_file};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -51,6 +51,18 @@ pub struct ProjectSnapshot {
|
|||||||
pub timestamp: DateTime<Utc>,
|
pub timestamp: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RULES_FILE_NAMES: [&str; 9] = [
|
||||||
|
".rules",
|
||||||
|
".cursorrules",
|
||||||
|
".windsurfrules",
|
||||||
|
".clinerules",
|
||||||
|
".github/copilot-instructions.md",
|
||||||
|
"CLAUDE.md",
|
||||||
|
"AGENT.md",
|
||||||
|
"AGENTS.md",
|
||||||
|
"GEMINI.md",
|
||||||
|
];
|
||||||
|
|
||||||
pub struct RulesLoadingError {
|
pub struct RulesLoadingError {
|
||||||
pub message: SharedString,
|
pub message: SharedString,
|
||||||
}
|
}
|
||||||
@@ -93,7 +105,7 @@ impl LanguageModels {
|
|||||||
fn refresh_list(&mut self, cx: &App) {
|
fn refresh_list(&mut self, cx: &App) {
|
||||||
let providers = LanguageModelRegistry::global(cx)
|
let providers = LanguageModelRegistry::global(cx)
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.visible_providers()
|
.providers()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|provider| provider.is_authenticated(cx))
|
.filter(|provider| provider.is_authenticated(cx))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -153,10 +165,7 @@ impl LanguageModels {
|
|||||||
id: Self::model_id(model),
|
id: Self::model_id(model),
|
||||||
name: model.name().0,
|
name: model.name().0,
|
||||||
description: None,
|
description: None,
|
||||||
icon: Some(match provider.icon() {
|
icon: Some(provider.icon()),
|
||||||
IconOrSvg::Svg(path) => acp_thread::AgentModelIcon::Path(path),
|
|
||||||
IconOrSvg::Icon(name) => acp_thread::AgentModelIcon::Named(name),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +176,7 @@ impl LanguageModels {
|
|||||||
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
|
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
|
||||||
let authenticate_all_providers = LanguageModelRegistry::global(cx)
|
let authenticate_all_providers = LanguageModelRegistry::global(cx)
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.visible_providers()
|
.providers()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|provider| (provider.id(), provider.name(), provider.authenticate(cx)))
|
.map(|provider| (provider.id(), provider.name(), provider.authenticate(cx)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -254,24 +263,12 @@ impl NativeAgent {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let context_server_store = project.read(cx).context_server_store();
|
|
||||||
let context_server_registry =
|
|
||||||
cx.new(|cx| ContextServerRegistry::new(context_server_store.clone(), cx));
|
|
||||||
|
|
||||||
let mut subscriptions = vec![
|
let mut subscriptions = vec![
|
||||||
cx.subscribe(&project, Self::handle_project_event),
|
cx.subscribe(&project, Self::handle_project_event),
|
||||||
cx.subscribe(
|
cx.subscribe(
|
||||||
&LanguageModelRegistry::global(cx),
|
&LanguageModelRegistry::global(cx),
|
||||||
Self::handle_models_updated_event,
|
Self::handle_models_updated_event,
|
||||||
),
|
),
|
||||||
cx.subscribe(
|
|
||||||
&context_server_store,
|
|
||||||
Self::handle_context_server_store_updated,
|
|
||||||
),
|
|
||||||
cx.subscribe(
|
|
||||||
&context_server_registry,
|
|
||||||
Self::handle_context_server_registry_event,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
if let Some(prompt_store) = prompt_store.as_ref() {
|
if let Some(prompt_store) = prompt_store.as_ref() {
|
||||||
subscriptions.push(cx.subscribe(prompt_store, Self::handle_prompts_updated_event))
|
subscriptions.push(cx.subscribe(prompt_store, Self::handle_prompts_updated_event))
|
||||||
@@ -280,14 +277,16 @@ impl NativeAgent {
|
|||||||
let (project_context_needs_refresh_tx, project_context_needs_refresh_rx) =
|
let (project_context_needs_refresh_tx, project_context_needs_refresh_rx) =
|
||||||
watch::channel(());
|
watch::channel(());
|
||||||
Self {
|
Self {
|
||||||
sessions: HashMap::default(),
|
sessions: HashMap::new(),
|
||||||
history,
|
history,
|
||||||
project_context: cx.new(|_| project_context),
|
project_context: cx.new(|_| project_context),
|
||||||
project_context_needs_refresh: project_context_needs_refresh_tx,
|
project_context_needs_refresh: project_context_needs_refresh_tx,
|
||||||
_maintain_project_context: cx.spawn(async move |this, cx| {
|
_maintain_project_context: cx.spawn(async move |this, cx| {
|
||||||
Self::maintain_project_context(this, project_context_needs_refresh_rx, cx).await
|
Self::maintain_project_context(this, project_context_needs_refresh_rx, cx).await
|
||||||
}),
|
}),
|
||||||
context_server_registry,
|
context_server_registry: cx.new(|cx| {
|
||||||
|
ContextServerRegistry::new(project.read(cx).context_server_store(), cx)
|
||||||
|
}),
|
||||||
templates,
|
templates,
|
||||||
models: LanguageModels::new(cx),
|
models: LanguageModels::new(cx),
|
||||||
project,
|
project,
|
||||||
@@ -356,9 +355,6 @@ impl NativeAgent {
|
|||||||
pending_save: Task::ready(()),
|
pending_save: Task::ready(()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
self.update_available_commands(cx);
|
|
||||||
|
|
||||||
acp_thread
|
acp_thread
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +425,10 @@ impl NativeAgent {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(contents, prompt_metadata)| match contents {
|
.flat_map(|(contents, prompt_metadata)| match contents {
|
||||||
Ok(contents) => Some(UserRulesContext {
|
Ok(contents) => Some(UserRulesContext {
|
||||||
uuid: prompt_metadata.id.as_user()?,
|
uuid: match prompt_metadata.id {
|
||||||
|
prompt_store::PromptId::User { uuid } => uuid,
|
||||||
|
prompt_store::PromptId::EditWorkflow => return None,
|
||||||
|
},
|
||||||
title: prompt_metadata.title.map(|title| title.to_string()),
|
title: prompt_metadata.title.map(|title| title.to_string()),
|
||||||
contents,
|
contents,
|
||||||
}),
|
}),
|
||||||
@@ -623,99 +622,6 @@ impl NativeAgent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_context_server_store_updated(
|
|
||||||
&mut self,
|
|
||||||
_store: Entity<project::context_server_store::ContextServerStore>,
|
|
||||||
_event: &project::context_server_store::Event,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.update_available_commands(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_context_server_registry_event(
|
|
||||||
&mut self,
|
|
||||||
_registry: Entity<ContextServerRegistry>,
|
|
||||||
event: &ContextServerRegistryEvent,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
ContextServerRegistryEvent::ToolsChanged => {}
|
|
||||||
ContextServerRegistryEvent::PromptsChanged => {
|
|
||||||
self.update_available_commands(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_available_commands(&self, cx: &mut Context<Self>) {
|
|
||||||
let available_commands = self.build_available_commands(cx);
|
|
||||||
for session in self.sessions.values() {
|
|
||||||
if let Some(acp_thread) = session.acp_thread.upgrade() {
|
|
||||||
acp_thread.update(cx, |thread, cx| {
|
|
||||||
thread
|
|
||||||
.handle_session_update(
|
|
||||||
acp::SessionUpdate::AvailableCommandsUpdate(
|
|
||||||
acp::AvailableCommandsUpdate::new(available_commands.clone()),
|
|
||||||
),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.log_err();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_available_commands(&self, cx: &App) -> Vec<acp::AvailableCommand> {
|
|
||||||
let registry = self.context_server_registry.read(cx);
|
|
||||||
|
|
||||||
let mut prompt_name_counts: HashMap<&str, usize> = HashMap::default();
|
|
||||||
for context_server_prompt in registry.prompts() {
|
|
||||||
*prompt_name_counts
|
|
||||||
.entry(context_server_prompt.prompt.name.as_str())
|
|
||||||
.or_insert(0) += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
registry
|
|
||||||
.prompts()
|
|
||||||
.flat_map(|context_server_prompt| {
|
|
||||||
let prompt = &context_server_prompt.prompt;
|
|
||||||
|
|
||||||
let should_prefix = prompt_name_counts
|
|
||||||
.get(prompt.name.as_str())
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(0)
|
|
||||||
> 1;
|
|
||||||
|
|
||||||
let name = if should_prefix {
|
|
||||||
format!("{}.{}", context_server_prompt.server_id, prompt.name)
|
|
||||||
} else {
|
|
||||||
prompt.name.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut command = acp::AvailableCommand::new(
|
|
||||||
name,
|
|
||||||
prompt.description.clone().unwrap_or_default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
match prompt.arguments.as_deref() {
|
|
||||||
Some([arg]) => {
|
|
||||||
let hint = format!("<{}>", arg.name);
|
|
||||||
|
|
||||||
command = command.input(acp::AvailableCommandInput::Unstructured(
|
|
||||||
acp::UnstructuredCommandInput::new(hint),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Some([]) | None => {}
|
|
||||||
Some(_) => {
|
|
||||||
// skip >1 argument commands since we don't support them yet
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(command)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_thread(
|
pub fn load_thread(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: acp::SessionId,
|
id: acp::SessionId,
|
||||||
@@ -814,102 +720,6 @@ impl NativeAgent {
|
|||||||
history.update(cx, |history, cx| history.reload(cx)).ok();
|
history.update(cx, |history, cx| history.reload(cx)).ok();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_mcp_prompt(
|
|
||||||
&self,
|
|
||||||
message_id: UserMessageId,
|
|
||||||
session_id: agent_client_protocol::SessionId,
|
|
||||||
prompt_name: String,
|
|
||||||
server_id: ContextServerId,
|
|
||||||
arguments: HashMap<String, String>,
|
|
||||||
original_content: Vec<acp::ContentBlock>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Task<Result<acp::PromptResponse>> {
|
|
||||||
let server_store = self.context_server_registry.read(cx).server_store().clone();
|
|
||||||
let path_style = self.project.read(cx).path_style(cx);
|
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
let prompt =
|
|
||||||
crate::get_prompt(&server_store, &server_id, &prompt_name, arguments, cx).await?;
|
|
||||||
|
|
||||||
let (acp_thread, thread) = this.update(cx, |this, _cx| {
|
|
||||||
let session = this
|
|
||||||
.sessions
|
|
||||||
.get(&session_id)
|
|
||||||
.context("Failed to get session")?;
|
|
||||||
anyhow::Ok((session.acp_thread.clone(), session.thread.clone()))
|
|
||||||
})??;
|
|
||||||
|
|
||||||
let mut last_is_user = true;
|
|
||||||
|
|
||||||
thread.update(cx, |thread, cx| {
|
|
||||||
thread.push_acp_user_block(
|
|
||||||
message_id,
|
|
||||||
original_content.into_iter().skip(1),
|
|
||||||
path_style,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for message in prompt.messages {
|
|
||||||
let context_server::types::PromptMessage { role, content } = message;
|
|
||||||
let block = mcp_message_content_to_acp_content_block(content);
|
|
||||||
|
|
||||||
match role {
|
|
||||||
context_server::types::Role::User => {
|
|
||||||
let id = acp_thread::UserMessageId::new();
|
|
||||||
|
|
||||||
acp_thread.update(cx, |acp_thread, cx| {
|
|
||||||
acp_thread.push_user_content_block_with_indent(
|
|
||||||
Some(id.clone()),
|
|
||||||
block.clone(),
|
|
||||||
true,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
|
|
||||||
thread.update(cx, |thread, cx| {
|
|
||||||
thread.push_acp_user_block(id, [block], path_style, cx);
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
}
|
|
||||||
context_server::types::Role::Assistant => {
|
|
||||||
acp_thread.update(cx, |acp_thread, cx| {
|
|
||||||
acp_thread.push_assistant_content_block_with_indent(
|
|
||||||
block.clone(),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
|
|
||||||
thread.update(cx, |thread, cx| {
|
|
||||||
thread.push_acp_agent_block(block, cx);
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last_is_user = role == context_server::types::Role::User;
|
|
||||||
}
|
|
||||||
|
|
||||||
let response_stream = thread.update(cx, |thread, cx| {
|
|
||||||
if last_is_user {
|
|
||||||
thread.send_existing(cx)
|
|
||||||
} else {
|
|
||||||
// Resume if MCP prompt did not end with a user message
|
|
||||||
thread.resume(cx)
|
|
||||||
}
|
|
||||||
})??;
|
|
||||||
|
|
||||||
cx.update(|cx| {
|
|
||||||
NativeAgentConnection::handle_thread_events(response_stream, acp_thread, cx)
|
|
||||||
})?
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper struct that implements the AgentConnection trait
|
/// Wrapper struct that implements the AgentConnection trait
|
||||||
@@ -1044,39 +854,6 @@ impl NativeAgentConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Command<'a> {
|
|
||||||
prompt_name: &'a str,
|
|
||||||
arg_value: &'a str,
|
|
||||||
explicit_server_id: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Command<'a> {
|
|
||||||
fn parse(prompt: &'a [acp::ContentBlock]) -> Option<Self> {
|
|
||||||
let acp::ContentBlock::Text(text_content) = prompt.first()? else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let text = text_content.text.trim();
|
|
||||||
let command = text.strip_prefix('/')?;
|
|
||||||
let (command, arg_value) = command
|
|
||||||
.split_once(char::is_whitespace)
|
|
||||||
.unwrap_or((command, ""));
|
|
||||||
|
|
||||||
if let Some((server_id, prompt_name)) = command.split_once('.') {
|
|
||||||
Some(Self {
|
|
||||||
prompt_name,
|
|
||||||
arg_value,
|
|
||||||
explicit_server_id: Some(server_id),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Some(Self {
|
|
||||||
prompt_name: command,
|
|
||||||
arg_value,
|
|
||||||
explicit_server_id: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NativeAgentModelSelector {
|
struct NativeAgentModelSelector {
|
||||||
session_id: acp::SessionId,
|
session_id: acp::SessionId,
|
||||||
connection: NativeAgentConnection,
|
connection: NativeAgentConnection,
|
||||||
@@ -1242,47 +1019,6 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
|
|||||||
let session_id = params.session_id.clone();
|
let session_id = params.session_id.clone();
|
||||||
log::info!("Received prompt request for session: {}", session_id);
|
log::info!("Received prompt request for session: {}", session_id);
|
||||||
log::debug!("Prompt blocks count: {}", params.prompt.len());
|
log::debug!("Prompt blocks count: {}", params.prompt.len());
|
||||||
|
|
||||||
if let Some(parsed_command) = Command::parse(¶ms.prompt) {
|
|
||||||
let registry = self.0.read(cx).context_server_registry.read(cx);
|
|
||||||
|
|
||||||
let explicit_server_id = parsed_command
|
|
||||||
.explicit_server_id
|
|
||||||
.map(|server_id| ContextServerId(server_id.into()));
|
|
||||||
|
|
||||||
if let Some(prompt) =
|
|
||||||
registry.find_prompt(explicit_server_id.as_ref(), parsed_command.prompt_name)
|
|
||||||
{
|
|
||||||
let arguments = if !parsed_command.arg_value.is_empty()
|
|
||||||
&& let Some(arg_name) = prompt
|
|
||||||
.prompt
|
|
||||||
.arguments
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|args| args.first())
|
|
||||||
.map(|arg| arg.name.clone())
|
|
||||||
{
|
|
||||||
HashMap::from_iter([(arg_name, parsed_command.arg_value.to_string())])
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let prompt_name = prompt.prompt.name.clone();
|
|
||||||
let server_id = prompt.server_id.clone();
|
|
||||||
|
|
||||||
return self.0.update(cx, |agent, cx| {
|
|
||||||
agent.send_mcp_prompt(
|
|
||||||
id,
|
|
||||||
session_id.clone(),
|
|
||||||
prompt_name,
|
|
||||||
server_id,
|
|
||||||
arguments,
|
|
||||||
params.prompt,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let path_style = self.0.read(cx).project.read(cx).path_style(cx);
|
let path_style = self.0.read(cx).project.read(cx).path_style(cx);
|
||||||
|
|
||||||
self.run_turn(session_id, cx, move |thread, cx| {
|
self.run_turn(session_id, cx, move |thread, cx| {
|
||||||
@@ -1483,15 +1219,6 @@ impl TerminalHandle for AcpTerminalHandle {
|
|||||||
self.terminal
|
self.terminal
|
||||||
.read_with(cx, |term, cx| term.current_output(cx))
|
.read_with(cx, |term, cx| term.current_output(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill(&self, cx: &AsyncApp) -> Result<()> {
|
|
||||||
cx.update(|cx| {
|
|
||||||
self.terminal.update(cx, |terminal, cx| {
|
|
||||||
terminal.kill(cx);
|
|
||||||
});
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -1629,9 +1356,7 @@ mod internal_tests {
|
|||||||
id: acp::ModelId::new("fake/fake"),
|
id: acp::ModelId::new("fake/fake"),
|
||||||
name: "Fake".into(),
|
name: "Fake".into(),
|
||||||
description: None,
|
description: None,
|
||||||
icon: Some(acp_thread::AgentModelIcon::Named(
|
icon: Some(ui::IconName::ZedAssistant),
|
||||||
ui::IconName::ZedAssistant
|
|
||||||
)),
|
|
||||||
}]
|
}]
|
||||||
)])
|
)])
|
||||||
);
|
);
|
||||||
@@ -1881,35 +1606,3 @@ mod internal_tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mcp_message_content_to_acp_content_block(
|
|
||||||
content: context_server::types::MessageContent,
|
|
||||||
) -> acp::ContentBlock {
|
|
||||||
match content {
|
|
||||||
context_server::types::MessageContent::Text {
|
|
||||||
text,
|
|
||||||
annotations: _,
|
|
||||||
} => text.into(),
|
|
||||||
context_server::types::MessageContent::Image {
|
|
||||||
data,
|
|
||||||
mime_type,
|
|
||||||
annotations: _,
|
|
||||||
} => acp::ContentBlock::Image(acp::ImageContent::new(data, mime_type)),
|
|
||||||
context_server::types::MessageContent::Audio {
|
|
||||||
data,
|
|
||||||
mime_type,
|
|
||||||
annotations: _,
|
|
||||||
} => acp::ContentBlock::Audio(acp::AudioContent::new(data, mime_type)),
|
|
||||||
context_server::types::MessageContent::Resource {
|
|
||||||
resource,
|
|
||||||
annotations: _,
|
|
||||||
} => {
|
|
||||||
let mut link =
|
|
||||||
acp::ResourceLink::new(resource.uri.to_string(), resource.uri.to_string());
|
|
||||||
if let Some(mime_type) = resource.mime_type {
|
|
||||||
link = link.mime_type(mime_type);
|
|
||||||
}
|
|
||||||
acp::ContentBlock::ResourceLink(link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1343,7 +1343,6 @@ fn run_eval(eval: EvalInput) -> eval_utils::EvalOutput<EditEvalMetadata> {
|
|||||||
let test = EditAgentTest::new(&mut cx).await;
|
let test = EditAgentTest::new(&mut cx).await;
|
||||||
test.eval(eval, &mut cx).await
|
test.eval(eval, &mut cx).await
|
||||||
});
|
});
|
||||||
cx.quit();
|
|
||||||
match result {
|
match result {
|
||||||
Ok(output) => eval_utils::EvalOutput {
|
Ok(output) => eval_utils::EvalOutput {
|
||||||
data: output.to_string(),
|
data: output.to_string(),
|
||||||
|
|||||||
@@ -216,10 +216,14 @@ impl HistoryStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reload(&self, cx: &mut Context<Self>) {
|
pub fn reload(&self, cx: &mut Context<Self>) {
|
||||||
let database_connection = ThreadsDatabase::connect(cx);
|
let database_future = ThreadsDatabase::connect(cx);
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let database = database_connection.await;
|
let threads = database_future
|
||||||
let threads = database.map_err(|err| anyhow!(err))?.list_threads().await?;
|
.await
|
||||||
|
.map_err(|err| anyhow!(err))?
|
||||||
|
.list_threads()
|
||||||
|
.await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if this.recently_opened_entries.len() < MAX_RECENTLY_OPENED_ENTRIES {
|
if this.recently_opened_entries.len() < MAX_RECENTLY_OPENED_ENTRIES {
|
||||||
for thread in threads
|
for thread in threads
|
||||||
@@ -340,8 +344,7 @@ impl HistoryStore {
|
|||||||
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<VecDeque<HistoryEntryId>>> {
|
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<VecDeque<HistoryEntryId>>> {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
if cfg!(any(feature = "test-support", test)) {
|
if cfg!(any(feature = "test-support", test)) {
|
||||||
log::warn!("history store does not persist in tests");
|
anyhow::bail!("history store does not persist in tests");
|
||||||
return Ok(VecDeque::new());
|
|
||||||
}
|
}
|
||||||
let json = KEY_VALUE_STORE
|
let json = KEY_VALUE_STORE
|
||||||
.read_kvp(RECENTLY_OPENED_THREADS_KEY)?
|
.read_kvp(RECENTLY_OPENED_THREADS_KEY)?
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use agent_client_protocol as acp;
|
|
||||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||||
use agent_settings::AgentSettings;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashSet;
|
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, Entity, SharedString, Task};
|
use gpui::{App, Entity, SharedString, Task};
|
||||||
use prompt_store::PromptStore;
|
use prompt_store::PromptStore;
|
||||||
use settings::{LanguageModelSelection, Settings as _, update_settings_file};
|
|
||||||
|
|
||||||
use crate::{HistoryStore, NativeAgent, NativeAgentConnection, templates::Templates};
|
use crate::{HistoryStore, NativeAgent, NativeAgentConnection, templates::Templates};
|
||||||
|
|
||||||
@@ -75,38 +71,6 @@ impl AgentServer for NativeAgentServer {
|
|||||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn favorite_model_ids(&self, cx: &mut App) -> HashSet<acp::ModelId> {
|
|
||||||
AgentSettings::get_global(cx).favorite_model_ids()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_favorite_model(
|
|
||||||
&self,
|
|
||||||
model_id: acp::ModelId,
|
|
||||||
should_be_favorite: bool,
|
|
||||||
fs: Arc<dyn Fs>,
|
|
||||||
cx: &App,
|
|
||||||
) {
|
|
||||||
let selection = model_id_to_selection(&model_id);
|
|
||||||
update_settings_file(fs, cx, move |settings, _| {
|
|
||||||
let agent = settings.agent.get_or_insert_default();
|
|
||||||
if should_be_favorite {
|
|
||||||
agent.add_favorite_model(selection.clone());
|
|
||||||
} else {
|
|
||||||
agent.remove_favorite_model(&selection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a ModelId (e.g. "anthropic/claude-3-5-sonnet") to a LanguageModelSelection.
|
|
||||||
fn model_id_to_selection(model_id: &acp::ModelId) -> LanguageModelSelection {
|
|
||||||
let id = model_id.0.as_ref();
|
|
||||||
let (provider, model) = id.split_once('/').unwrap_or(("", id));
|
|
||||||
LanguageModelSelection {
|
|
||||||
provider: provider.to_owned().into(),
|
|
||||||
model: model.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ You are a highly skilled software engineer with extensive knowledge in many prog
|
|||||||
3. DO NOT use tools to access items that are already available in the context section.
|
3. DO NOT use tools to access items that are already available in the context section.
|
||||||
4. Use only the tools that are currently available.
|
4. Use only the tools that are currently available.
|
||||||
5. DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
|
5. DO NOT use a tool that is not available just because it appears in the conversation. This means the user turned it off.
|
||||||
6. When running commands that may run indefinitely or for a long time (such as build scripts, tests, servers, or file watchers), specify `timeout_ms` to bound runtime. If the command times out, the user can always ask you to run it again with a longer timeout or no timeout if they're willing to wait or cancel manually.
|
6. NEVER run commands that don't terminate on their own such as web servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers.
|
||||||
7. Avoid HTML entity escaping - use plain characters instead.
|
7. Avoid HTML entity escaping - use plain characters instead.
|
||||||
|
|
||||||
## Searching and Reading
|
## Searching and Reading
|
||||||
|
|||||||
@@ -9,16 +9,14 @@ use collections::IndexMap;
|
|||||||
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
|
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
|
||||||
use fs::{FakeFs, Fs};
|
use fs::{FakeFs, Fs};
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt as _, StreamExt,
|
StreamExt,
|
||||||
channel::{
|
channel::{
|
||||||
mpsc::{self, UnboundedReceiver},
|
mpsc::{self, UnboundedReceiver},
|
||||||
oneshot,
|
oneshot,
|
||||||
},
|
},
|
||||||
future::{Fuse, Shared},
|
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, AsyncApp, Entity, Task, TestAppContext, UpdateGlobal,
|
App, AppContext, Entity, Task, TestAppContext, UpdateGlobal, http_client::FakeHttpClient,
|
||||||
http_client::FakeHttpClient,
|
|
||||||
};
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
@@ -37,109 +35,12 @@ use schemars::JsonSchema;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use std::{
|
use std::{path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||||
path::Path,
|
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use util::path;
|
use util::path;
|
||||||
|
|
||||||
mod test_tools;
|
mod test_tools;
|
||||||
use test_tools::*;
|
use test_tools::*;
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) {
|
|
||||||
cx.update(|cx| {
|
|
||||||
let settings_store = SettingsStore::test(cx);
|
|
||||||
cx.set_global(settings_store);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FakeTerminalHandle {
|
|
||||||
killed: Arc<AtomicBool>,
|
|
||||||
wait_for_exit: Shared<Task<acp::TerminalExitStatus>>,
|
|
||||||
output: acp::TerminalOutputResponse,
|
|
||||||
id: acp::TerminalId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FakeTerminalHandle {
|
|
||||||
fn new_never_exits(cx: &mut App) -> Self {
|
|
||||||
let killed = Arc::new(AtomicBool::new(false));
|
|
||||||
|
|
||||||
let killed_for_task = killed.clone();
|
|
||||||
let wait_for_exit = cx
|
|
||||||
.spawn(async move |cx| {
|
|
||||||
loop {
|
|
||||||
if killed_for_task.load(Ordering::SeqCst) {
|
|
||||||
return acp::TerminalExitStatus::new();
|
|
||||||
}
|
|
||||||
cx.background_executor()
|
|
||||||
.timer(Duration::from_millis(1))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.shared();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
killed,
|
|
||||||
wait_for_exit,
|
|
||||||
output: acp::TerminalOutputResponse::new("partial output".to_string(), false),
|
|
||||||
id: acp::TerminalId::new("fake_terminal".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn was_killed(&self) -> bool {
|
|
||||||
self.killed.load(Ordering::SeqCst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::TerminalHandle for FakeTerminalHandle {
|
|
||||||
fn id(&self, _cx: &AsyncApp) -> Result<acp::TerminalId> {
|
|
||||||
Ok(self.id.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn current_output(&self, _cx: &AsyncApp) -> Result<acp::TerminalOutputResponse> {
|
|
||||||
Ok(self.output.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wait_for_exit(&self, _cx: &AsyncApp) -> Result<Shared<Task<acp::TerminalExitStatus>>> {
|
|
||||||
Ok(self.wait_for_exit.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kill(&self, _cx: &AsyncApp) -> Result<()> {
|
|
||||||
self.killed.store(true, Ordering::SeqCst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FakeThreadEnvironment {
|
|
||||||
handle: Rc<FakeTerminalHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::ThreadEnvironment for FakeThreadEnvironment {
|
|
||||||
fn create_terminal(
|
|
||||||
&self,
|
|
||||||
_command: String,
|
|
||||||
_cwd: Option<std::path::PathBuf>,
|
|
||||||
_output_byte_limit: Option<u64>,
|
|
||||||
_cx: &mut AsyncApp,
|
|
||||||
) -> Task<Result<Rc<dyn crate::TerminalHandle>>> {
|
|
||||||
Task::ready(Ok(self.handle.clone() as Rc<dyn crate::TerminalHandle>))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn always_allow_tools(cx: &mut TestAppContext) {
|
|
||||||
cx.update(|cx| {
|
|
||||||
let mut settings = agent_settings::AgentSettings::get_global(cx).clone();
|
|
||||||
settings.always_allow_tool_actions = true;
|
|
||||||
agent_settings::AgentSettings::override_global(settings, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_echo(cx: &mut TestAppContext) {
|
async fn test_echo(cx: &mut TestAppContext) {
|
||||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
||||||
@@ -170,120 +71,6 @@ async fn test_echo(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
|
assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_terminal_tool_timeout_kills_handle(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
always_allow_tools(cx);
|
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
|
||||||
let project = Project::test(fs, [], cx).await;
|
|
||||||
|
|
||||||
let handle = Rc::new(cx.update(|cx| FakeTerminalHandle::new_never_exits(cx)));
|
|
||||||
let environment = Rc::new(FakeThreadEnvironment {
|
|
||||||
handle: handle.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
|
||||||
let tool = Arc::new(crate::TerminalTool::new(project, environment));
|
|
||||||
let (event_stream, mut rx) = crate::ToolCallEventStream::test();
|
|
||||||
|
|
||||||
let task = cx.update(|cx| {
|
|
||||||
tool.run(
|
|
||||||
crate::TerminalToolInput {
|
|
||||||
command: "sleep 1000".to_string(),
|
|
||||||
cd: ".".to_string(),
|
|
||||||
timeout_ms: Some(5),
|
|
||||||
},
|
|
||||||
event_stream,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let update = rx.expect_update_fields().await;
|
|
||||||
assert!(
|
|
||||||
update.content.iter().any(|blocks| {
|
|
||||||
blocks
|
|
||||||
.iter()
|
|
||||||
.any(|c| matches!(c, acp::ToolCallContent::Terminal(_)))
|
|
||||||
}),
|
|
||||||
"expected tool call update to include terminal content"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut task_future: Pin<Box<Fuse<Task<Result<String>>>>> = Box::pin(task.fuse());
|
|
||||||
|
|
||||||
let deadline = std::time::Instant::now() + Duration::from_millis(500);
|
|
||||||
loop {
|
|
||||||
if let Some(result) = task_future.as_mut().now_or_never() {
|
|
||||||
let result = result.expect("terminal tool task should complete");
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
handle.was_killed(),
|
|
||||||
"expected terminal handle to be killed on timeout"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
result.contains("partial output"),
|
|
||||||
"expected result to include terminal output, got: {result}"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if std::time::Instant::now() >= deadline {
|
|
||||||
panic!("timed out waiting for terminal tool task to complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
|
||||||
cx.background_executor.timer(Duration::from_millis(1)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
#[ignore]
|
|
||||||
async fn test_terminal_tool_without_timeout_does_not_kill_handle(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
always_allow_tools(cx);
|
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
|
||||||
let project = Project::test(fs, [], cx).await;
|
|
||||||
|
|
||||||
let handle = Rc::new(cx.update(|cx| FakeTerminalHandle::new_never_exits(cx)));
|
|
||||||
let environment = Rc::new(FakeThreadEnvironment {
|
|
||||||
handle: handle.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
|
||||||
let tool = Arc::new(crate::TerminalTool::new(project, environment));
|
|
||||||
let (event_stream, mut rx) = crate::ToolCallEventStream::test();
|
|
||||||
|
|
||||||
let _task = cx.update(|cx| {
|
|
||||||
tool.run(
|
|
||||||
crate::TerminalToolInput {
|
|
||||||
command: "sleep 1000".to_string(),
|
|
||||||
cd: ".".to_string(),
|
|
||||||
timeout_ms: None,
|
|
||||||
},
|
|
||||||
event_stream,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let update = rx.expect_update_fields().await;
|
|
||||||
assert!(
|
|
||||||
update.content.iter().any(|blocks| {
|
|
||||||
blocks
|
|
||||||
.iter()
|
|
||||||
.any(|c| matches!(c, acp::ToolCallContent::Terminal(_)))
|
|
||||||
}),
|
|
||||||
"expected tool call update to include terminal content"
|
|
||||||
);
|
|
||||||
|
|
||||||
smol::Timer::after(Duration::from_millis(25)).await;
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!handle.was_killed(),
|
|
||||||
"did not expect terminal handle to be killed without a timeout"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_thinking(cx: &mut TestAppContext) {
|
async fn test_thinking(cx: &mut TestAppContext) {
|
||||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
||||||
@@ -2809,181 +2596,3 @@ fn setup_context_server(
|
|||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
mcp_tool_calls_rx
|
mcp_tool_calls_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_tokens_before_message(cx: &mut TestAppContext) {
|
|
||||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
|
||||||
let fake_model = model.as_fake();
|
|
||||||
|
|
||||||
// First message
|
|
||||||
let message_1_id = UserMessageId::new();
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| {
|
|
||||||
thread.send(message_1_id.clone(), ["First message"], cx)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// Before any response, tokens_before_message should return None for first message
|
|
||||||
thread.read_with(cx, |thread, _| {
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_1_id),
|
|
||||||
None,
|
|
||||||
"First message should have no tokens before it"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Complete first message with usage
|
|
||||||
fake_model.send_last_completion_stream_text_chunk("Response 1");
|
|
||||||
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate(
|
|
||||||
language_model::TokenUsage {
|
|
||||||
input_tokens: 100,
|
|
||||||
output_tokens: 50,
|
|
||||||
cache_creation_input_tokens: 0,
|
|
||||||
cache_read_input_tokens: 0,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
fake_model.end_last_completion_stream();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// First message still has no tokens before it
|
|
||||||
thread.read_with(cx, |thread, _| {
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_1_id),
|
|
||||||
None,
|
|
||||||
"First message should still have no tokens before it after response"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Second message
|
|
||||||
let message_2_id = UserMessageId::new();
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| {
|
|
||||||
thread.send(message_2_id.clone(), ["Second message"], cx)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// Second message should have first message's input tokens before it
|
|
||||||
thread.read_with(cx, |thread, _| {
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_2_id),
|
|
||||||
Some(100),
|
|
||||||
"Second message should have 100 tokens before it (from first request)"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Complete second message
|
|
||||||
fake_model.send_last_completion_stream_text_chunk("Response 2");
|
|
||||||
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate(
|
|
||||||
language_model::TokenUsage {
|
|
||||||
input_tokens: 250, // Total for this request (includes previous context)
|
|
||||||
output_tokens: 75,
|
|
||||||
cache_creation_input_tokens: 0,
|
|
||||||
cache_read_input_tokens: 0,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
fake_model.end_last_completion_stream();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// Third message
|
|
||||||
let message_3_id = UserMessageId::new();
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| {
|
|
||||||
thread.send(message_3_id.clone(), ["Third message"], cx)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// Third message should have second message's input tokens (250) before it
|
|
||||||
thread.read_with(cx, |thread, _| {
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_3_id),
|
|
||||||
Some(250),
|
|
||||||
"Third message should have 250 tokens before it (from second request)"
|
|
||||||
);
|
|
||||||
// Second message should still have 100
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_2_id),
|
|
||||||
Some(100),
|
|
||||||
"Second message should still have 100 tokens before it"
|
|
||||||
);
|
|
||||||
// First message still has none
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_1_id),
|
|
||||||
None,
|
|
||||||
"First message should still have no tokens before it"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_tokens_before_message_after_truncate(cx: &mut TestAppContext) {
|
|
||||||
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
|
|
||||||
let fake_model = model.as_fake();
|
|
||||||
|
|
||||||
// Set up three messages with responses
|
|
||||||
let message_1_id = UserMessageId::new();
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| {
|
|
||||||
thread.send(message_1_id.clone(), ["Message 1"], cx)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
cx.run_until_parked();
|
|
||||||
fake_model.send_last_completion_stream_text_chunk("Response 1");
|
|
||||||
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate(
|
|
||||||
language_model::TokenUsage {
|
|
||||||
input_tokens: 100,
|
|
||||||
output_tokens: 50,
|
|
||||||
cache_creation_input_tokens: 0,
|
|
||||||
cache_read_input_tokens: 0,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
fake_model.end_last_completion_stream();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
let message_2_id = UserMessageId::new();
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| {
|
|
||||||
thread.send(message_2_id.clone(), ["Message 2"], cx)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
cx.run_until_parked();
|
|
||||||
fake_model.send_last_completion_stream_text_chunk("Response 2");
|
|
||||||
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::UsageUpdate(
|
|
||||||
language_model::TokenUsage {
|
|
||||||
input_tokens: 250,
|
|
||||||
output_tokens: 75,
|
|
||||||
cache_creation_input_tokens: 0,
|
|
||||||
cache_read_input_tokens: 0,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
fake_model.end_last_completion_stream();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// Verify initial state
|
|
||||||
thread.read_with(cx, |thread, _| {
|
|
||||||
assert_eq!(thread.tokens_before_message(&message_2_id), Some(100));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Truncate at message 2 (removes message 2 and everything after)
|
|
||||||
thread
|
|
||||||
.update(cx, |thread, cx| thread.truncate(message_2_id.clone(), cx))
|
|
||||||
.unwrap();
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
// After truncation, message_2_id no longer exists, so lookup should return None
|
|
||||||
thread.read_with(cx, |thread, _| {
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_2_id),
|
|
||||||
None,
|
|
||||||
"After truncation, message 2 no longer exists"
|
|
||||||
);
|
|
||||||
// Message 1 still exists but has no tokens before it
|
|
||||||
assert_eq!(
|
|
||||||
thread.tokens_before_message(&message_1_id),
|
|
||||||
None,
|
|
||||||
"First message still has no tokens before it"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ use crate::{
|
|||||||
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
|
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
|
||||||
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
|
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
|
||||||
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
|
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
|
||||||
RestoreFileFromDiskTool, SaveFileTool, SystemPromptTemplate, Template, Templates, TerminalTool,
|
SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,
|
||||||
ThinkingTool, WebSearchTool,
|
|
||||||
};
|
};
|
||||||
use acp_thread::{MentionUri, UserMessageId};
|
use acp_thread::{MentionUri, UserMessageId};
|
||||||
use action_log::ActionLog;
|
use action_log::ActionLog;
|
||||||
@@ -108,13 +107,7 @@ impl Message {
|
|||||||
|
|
||||||
pub fn to_request(&self) -> Vec<LanguageModelRequestMessage> {
|
pub fn to_request(&self) -> Vec<LanguageModelRequestMessage> {
|
||||||
match self {
|
match self {
|
||||||
Message::User(message) => {
|
Message::User(message) => vec![message.to_request()],
|
||||||
if message.content.is_empty() {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
vec![message.to_request()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::Agent(message) => message.to_request(),
|
Message::Agent(message) => message.to_request(),
|
||||||
Message::Resume => vec![LanguageModelRequestMessage {
|
Message::Resume => vec![LanguageModelRequestMessage {
|
||||||
role: Role::User,
|
role: Role::User,
|
||||||
@@ -537,7 +530,6 @@ pub trait TerminalHandle {
|
|||||||
fn id(&self, cx: &AsyncApp) -> Result<acp::TerminalId>;
|
fn id(&self, cx: &AsyncApp) -> Result<acp::TerminalId>;
|
||||||
fn current_output(&self, cx: &AsyncApp) -> Result<acp::TerminalOutputResponse>;
|
fn current_output(&self, cx: &AsyncApp) -> Result<acp::TerminalOutputResponse>;
|
||||||
fn wait_for_exit(&self, cx: &AsyncApp) -> Result<Shared<Task<acp::TerminalExitStatus>>>;
|
fn wait_for_exit(&self, cx: &AsyncApp) -> Result<Shared<Task<acp::TerminalExitStatus>>>;
|
||||||
fn kill(&self, cx: &AsyncApp) -> Result<()>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ThreadEnvironment {
|
pub trait ThreadEnvironment {
|
||||||
@@ -1009,8 +1001,6 @@ impl Thread {
|
|||||||
self.project.clone(),
|
self.project.clone(),
|
||||||
self.action_log.clone(),
|
self.action_log.clone(),
|
||||||
));
|
));
|
||||||
self.add_tool(SaveFileTool::new(self.project.clone()));
|
|
||||||
self.add_tool(RestoreFileFromDiskTool::new(self.project.clone()));
|
|
||||||
self.add_tool(TerminalTool::new(self.project.clone(), environment));
|
self.add_tool(TerminalTool::new(self.project.clone(), environment));
|
||||||
self.add_tool(ThinkingTool);
|
self.add_tool(ThinkingTool);
|
||||||
self.add_tool(WebSearchTool);
|
self.add_tool(WebSearchTool);
|
||||||
@@ -1095,28 +1085,6 @@ impl Thread {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the total input token count as of the message before the given message.
|
|
||||||
///
|
|
||||||
/// Returns `None` if:
|
|
||||||
/// - `target_id` is the first message (no previous message)
|
|
||||||
/// - The previous message hasn't received a response yet (no usage data)
|
|
||||||
/// - `target_id` is not found in the messages
|
|
||||||
pub fn tokens_before_message(&self, target_id: &UserMessageId) -> Option<u64> {
|
|
||||||
let mut previous_user_message_id: Option<&UserMessageId> = None;
|
|
||||||
|
|
||||||
for message in &self.messages {
|
|
||||||
if let Message::User(user_msg) = message {
|
|
||||||
if &user_msg.id == target_id {
|
|
||||||
let prev_id = previous_user_message_id?;
|
|
||||||
let usage = self.request_token_usage.get(prev_id)?;
|
|
||||||
return Some(usage.input_tokens);
|
|
||||||
}
|
|
||||||
previous_user_message_id = Some(&user_msg.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up the active profile and resolve its preferred model if one is configured.
|
/// Look up the active profile and resolve its preferred model if one is configured.
|
||||||
fn resolve_profile_model(
|
fn resolve_profile_model(
|
||||||
profile_id: &AgentProfileId,
|
profile_id: &AgentProfileId,
|
||||||
@@ -1169,6 +1137,11 @@ impl Thread {
|
|||||||
where
|
where
|
||||||
T: Into<UserMessageContent>,
|
T: Into<UserMessageContent>,
|
||||||
{
|
{
|
||||||
|
let model = self.model().context("No language model configured")?;
|
||||||
|
|
||||||
|
log::info!("Thread::send called with model: {}", model.name().0);
|
||||||
|
self.advance_prompt_id();
|
||||||
|
|
||||||
let content = content.into_iter().map(Into::into).collect::<Vec<_>>();
|
let content = content.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||||
log::debug!("Thread::send content: {:?}", content);
|
log::debug!("Thread::send content: {:?}", content);
|
||||||
|
|
||||||
@@ -1176,59 +1149,10 @@ impl Thread {
|
|||||||
.push(Message::User(UserMessage { id, content }));
|
.push(Message::User(UserMessage { id, content }));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
self.send_existing(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_existing(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Result<mpsc::UnboundedReceiver<Result<ThreadEvent>>> {
|
|
||||||
let model = self.model().context("No language model configured")?;
|
|
||||||
|
|
||||||
log::info!("Thread::send called with model: {}", model.name().0);
|
|
||||||
self.advance_prompt_id();
|
|
||||||
|
|
||||||
log::debug!("Total messages in thread: {}", self.messages.len());
|
log::debug!("Total messages in thread: {}", self.messages.len());
|
||||||
self.run_turn(cx)
|
self.run_turn(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_acp_user_block(
|
|
||||||
&mut self,
|
|
||||||
id: UserMessageId,
|
|
||||||
blocks: impl IntoIterator<Item = acp::ContentBlock>,
|
|
||||||
path_style: PathStyle,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let content = blocks
|
|
||||||
.into_iter()
|
|
||||||
.map(|block| UserMessageContent::from_content_block(block, path_style))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
self.messages
|
|
||||||
.push(Message::User(UserMessage { id, content }));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_acp_agent_block(&mut self, block: acp::ContentBlock, cx: &mut Context<Self>) {
|
|
||||||
let text = match block {
|
|
||||||
acp::ContentBlock::Text(text_content) => text_content.text,
|
|
||||||
acp::ContentBlock::Image(_) => "[image]".to_string(),
|
|
||||||
acp::ContentBlock::Audio(_) => "[audio]".to_string(),
|
|
||||||
acp::ContentBlock::ResourceLink(resource_link) => resource_link.uri,
|
|
||||||
acp::ContentBlock::Resource(resource) => match resource.resource {
|
|
||||||
acp::EmbeddedResourceResource::TextResourceContents(resource) => resource.uri,
|
|
||||||
acp::EmbeddedResourceResource::BlobResourceContents(resource) => resource.uri,
|
|
||||||
_ => "[resource]".to_string(),
|
|
||||||
},
|
|
||||||
_ => "[unknown]".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.messages.push(Message::Agent(AgentMessage {
|
|
||||||
content: vec![AgentMessageContent::Text(text)],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "eval")]
|
#[cfg(feature = "eval")]
|
||||||
pub fn proceed(
|
pub fn proceed(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -1725,10 +1649,6 @@ impl Thread {
|
|||||||
self.pending_summary_generation.is_some()
|
self.pending_summary_generation.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_generating_title(&self) -> bool {
|
|
||||||
self.pending_title_generation.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn summary(&mut self, cx: &mut Context<Self>) -> Shared<Task<Option<SharedString>>> {
|
pub fn summary(&mut self, cx: &mut Context<Self>) -> Shared<Task<Option<SharedString>>> {
|
||||||
if let Some(summary) = self.summary.as_ref() {
|
if let Some(summary) = self.summary.as_ref() {
|
||||||
return Task::ready(Some(summary.clone())).shared();
|
return Task::ready(Some(summary.clone())).shared();
|
||||||
@@ -1796,7 +1716,7 @@ impl Thread {
|
|||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_title(&mut self, cx: &mut Context<Self>) {
|
fn generate_title(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(model) = self.summarization_model.clone() else {
|
let Some(model) = self.summarization_model.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -2045,12 +1965,6 @@ impl Thread {
|
|||||||
self.running_turn.as_ref()?.tools.get(name).cloned()
|
self.running_turn.as_ref()?.tools.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_tool(&self, name: &str) -> bool {
|
|
||||||
self.running_turn
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|turn| turn.tools.contains_key(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_request_messages(
|
fn build_request_messages(
|
||||||
&self,
|
&self,
|
||||||
available_tools: Vec<SharedString>,
|
available_tools: Vec<SharedString>,
|
||||||
@@ -2744,6 +2658,7 @@ impl From<UserMessageContent> for acp::ContentBlock {
|
|||||||
fn convert_image(image_content: acp::ImageContent) -> LanguageModelImage {
|
fn convert_image(image_content: acp::ImageContent) -> LanguageModelImage {
|
||||||
LanguageModelImage {
|
LanguageModelImage {
|
||||||
source: image_content.data.into(),
|
source: image_content.data.into(),
|
||||||
size: None,
|
// TODO: make this optional?
|
||||||
|
size: gpui::Size::new(0.into(), 0.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod create_directory_tool;
|
|||||||
mod delete_path_tool;
|
mod delete_path_tool;
|
||||||
mod diagnostics_tool;
|
mod diagnostics_tool;
|
||||||
mod edit_file_tool;
|
mod edit_file_tool;
|
||||||
|
|
||||||
mod fetch_tool;
|
mod fetch_tool;
|
||||||
mod find_path_tool;
|
mod find_path_tool;
|
||||||
mod grep_tool;
|
mod grep_tool;
|
||||||
@@ -12,8 +13,6 @@ mod move_path_tool;
|
|||||||
mod now_tool;
|
mod now_tool;
|
||||||
mod open_tool;
|
mod open_tool;
|
||||||
mod read_file_tool;
|
mod read_file_tool;
|
||||||
mod restore_file_from_disk_tool;
|
|
||||||
mod save_file_tool;
|
|
||||||
|
|
||||||
mod terminal_tool;
|
mod terminal_tool;
|
||||||
mod thinking_tool;
|
mod thinking_tool;
|
||||||
@@ -28,6 +27,7 @@ pub use create_directory_tool::*;
|
|||||||
pub use delete_path_tool::*;
|
pub use delete_path_tool::*;
|
||||||
pub use diagnostics_tool::*;
|
pub use diagnostics_tool::*;
|
||||||
pub use edit_file_tool::*;
|
pub use edit_file_tool::*;
|
||||||
|
|
||||||
pub use fetch_tool::*;
|
pub use fetch_tool::*;
|
||||||
pub use find_path_tool::*;
|
pub use find_path_tool::*;
|
||||||
pub use grep_tool::*;
|
pub use grep_tool::*;
|
||||||
@@ -36,8 +36,6 @@ pub use move_path_tool::*;
|
|||||||
pub use now_tool::*;
|
pub use now_tool::*;
|
||||||
pub use open_tool::*;
|
pub use open_tool::*;
|
||||||
pub use read_file_tool::*;
|
pub use read_file_tool::*;
|
||||||
pub use restore_file_from_disk_tool::*;
|
|
||||||
pub use save_file_tool::*;
|
|
||||||
|
|
||||||
pub use terminal_tool::*;
|
pub use terminal_tool::*;
|
||||||
pub use thinking_tool::*;
|
pub use thinking_tool::*;
|
||||||
@@ -94,8 +92,6 @@ tools! {
|
|||||||
NowTool,
|
NowTool,
|
||||||
OpenTool,
|
OpenTool,
|
||||||
ReadFileTool,
|
ReadFileTool,
|
||||||
RestoreFileFromDiskTool,
|
|
||||||
SaveFileTool,
|
|
||||||
TerminalTool,
|
TerminalTool,
|
||||||
ThinkingTool,
|
ThinkingTool,
|
||||||
WebSearchTool,
|
WebSearchTool,
|
||||||
|
|||||||
@@ -2,24 +2,12 @@ use crate::{AgentToolOutput, AnyAgentTool, ToolCallEventStream};
|
|||||||
use agent_client_protocol::ToolKind;
|
use agent_client_protocol::ToolKind;
|
||||||
use anyhow::{Result, anyhow, bail};
|
use anyhow::{Result, anyhow, bail};
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use context_server::{ContextServerId, client::NotificationSubscription};
|
use context_server::ContextServerId;
|
||||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
|
use gpui::{App, Context, Entity, SharedString, Task};
|
||||||
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
use project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub struct ContextServerPrompt {
|
|
||||||
pub server_id: ContextServerId,
|
|
||||||
pub prompt: context_server::types::Prompt,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ContextServerRegistryEvent {
|
|
||||||
ToolsChanged,
|
|
||||||
PromptsChanged,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<ContextServerRegistryEvent> for ContextServerRegistry {}
|
|
||||||
|
|
||||||
pub struct ContextServerRegistry {
|
pub struct ContextServerRegistry {
|
||||||
server_store: Entity<ContextServerStore>,
|
server_store: Entity<ContextServerStore>,
|
||||||
registered_servers: HashMap<ContextServerId, RegisteredContextServer>,
|
registered_servers: HashMap<ContextServerId, RegisteredContextServer>,
|
||||||
@@ -28,10 +16,7 @@ pub struct ContextServerRegistry {
|
|||||||
|
|
||||||
struct RegisteredContextServer {
|
struct RegisteredContextServer {
|
||||||
tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
|
tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
|
||||||
prompts: BTreeMap<SharedString, ContextServerPrompt>,
|
|
||||||
load_tools: Task<Result<()>>,
|
load_tools: Task<Result<()>>,
|
||||||
load_prompts: Task<Result<()>>,
|
|
||||||
_tools_updated_subscription: Option<NotificationSubscription>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextServerRegistry {
|
impl ContextServerRegistry {
|
||||||
@@ -43,7 +28,6 @@ impl ContextServerRegistry {
|
|||||||
};
|
};
|
||||||
for server in server_store.read(cx).running_servers() {
|
for server in server_store.read(cx).running_servers() {
|
||||||
this.reload_tools_for_server(server.id(), cx);
|
this.reload_tools_for_server(server.id(), cx);
|
||||||
this.reload_prompts_for_server(server.id(), cx);
|
|
||||||
}
|
}
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
@@ -72,88 +56,6 @@ impl ContextServerRegistry {
|
|||||||
.map(|(id, server)| (id, &server.tools))
|
.map(|(id, server)| (id, &server.tools))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prompts(&self) -> impl Iterator<Item = &ContextServerPrompt> {
|
|
||||||
self.registered_servers
|
|
||||||
.values()
|
|
||||||
.flat_map(|server| server.prompts.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_prompt(
|
|
||||||
&self,
|
|
||||||
server_id: Option<&ContextServerId>,
|
|
||||||
name: &str,
|
|
||||||
) -> Option<&ContextServerPrompt> {
|
|
||||||
if let Some(server_id) = server_id {
|
|
||||||
self.registered_servers
|
|
||||||
.get(server_id)
|
|
||||||
.and_then(|server| server.prompts.get(name))
|
|
||||||
} else {
|
|
||||||
self.registered_servers
|
|
||||||
.values()
|
|
||||||
.find_map(|server| server.prompts.get(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn server_store(&self) -> &Entity<ContextServerStore> {
|
|
||||||
&self.server_store
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_or_register_server(
|
|
||||||
&mut self,
|
|
||||||
server_id: &ContextServerId,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> &mut RegisteredContextServer {
|
|
||||||
self.registered_servers
|
|
||||||
.entry(server_id.clone())
|
|
||||||
.or_insert_with(|| Self::init_registered_server(server_id, &self.server_store, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_registered_server(
|
|
||||||
server_id: &ContextServerId,
|
|
||||||
server_store: &Entity<ContextServerStore>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> RegisteredContextServer {
|
|
||||||
let tools_updated_subscription = server_store
|
|
||||||
.read(cx)
|
|
||||||
.get_running_server(server_id)
|
|
||||||
.and_then(|server| {
|
|
||||||
let client = server.client()?;
|
|
||||||
|
|
||||||
if !client.capable(context_server::protocol::ServerCapability::Tools) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let server_id = server.id();
|
|
||||||
let this = cx.entity().downgrade();
|
|
||||||
|
|
||||||
Some(client.on_notification(
|
|
||||||
"notifications/tools/list_changed",
|
|
||||||
Box::new(move |_params, cx: AsyncApp| {
|
|
||||||
let server_id = server_id.clone();
|
|
||||||
let this = this.clone();
|
|
||||||
cx.spawn(async move |cx| {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
log::info!(
|
|
||||||
"Received tools/list_changed notification for server {}",
|
|
||||||
server_id
|
|
||||||
);
|
|
||||||
this.reload_tools_for_server(server_id, cx);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
});
|
|
||||||
|
|
||||||
RegisteredContextServer {
|
|
||||||
tools: BTreeMap::default(),
|
|
||||||
prompts: BTreeMap::default(),
|
|
||||||
load_tools: Task::ready(Ok(())),
|
|
||||||
load_prompts: Task::ready(Ok(())),
|
|
||||||
_tools_updated_subscription: tools_updated_subscription,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload_tools_for_server(&mut self, server_id: ContextServerId, cx: &mut Context<Self>) {
|
fn reload_tools_for_server(&mut self, server_id: ContextServerId, cx: &mut Context<Self>) {
|
||||||
let Some(server) = self.server_store.read(cx).get_running_server(&server_id) else {
|
let Some(server) = self.server_store.read(cx).get_running_server(&server_id) else {
|
||||||
return;
|
return;
|
||||||
@@ -161,12 +63,17 @@ impl ContextServerRegistry {
|
|||||||
let Some(client) = server.client() else {
|
let Some(client) = server.client() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !client.capable(context_server::protocol::ServerCapability::Tools) {
|
if !client.capable(context_server::protocol::ServerCapability::Tools) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let registered_server = self.get_or_register_server(&server_id, cx);
|
let registered_server =
|
||||||
|
self.registered_servers
|
||||||
|
.entry(server_id.clone())
|
||||||
|
.or_insert(RegisteredContextServer {
|
||||||
|
tools: BTreeMap::default(),
|
||||||
|
load_tools: Task::ready(Ok(())),
|
||||||
|
});
|
||||||
registered_server.load_tools = cx.spawn(async move |this, cx| {
|
registered_server.load_tools = cx.spawn(async move |this, cx| {
|
||||||
let response = client
|
let response = client
|
||||||
.request::<context_server::types::requests::ListTools>(())
|
.request::<context_server::types::requests::ListTools>(())
|
||||||
@@ -187,49 +94,6 @@ impl ContextServerRegistry {
|
|||||||
));
|
));
|
||||||
registered_server.tools.insert(tool.name(), tool);
|
registered_server.tools.insert(tool.name(), tool);
|
||||||
}
|
}
|
||||||
cx.emit(ContextServerRegistryEvent::ToolsChanged);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload_prompts_for_server(&mut self, server_id: ContextServerId, cx: &mut Context<Self>) {
|
|
||||||
let Some(server) = self.server_store.read(cx).get_running_server(&server_id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(client) = server.client() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if !client.capable(context_server::protocol::ServerCapability::Prompts) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let registered_server = self.get_or_register_server(&server_id, cx);
|
|
||||||
|
|
||||||
registered_server.load_prompts = cx.spawn(async move |this, cx| {
|
|
||||||
let response = client
|
|
||||||
.request::<context_server::types::requests::PromptsList>(())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
let Some(registered_server) = this.registered_servers.get_mut(&server_id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
registered_server.prompts.clear();
|
|
||||||
if let Some(response) = response.log_err() {
|
|
||||||
for prompt in response.prompts {
|
|
||||||
let name: SharedString = prompt.name.clone().into();
|
|
||||||
registered_server.prompts.insert(
|
|
||||||
name,
|
|
||||||
ContextServerPrompt {
|
|
||||||
server_id: server_id.clone(),
|
|
||||||
prompt,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
cx.emit(ContextServerRegistryEvent::PromptsChanged);
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -248,17 +112,9 @@ impl ContextServerRegistry {
|
|||||||
ContextServerStatus::Starting => {}
|
ContextServerStatus::Starting => {}
|
||||||
ContextServerStatus::Running => {
|
ContextServerStatus::Running => {
|
||||||
self.reload_tools_for_server(server_id.clone(), cx);
|
self.reload_tools_for_server(server_id.clone(), cx);
|
||||||
self.reload_prompts_for_server(server_id.clone(), cx);
|
|
||||||
}
|
}
|
||||||
ContextServerStatus::Stopped | ContextServerStatus::Error(_) => {
|
ContextServerStatus::Stopped | ContextServerStatus::Error(_) => {
|
||||||
if let Some(registered_server) = self.registered_servers.remove(server_id) {
|
self.registered_servers.remove(server_id);
|
||||||
if !registered_server.tools.is_empty() {
|
|
||||||
cx.emit(ContextServerRegistryEvent::ToolsChanged);
|
|
||||||
}
|
|
||||||
if !registered_server.prompts.is_empty() {
|
|
||||||
cx.emit(ContextServerRegistryEvent::PromptsChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,39 +251,3 @@ impl AnyAgentTool for ContextServerTool {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_prompt(
|
|
||||||
server_store: &Entity<ContextServerStore>,
|
|
||||||
server_id: &ContextServerId,
|
|
||||||
prompt_name: &str,
|
|
||||||
arguments: HashMap<String, String>,
|
|
||||||
cx: &mut AsyncApp,
|
|
||||||
) -> Task<Result<context_server::types::PromptsGetResponse>> {
|
|
||||||
let server = match cx.update(|cx| server_store.read(cx).get_running_server(server_id)) {
|
|
||||||
Ok(server) => server,
|
|
||||||
Err(error) => return Task::ready(Err(error)),
|
|
||||||
};
|
|
||||||
let Some(server) = server else {
|
|
||||||
return Task::ready(Err(anyhow::anyhow!("Context server not found")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(protocol) = server.client() else {
|
|
||||||
return Task::ready(Err(anyhow::anyhow!("Context server not initialized")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let prompt_name = prompt_name.to_string();
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let response = protocol
|
|
||||||
.request::<context_server::types::requests::PromptsGet>(
|
|
||||||
context_server::types::PromptsGetParams {
|
|
||||||
name: prompt_name,
|
|
||||||
arguments: (!arguments.is_empty()).then(|| arguments),
|
|
||||||
meta: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -306,39 +306,20 @@ impl AgentTool for EditFileTool {
|
|||||||
|
|
||||||
// Check if the file has been modified since the agent last read it
|
// Check if the file has been modified since the agent last read it
|
||||||
if let Some(abs_path) = abs_path.as_ref() {
|
if let Some(abs_path) = abs_path.as_ref() {
|
||||||
let (last_read_mtime, current_mtime, is_dirty, has_save_tool, has_restore_tool) = self.thread.update(cx, |thread, cx| {
|
let (last_read_mtime, current_mtime, is_dirty) = self.thread.update(cx, |thread, cx| {
|
||||||
let last_read = thread.file_read_times.get(abs_path).copied();
|
let last_read = thread.file_read_times.get(abs_path).copied();
|
||||||
let current = buffer.read(cx).file().and_then(|file| file.disk_state().mtime());
|
let current = buffer.read(cx).file().and_then(|file| file.disk_state().mtime());
|
||||||
let dirty = buffer.read(cx).is_dirty();
|
let dirty = buffer.read(cx).is_dirty();
|
||||||
let has_save = thread.has_tool("save_file");
|
(last_read, current, dirty)
|
||||||
let has_restore = thread.has_tool("restore_file_from_disk");
|
|
||||||
(last_read, current, dirty, has_save, has_restore)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Check for unsaved changes first - these indicate modifications we don't know about
|
// Check for unsaved changes first - these indicate modifications we don't know about
|
||||||
if is_dirty {
|
if is_dirty {
|
||||||
let message = match (has_save_tool, has_restore_tool) {
|
anyhow::bail!(
|
||||||
(true, true) => {
|
"This file cannot be written to because it has unsaved changes. \
|
||||||
"This file has unsaved changes. Ask the user whether they want to keep or discard those changes. \
|
Please end the current conversation immediately by telling the user you want to write to this file (mention its path explicitly) but you can't write to it because it has unsaved changes. \
|
||||||
If they want to keep them, ask for confirmation then use the save_file tool to save the file, then retry this edit. \
|
Ask the user to save that buffer's changes and to inform you when it's ok to proceed."
|
||||||
If they want to discard them, ask for confirmation then use the restore_file_from_disk tool to restore the on-disk contents, then retry this edit."
|
);
|
||||||
}
|
|
||||||
(true, false) => {
|
|
||||||
"This file has unsaved changes. Ask the user whether they want to keep or discard those changes. \
|
|
||||||
If they want to keep them, ask for confirmation then use the save_file tool to save the file, then retry this edit. \
|
|
||||||
If they want to discard them, ask the user to manually revert the file, then inform you when it's ok to proceed."
|
|
||||||
}
|
|
||||||
(false, true) => {
|
|
||||||
"This file has unsaved changes. Ask the user whether they want to keep or discard those changes. \
|
|
||||||
If they want to keep them, ask the user to manually save the file, then inform you when it's ok to proceed. \
|
|
||||||
If they want to discard them, ask for confirmation then use the restore_file_from_disk tool to restore the on-disk contents, then retry this edit."
|
|
||||||
}
|
|
||||||
(false, false) => {
|
|
||||||
"This file has unsaved changes. Ask the user whether they want to keep or discard those changes, \
|
|
||||||
then ask them to save or revert the file manually and inform you when it's ok to proceed."
|
|
||||||
}
|
|
||||||
};
|
|
||||||
anyhow::bail!("{}", message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the file was modified on disk since we last read it
|
// Check if the file was modified on disk since we last read it
|
||||||
@@ -2221,21 +2202,9 @@ mod tests {
|
|||||||
assert!(result.is_err(), "Edit should fail when buffer is dirty");
|
assert!(result.is_err(), "Edit should fail when buffer is dirty");
|
||||||
let error_msg = result.unwrap_err().to_string();
|
let error_msg = result.unwrap_err().to_string();
|
||||||
assert!(
|
assert!(
|
||||||
error_msg.contains("This file has unsaved changes."),
|
error_msg.contains("cannot be written to because it has unsaved changes"),
|
||||||
"Error should mention unsaved changes, got: {}",
|
"Error should mention unsaved changes, got: {}",
|
||||||
error_msg
|
error_msg
|
||||||
);
|
);
|
||||||
assert!(
|
|
||||||
error_msg.contains("keep or discard"),
|
|
||||||
"Error should ask whether to keep or discard changes, got: {}",
|
|
||||||
error_msg
|
|
||||||
);
|
|
||||||
// Since save_file and restore_file_from_disk tools aren't added to the thread,
|
|
||||||
// the error message should ask the user to manually save or revert
|
|
||||||
assert!(
|
|
||||||
error_msg.contains("save or revert the file manually"),
|
|
||||||
"Error should ask user to manually save or revert when tools aren't available, got: {}",
|
|
||||||
error_msg
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,352 +0,0 @@
|
|||||||
use agent_client_protocol as acp;
|
|
||||||
use anyhow::Result;
|
|
||||||
use collections::FxHashSet;
|
|
||||||
use gpui::{App, Entity, SharedString, Task};
|
|
||||||
use language::Buffer;
|
|
||||||
use project::Project;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{AgentTool, ToolCallEventStream};
|
|
||||||
|
|
||||||
/// Discards unsaved changes in open buffers by reloading file contents from disk.
|
|
||||||
///
|
|
||||||
/// Use this tool when:
|
|
||||||
/// - You attempted to edit files but they have unsaved changes the user does not want to keep.
|
|
||||||
/// - You want to reset files to the on-disk state before retrying an edit.
|
|
||||||
///
|
|
||||||
/// Only use this tool after asking the user for permission, because it will discard unsaved changes.
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct RestoreFileFromDiskToolInput {
|
|
||||||
/// The paths of the files to restore from disk.
|
|
||||||
pub paths: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RestoreFileFromDiskTool {
|
|
||||||
project: Entity<Project>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RestoreFileFromDiskTool {
|
|
||||||
pub fn new(project: Entity<Project>) -> Self {
|
|
||||||
Self { project }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AgentTool for RestoreFileFromDiskTool {
|
|
||||||
type Input = RestoreFileFromDiskToolInput;
|
|
||||||
type Output = String;
|
|
||||||
|
|
||||||
fn name() -> &'static str {
|
|
||||||
"restore_file_from_disk"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kind() -> acp::ToolKind {
|
|
||||||
acp::ToolKind::Other
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initial_title(
|
|
||||||
&self,
|
|
||||||
input: Result<Self::Input, serde_json::Value>,
|
|
||||||
_cx: &mut App,
|
|
||||||
) -> SharedString {
|
|
||||||
match input {
|
|
||||||
Ok(input) if input.paths.len() == 1 => "Restore file from disk".into(),
|
|
||||||
Ok(input) => format!("Restore {} files from disk", input.paths.len()).into(),
|
|
||||||
Err(_) => "Restore files from disk".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
self: Arc<Self>,
|
|
||||||
input: Self::Input,
|
|
||||||
_event_stream: ToolCallEventStream,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<String>> {
|
|
||||||
let project = self.project.clone();
|
|
||||||
let input_paths = input.paths;
|
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
|
||||||
let mut buffers_to_reload: FxHashSet<Entity<Buffer>> = FxHashSet::default();
|
|
||||||
|
|
||||||
let mut restored_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
let mut clean_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
let mut not_found_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
let mut open_errors: Vec<(PathBuf, String)> = Vec::new();
|
|
||||||
let mut dirty_check_errors: Vec<(PathBuf, String)> = Vec::new();
|
|
||||||
let mut reload_errors: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
for path in input_paths {
|
|
||||||
let project_path =
|
|
||||||
project.read_with(cx, |project, cx| project.find_project_path(&path, cx));
|
|
||||||
|
|
||||||
let project_path = match project_path {
|
|
||||||
Ok(Some(project_path)) => project_path,
|
|
||||||
Ok(None) => {
|
|
||||||
not_found_paths.push(path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
open_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let open_buffer_task =
|
|
||||||
project.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
|
||||||
|
|
||||||
let buffer = match open_buffer_task {
|
|
||||||
Ok(task) => match task.await {
|
|
||||||
Ok(buffer) => buffer,
|
|
||||||
Err(error) => {
|
|
||||||
open_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
open_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_dirty = match buffer.read_with(cx, |buffer, _| buffer.is_dirty()) {
|
|
||||||
Ok(is_dirty) => is_dirty,
|
|
||||||
Err(error) => {
|
|
||||||
dirty_check_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_dirty {
|
|
||||||
buffers_to_reload.insert(buffer);
|
|
||||||
restored_paths.push(path);
|
|
||||||
} else {
|
|
||||||
clean_paths.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !buffers_to_reload.is_empty() {
|
|
||||||
let reload_task = project.update(cx, |project, cx| {
|
|
||||||
project.reload_buffers(buffers_to_reload, true, cx)
|
|
||||||
});
|
|
||||||
|
|
||||||
match reload_task {
|
|
||||||
Ok(task) => {
|
|
||||||
if let Err(error) = task.await {
|
|
||||||
reload_errors.push(error.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
reload_errors.push(error.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lines: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
if !restored_paths.is_empty() {
|
|
||||||
lines.push(format!("Restored {} file(s).", restored_paths.len()));
|
|
||||||
}
|
|
||||||
if !clean_paths.is_empty() {
|
|
||||||
lines.push(format!("{} clean.", clean_paths.len()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !not_found_paths.is_empty() {
|
|
||||||
lines.push(format!("Not found ({}):", not_found_paths.len()));
|
|
||||||
for path in ¬_found_paths {
|
|
||||||
lines.push(format!("- {}", path.display()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !open_errors.is_empty() {
|
|
||||||
lines.push(format!("Open failed ({}):", open_errors.len()));
|
|
||||||
for (path, error) in &open_errors {
|
|
||||||
lines.push(format!("- {}: {}", path.display(), error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !dirty_check_errors.is_empty() {
|
|
||||||
lines.push(format!(
|
|
||||||
"Dirty check failed ({}):",
|
|
||||||
dirty_check_errors.len()
|
|
||||||
));
|
|
||||||
for (path, error) in &dirty_check_errors {
|
|
||||||
lines.push(format!("- {}: {}", path.display(), error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !reload_errors.is_empty() {
|
|
||||||
lines.push(format!("Reload failed ({}):", reload_errors.len()));
|
|
||||||
for error in &reload_errors {
|
|
||||||
lines.push(format!("- {}", error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lines.is_empty() {
|
|
||||||
Ok("No paths provided.".to_string())
|
|
||||||
} else {
|
|
||||||
Ok(lines.join("\n"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use fs::Fs;
|
|
||||||
use gpui::TestAppContext;
|
|
||||||
use language::LineEnding;
|
|
||||||
use project::FakeFs;
|
|
||||||
use serde_json::json;
|
|
||||||
use settings::SettingsStore;
|
|
||||||
use util::path;
|
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) {
|
|
||||||
cx.update(|cx| {
|
|
||||||
let settings_store = SettingsStore::test(cx);
|
|
||||||
cx.set_global(settings_store);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_restore_file_from_disk_output_and_effects(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
|
||||||
fs.insert_tree(
|
|
||||||
"/root",
|
|
||||||
json!({
|
|
||||||
"dirty.txt": "on disk: dirty\n",
|
|
||||||
"clean.txt": "on disk: clean\n",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
|
||||||
let tool = Arc::new(RestoreFileFromDiskTool::new(project.clone()));
|
|
||||||
|
|
||||||
// Make dirty.txt dirty in-memory by saving different content into the buffer without saving to disk.
|
|
||||||
let dirty_project_path = project.read_with(cx, |project, cx| {
|
|
||||||
project
|
|
||||||
.find_project_path("root/dirty.txt", cx)
|
|
||||||
.expect("dirty.txt should exist in project")
|
|
||||||
});
|
|
||||||
|
|
||||||
let dirty_buffer = project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.open_buffer(dirty_project_path, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
dirty_buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.edit([(0..buffer.len(), "in memory: dirty\n")], None, cx);
|
|
||||||
});
|
|
||||||
assert!(
|
|
||||||
dirty_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"dirty.txt buffer should be dirty before restore"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure clean.txt is opened but remains clean.
|
|
||||||
let clean_project_path = project.read_with(cx, |project, cx| {
|
|
||||||
project
|
|
||||||
.find_project_path("root/clean.txt", cx)
|
|
||||||
.expect("clean.txt should exist in project")
|
|
||||||
});
|
|
||||||
|
|
||||||
let clean_buffer = project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.open_buffer(clean_project_path, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert!(
|
|
||||||
!clean_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"clean.txt buffer should start clean"
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = cx
|
|
||||||
.update(|cx| {
|
|
||||||
tool.clone().run(
|
|
||||||
RestoreFileFromDiskToolInput {
|
|
||||||
paths: vec![
|
|
||||||
PathBuf::from("root/dirty.txt"),
|
|
||||||
PathBuf::from("root/clean.txt"),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
ToolCallEventStream::test().0,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Output should mention restored + clean.
|
|
||||||
assert!(
|
|
||||||
output.contains("Restored 1 file(s)."),
|
|
||||||
"expected restored count line, got:\n{output}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.contains("1 clean."),
|
|
||||||
"expected clean count line, got:\n{output}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Effect: dirty buffer should be restored back to disk content and become clean.
|
|
||||||
let dirty_text = dirty_buffer.read_with(cx, |buffer, _| buffer.text());
|
|
||||||
assert_eq!(
|
|
||||||
dirty_text, "on disk: dirty\n",
|
|
||||||
"dirty.txt buffer should be restored to disk contents"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!dirty_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"dirty.txt buffer should not be dirty after restore"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Disk contents should be unchanged (restore-from-disk should not write).
|
|
||||||
let disk_dirty = fs.load(path!("/root/dirty.txt").as_ref()).await.unwrap();
|
|
||||||
assert_eq!(disk_dirty, "on disk: dirty\n");
|
|
||||||
|
|
||||||
// Sanity: clean buffer should remain clean and unchanged.
|
|
||||||
let clean_text = clean_buffer.read_with(cx, |buffer, _| buffer.text());
|
|
||||||
assert_eq!(clean_text, "on disk: clean\n");
|
|
||||||
assert!(
|
|
||||||
!clean_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"clean.txt buffer should remain clean"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test empty paths case.
|
|
||||||
let output = cx
|
|
||||||
.update(|cx| {
|
|
||||||
tool.clone().run(
|
|
||||||
RestoreFileFromDiskToolInput { paths: vec![] },
|
|
||||||
ToolCallEventStream::test().0,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(output, "No paths provided.");
|
|
||||||
|
|
||||||
// Test not-found path case (path outside the project root).
|
|
||||||
let output = cx
|
|
||||||
.update(|cx| {
|
|
||||||
tool.clone().run(
|
|
||||||
RestoreFileFromDiskToolInput {
|
|
||||||
paths: vec![PathBuf::from("nonexistent/path.txt")],
|
|
||||||
},
|
|
||||||
ToolCallEventStream::test().0,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert!(
|
|
||||||
output.contains("Not found (1):"),
|
|
||||||
"expected not-found header line, got:\n{output}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.contains("- nonexistent/path.txt"),
|
|
||||||
"expected not-found path bullet, got:\n{output}"
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = LineEnding::Unix; // keep import used if the buffer edit API changes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
use agent_client_protocol as acp;
|
|
||||||
use anyhow::Result;
|
|
||||||
use collections::FxHashSet;
|
|
||||||
use gpui::{App, Entity, SharedString, Task};
|
|
||||||
use language::Buffer;
|
|
||||||
use project::Project;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{AgentTool, ToolCallEventStream};
|
|
||||||
|
|
||||||
/// Saves files that have unsaved changes.
|
|
||||||
///
|
|
||||||
/// Use this tool when you need to edit files but they have unsaved changes that must be saved first.
|
|
||||||
/// Only use this tool after asking the user for permission to save their unsaved changes.
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct SaveFileToolInput {
|
|
||||||
/// The paths of the files to save.
|
|
||||||
pub paths: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SaveFileTool {
|
|
||||||
project: Entity<Project>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SaveFileTool {
|
|
||||||
pub fn new(project: Entity<Project>) -> Self {
|
|
||||||
Self { project }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AgentTool for SaveFileTool {
|
|
||||||
type Input = SaveFileToolInput;
|
|
||||||
type Output = String;
|
|
||||||
|
|
||||||
fn name() -> &'static str {
|
|
||||||
"save_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kind() -> acp::ToolKind {
|
|
||||||
acp::ToolKind::Other
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initial_title(
|
|
||||||
&self,
|
|
||||||
input: Result<Self::Input, serde_json::Value>,
|
|
||||||
_cx: &mut App,
|
|
||||||
) -> SharedString {
|
|
||||||
match input {
|
|
||||||
Ok(input) if input.paths.len() == 1 => "Save file".into(),
|
|
||||||
Ok(input) => format!("Save {} files", input.paths.len()).into(),
|
|
||||||
Err(_) => "Save files".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
self: Arc<Self>,
|
|
||||||
input: Self::Input,
|
|
||||||
_event_stream: ToolCallEventStream,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<String>> {
|
|
||||||
let project = self.project.clone();
|
|
||||||
let input_paths = input.paths;
|
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
|
||||||
let mut buffers_to_save: FxHashSet<Entity<Buffer>> = FxHashSet::default();
|
|
||||||
|
|
||||||
let mut saved_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
let mut clean_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
let mut not_found_paths: Vec<PathBuf> = Vec::new();
|
|
||||||
let mut open_errors: Vec<(PathBuf, String)> = Vec::new();
|
|
||||||
let mut dirty_check_errors: Vec<(PathBuf, String)> = Vec::new();
|
|
||||||
let mut save_errors: Vec<(String, String)> = Vec::new();
|
|
||||||
|
|
||||||
for path in input_paths {
|
|
||||||
let project_path =
|
|
||||||
project.read_with(cx, |project, cx| project.find_project_path(&path, cx));
|
|
||||||
|
|
||||||
let project_path = match project_path {
|
|
||||||
Ok(Some(project_path)) => project_path,
|
|
||||||
Ok(None) => {
|
|
||||||
not_found_paths.push(path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
open_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let open_buffer_task =
|
|
||||||
project.update(cx, |project, cx| project.open_buffer(project_path, cx));
|
|
||||||
|
|
||||||
let buffer = match open_buffer_task {
|
|
||||||
Ok(task) => match task.await {
|
|
||||||
Ok(buffer) => buffer,
|
|
||||||
Err(error) => {
|
|
||||||
open_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(error) => {
|
|
||||||
open_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_dirty = match buffer.read_with(cx, |buffer, _| buffer.is_dirty()) {
|
|
||||||
Ok(is_dirty) => is_dirty,
|
|
||||||
Err(error) => {
|
|
||||||
dirty_check_errors.push((path, error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_dirty {
|
|
||||||
buffers_to_save.insert(buffer);
|
|
||||||
saved_paths.push(path);
|
|
||||||
} else {
|
|
||||||
clean_paths.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save each buffer individually since there's no batch save API.
|
|
||||||
for buffer in buffers_to_save {
|
|
||||||
let path_for_buffer = match buffer.read_with(cx, |buffer, _| {
|
|
||||||
buffer
|
|
||||||
.file()
|
|
||||||
.map(|file| file.path().to_rel_path_buf())
|
|
||||||
.map(|path| path.as_rel_path().as_unix_str().to_owned())
|
|
||||||
}) {
|
|
||||||
Ok(path) => path.unwrap_or_else(|| "<unknown>".to_string()),
|
|
||||||
Err(error) => {
|
|
||||||
save_errors.push(("<unknown>".to_string(), error.to_string()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let save_task = project.update(cx, |project, cx| project.save_buffer(buffer, cx));
|
|
||||||
|
|
||||||
match save_task {
|
|
||||||
Ok(task) => {
|
|
||||||
if let Err(error) = task.await {
|
|
||||||
save_errors.push((path_for_buffer, error.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
save_errors.push((path_for_buffer, error.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lines: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
if !saved_paths.is_empty() {
|
|
||||||
lines.push(format!("Saved {} file(s).", saved_paths.len()));
|
|
||||||
}
|
|
||||||
if !clean_paths.is_empty() {
|
|
||||||
lines.push(format!("{} clean.", clean_paths.len()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !not_found_paths.is_empty() {
|
|
||||||
lines.push(format!("Not found ({}):", not_found_paths.len()));
|
|
||||||
for path in ¬_found_paths {
|
|
||||||
lines.push(format!("- {}", path.display()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !open_errors.is_empty() {
|
|
||||||
lines.push(format!("Open failed ({}):", open_errors.len()));
|
|
||||||
for (path, error) in &open_errors {
|
|
||||||
lines.push(format!("- {}: {}", path.display(), error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !dirty_check_errors.is_empty() {
|
|
||||||
lines.push(format!(
|
|
||||||
"Dirty check failed ({}):",
|
|
||||||
dirty_check_errors.len()
|
|
||||||
));
|
|
||||||
for (path, error) in &dirty_check_errors {
|
|
||||||
lines.push(format!("- {}: {}", path.display(), error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !save_errors.is_empty() {
|
|
||||||
lines.push(format!("Save failed ({}):", save_errors.len()));
|
|
||||||
for (path, error) in &save_errors {
|
|
||||||
lines.push(format!("- {}: {}", path, error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lines.is_empty() {
|
|
||||||
Ok("No paths provided.".to_string())
|
|
||||||
} else {
|
|
||||||
Ok(lines.join("\n"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use fs::Fs;
|
|
||||||
use gpui::TestAppContext;
|
|
||||||
use project::FakeFs;
|
|
||||||
use serde_json::json;
|
|
||||||
use settings::SettingsStore;
|
|
||||||
use util::path;
|
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) {
|
|
||||||
cx.update(|cx| {
|
|
||||||
let settings_store = SettingsStore::test(cx);
|
|
||||||
cx.set_global(settings_store);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_save_file_output_and_effects(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
|
||||||
fs.insert_tree(
|
|
||||||
"/root",
|
|
||||||
json!({
|
|
||||||
"dirty.txt": "on disk: dirty\n",
|
|
||||||
"clean.txt": "on disk: clean\n",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
|
||||||
let tool = Arc::new(SaveFileTool::new(project.clone()));
|
|
||||||
|
|
||||||
// Make dirty.txt dirty in-memory.
|
|
||||||
let dirty_project_path = project.read_with(cx, |project, cx| {
|
|
||||||
project
|
|
||||||
.find_project_path("root/dirty.txt", cx)
|
|
||||||
.expect("dirty.txt should exist in project")
|
|
||||||
});
|
|
||||||
|
|
||||||
let dirty_buffer = project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.open_buffer(dirty_project_path, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
dirty_buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.edit([(0..buffer.len(), "in memory: dirty\n")], None, cx);
|
|
||||||
});
|
|
||||||
assert!(
|
|
||||||
dirty_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"dirty.txt buffer should be dirty before save"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure clean.txt is opened but remains clean.
|
|
||||||
let clean_project_path = project.read_with(cx, |project, cx| {
|
|
||||||
project
|
|
||||||
.find_project_path("root/clean.txt", cx)
|
|
||||||
.expect("clean.txt should exist in project")
|
|
||||||
});
|
|
||||||
|
|
||||||
let clean_buffer = project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.open_buffer(clean_project_path, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert!(
|
|
||||||
!clean_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"clean.txt buffer should start clean"
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = cx
|
|
||||||
.update(|cx| {
|
|
||||||
tool.clone().run(
|
|
||||||
SaveFileToolInput {
|
|
||||||
paths: vec![
|
|
||||||
PathBuf::from("root/dirty.txt"),
|
|
||||||
PathBuf::from("root/clean.txt"),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
ToolCallEventStream::test().0,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Output should mention saved + clean.
|
|
||||||
assert!(
|
|
||||||
output.contains("Saved 1 file(s)."),
|
|
||||||
"expected saved count line, got:\n{output}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.contains("1 clean."),
|
|
||||||
"expected clean count line, got:\n{output}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Effect: dirty buffer should now be clean and disk should have new content.
|
|
||||||
assert!(
|
|
||||||
!dirty_buffer.read_with(cx, |buffer, _| buffer.is_dirty()),
|
|
||||||
"dirty.txt buffer should not be dirty after save"
|
|
||||||
);
|
|
||||||
|
|
||||||
let disk_dirty = fs.load(path!("/root/dirty.txt").as_ref()).await.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
disk_dirty, "in memory: dirty\n",
|
|
||||||
"dirty.txt disk content should be updated"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sanity: clean buffer should remain clean and disk unchanged.
|
|
||||||
let disk_clean = fs.load(path!("/root/clean.txt").as_ref()).await.unwrap();
|
|
||||||
assert_eq!(disk_clean, "on disk: clean\n");
|
|
||||||
|
|
||||||
// Test empty paths case.
|
|
||||||
let output = cx
|
|
||||||
.update(|cx| {
|
|
||||||
tool.clone().run(
|
|
||||||
SaveFileToolInput { paths: vec![] },
|
|
||||||
ToolCallEventStream::test().0,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(output, "No paths provided.");
|
|
||||||
|
|
||||||
// Test not-found path case.
|
|
||||||
let output = cx
|
|
||||||
.update(|cx| {
|
|
||||||
tool.clone().run(
|
|
||||||
SaveFileToolInput {
|
|
||||||
paths: vec![PathBuf::from("nonexistent/path.txt")],
|
|
||||||
},
|
|
||||||
ToolCallEventStream::test().0,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert!(
|
|
||||||
output.contains("Not found (1):"),
|
|
||||||
"expected not-found header line, got:\n{output}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.contains("- nonexistent/path.txt"),
|
|
||||||
"expected not-found path bullet, got:\n{output}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::FutureExt as _;
|
use gpui::{App, Entity, SharedString, Task};
|
||||||
use gpui::{App, AppContext, Entity, SharedString, Task};
|
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -9,7 +8,6 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
use util::markdown::MarkdownInlineCode;
|
use util::markdown::MarkdownInlineCode;
|
||||||
|
|
||||||
@@ -27,17 +25,13 @@ const COMMAND_OUTPUT_LIMIT: u64 = 16 * 1024;
|
|||||||
///
|
///
|
||||||
/// Do not use this tool for commands that run indefinitely, such as servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers that don't terminate on their own.
|
/// Do not use this tool for commands that run indefinitely, such as servers (like `npm run start`, `npm run dev`, `python -m http.server`, etc) or file watchers that don't terminate on their own.
|
||||||
///
|
///
|
||||||
/// For potentially long-running commands, prefer specifying `timeout_ms` to bound runtime and prevent indefinite hangs.
|
|
||||||
///
|
|
||||||
/// Remember that each invocation of this tool will spawn a new shell process, so you can't rely on any state from previous invocations.
|
/// Remember that each invocation of this tool will spawn a new shell process, so you can't rely on any state from previous invocations.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TerminalToolInput {
|
pub struct TerminalToolInput {
|
||||||
/// The one-liner command to execute.
|
/// The one-liner command to execute.
|
||||||
pub command: String,
|
command: String,
|
||||||
/// Working directory for the command. This must be one of the root directories of the project.
|
/// Working directory for the command. This must be one of the root directories of the project.
|
||||||
pub cd: String,
|
cd: String,
|
||||||
/// Optional maximum runtime (in milliseconds). If exceeded, the running terminal task is killed.
|
|
||||||
pub timeout_ms: Option<u64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TerminalTool {
|
pub struct TerminalTool {
|
||||||
@@ -122,26 +116,7 @@ impl AgentTool for TerminalTool {
|
|||||||
acp::ToolCallContent::Terminal(acp::Terminal::new(terminal_id)),
|
acp::ToolCallContent::Terminal(acp::Terminal::new(terminal_id)),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
let timeout = input.timeout_ms.map(Duration::from_millis);
|
let exit_status = terminal.wait_for_exit(cx)?.await;
|
||||||
|
|
||||||
let exit_status = match timeout {
|
|
||||||
Some(timeout) => {
|
|
||||||
let wait_for_exit = terminal.wait_for_exit(cx)?;
|
|
||||||
let timeout_task = cx.background_spawn(async move {
|
|
||||||
smol::Timer::after(timeout).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
futures::select! {
|
|
||||||
status = wait_for_exit.clone().fuse() => status,
|
|
||||||
_ = timeout_task.fuse() => {
|
|
||||||
terminal.kill(cx)?;
|
|
||||||
wait_for_exit.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => terminal.wait_for_exit(cx)?.await,
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = terminal.current_output(cx)?;
|
let output = terminal.current_output(cx)?;
|
||||||
|
|
||||||
Ok(process_content(output, &input.command, exit_status))
|
Ok(process_content(output, &input.command, exit_status))
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use project::agent_server_store::AgentServerCommand;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use task::ShellBuilder;
|
use task::ShellBuilder;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use task::ShellKind;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -89,9 +91,24 @@ impl AcpConnection {
|
|||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?;
|
let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?;
|
||||||
let builder = ShellBuilder::new(&shell, cfg!(windows)).non_interactive();
|
let builder = ShellBuilder::new(&shell, cfg!(windows));
|
||||||
let mut child =
|
#[cfg(windows)]
|
||||||
builder.build_command(Some(command.path.display().to_string()), &command.args);
|
let kind = builder.kind();
|
||||||
|
let (cmd, args) = builder.build(Some(command.path.display().to_string()), &command.args);
|
||||||
|
|
||||||
|
let mut child = util::command::new_smol_command(cmd);
|
||||||
|
#[cfg(windows)]
|
||||||
|
if kind == ShellKind::Cmd {
|
||||||
|
use smol::process::windows::CommandExt;
|
||||||
|
for arg in args {
|
||||||
|
child.raw_arg(arg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
child.args(args);
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
child.args(args);
|
||||||
|
|
||||||
child
|
child
|
||||||
.envs(command.env.iter().flatten())
|
.envs(command.env.iter().flatten())
|
||||||
.stdin(std::process::Stdio::piped())
|
.stdin(std::process::Stdio::piped())
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ mod codex;
|
|||||||
mod custom;
|
mod custom;
|
||||||
mod gemini;
|
mod gemini;
|
||||||
|
|
||||||
use collections::HashSet;
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod e2e_tests;
|
pub mod e2e_tests;
|
||||||
|
|
||||||
@@ -58,19 +56,9 @@ impl AgentServerDelegate {
|
|||||||
pub trait AgentServer: Send {
|
pub trait AgentServer: Send {
|
||||||
fn logo(&self) -> ui::IconName;
|
fn logo(&self) -> ui::IconName;
|
||||||
fn name(&self) -> SharedString;
|
fn name(&self) -> SharedString;
|
||||||
fn connect(
|
|
||||||
&self,
|
|
||||||
root_dir: Option<&Path>,
|
|
||||||
delegate: AgentServerDelegate,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
|
|
||||||
|
|
||||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
|
||||||
|
|
||||||
fn default_mode(&self, _cx: &mut App) -> Option<agent_client_protocol::SessionModeId> {
|
fn default_mode(&self, _cx: &mut App) -> Option<agent_client_protocol::SessionModeId> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_default_mode(
|
fn set_default_mode(
|
||||||
&self,
|
&self,
|
||||||
_mode_id: Option<agent_client_protocol::SessionModeId>,
|
_mode_id: Option<agent_client_protocol::SessionModeId>,
|
||||||
@@ -91,18 +79,14 @@ pub trait AgentServer: Send {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn favorite_model_ids(&self, _cx: &mut App) -> HashSet<agent_client_protocol::ModelId> {
|
fn connect(
|
||||||
HashSet::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_favorite_model(
|
|
||||||
&self,
|
&self,
|
||||||
_model_id: agent_client_protocol::ModelId,
|
root_dir: Option<&Path>,
|
||||||
_should_be_favorite: bool,
|
delegate: AgentServerDelegate,
|
||||||
_fs: Arc<dyn Fs>,
|
cx: &mut App,
|
||||||
_cx: &App,
|
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
|
||||||
) {
|
|
||||||
}
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn AgentServer {
|
impl dyn AgentServer {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use collections::HashSet;
|
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use settings::{SettingsStore, update_settings_file};
|
use settings::{SettingsStore, update_settings_file};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -73,48 +72,6 @@ impl AgentServer for ClaudeCode {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn favorite_model_ids(&self, cx: &mut App) -> HashSet<acp::ModelId> {
|
|
||||||
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
|
||||||
settings.get::<AllAgentServersSettings>(None).claude.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
settings
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| {
|
|
||||||
s.favorite_models
|
|
||||||
.iter()
|
|
||||||
.map(|id| acp::ModelId::new(id.clone()))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_favorite_model(
|
|
||||||
&self,
|
|
||||||
model_id: acp::ModelId,
|
|
||||||
should_be_favorite: bool,
|
|
||||||
fs: Arc<dyn Fs>,
|
|
||||||
cx: &App,
|
|
||||||
) {
|
|
||||||
update_settings_file(fs, cx, move |settings, _| {
|
|
||||||
let favorite_models = &mut settings
|
|
||||||
.agent_servers
|
|
||||||
.get_or_insert_default()
|
|
||||||
.claude
|
|
||||||
.get_or_insert_default()
|
|
||||||
.favorite_models;
|
|
||||||
|
|
||||||
let model_id_str = model_id.to_string();
|
|
||||||
if should_be_favorite {
|
|
||||||
if !favorite_models.contains(&model_id_str) {
|
|
||||||
favorite_models.push(model_id_str);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
favorite_models.retain(|id| id != &model_id_str);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect(
|
fn connect(
|
||||||
&self,
|
&self,
|
||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use std::{any::Any, path::Path};
|
|||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use collections::HashSet;
|
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, AppContext as _, SharedString, Task};
|
use gpui::{App, AppContext as _, SharedString, Task};
|
||||||
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
||||||
@@ -74,48 +73,6 @@ impl AgentServer for Codex {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn favorite_model_ids(&self, cx: &mut App) -> HashSet<acp::ModelId> {
|
|
||||||
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
|
||||||
settings.get::<AllAgentServersSettings>(None).codex.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
settings
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| {
|
|
||||||
s.favorite_models
|
|
||||||
.iter()
|
|
||||||
.map(|id| acp::ModelId::new(id.clone()))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_favorite_model(
|
|
||||||
&self,
|
|
||||||
model_id: acp::ModelId,
|
|
||||||
should_be_favorite: bool,
|
|
||||||
fs: Arc<dyn Fs>,
|
|
||||||
cx: &App,
|
|
||||||
) {
|
|
||||||
update_settings_file(fs, cx, move |settings, _| {
|
|
||||||
let favorite_models = &mut settings
|
|
||||||
.agent_servers
|
|
||||||
.get_or_insert_default()
|
|
||||||
.codex
|
|
||||||
.get_or_insert_default()
|
|
||||||
.favorite_models;
|
|
||||||
|
|
||||||
let model_id_str = model_id.to_string();
|
|
||||||
if should_be_favorite {
|
|
||||||
if !favorite_models.contains(&model_id_str) {
|
|
||||||
favorite_models.push(model_id_str);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
favorite_models.retain(|id| id != &model_id_str);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect(
|
fn connect(
|
||||||
&self,
|
&self,
|
||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
|||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use collections::HashSet;
|
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, AppContext as _, SharedString, Task};
|
use gpui::{App, AppContext as _, SharedString, Task};
|
||||||
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
|
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
|
||||||
@@ -55,7 +54,6 @@ impl AgentServer for CustomAgentServer {
|
|||||||
.or_insert_with(|| settings::CustomAgentServerSettings::Extension {
|
.or_insert_with(|| settings::CustomAgentServerSettings::Extension {
|
||||||
default_model: None,
|
default_model: None,
|
||||||
default_mode: None,
|
default_mode: None,
|
||||||
favorite_models: Vec::new(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
match settings {
|
match settings {
|
||||||
@@ -92,7 +90,6 @@ impl AgentServer for CustomAgentServer {
|
|||||||
.or_insert_with(|| settings::CustomAgentServerSettings::Extension {
|
.or_insert_with(|| settings::CustomAgentServerSettings::Extension {
|
||||||
default_model: None,
|
default_model: None,
|
||||||
default_mode: None,
|
default_mode: None,
|
||||||
favorite_models: Vec::new(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
match settings {
|
match settings {
|
||||||
@@ -104,66 +101,6 @@ impl AgentServer for CustomAgentServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn favorite_model_ids(&self, cx: &mut App) -> HashSet<acp::ModelId> {
|
|
||||||
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
|
||||||
settings
|
|
||||||
.get::<AllAgentServersSettings>(None)
|
|
||||||
.custom
|
|
||||||
.get(&self.name())
|
|
||||||
.cloned()
|
|
||||||
});
|
|
||||||
|
|
||||||
settings
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| {
|
|
||||||
s.favorite_models()
|
|
||||||
.iter()
|
|
||||||
.map(|id| acp::ModelId::new(id.clone()))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_favorite_model(
|
|
||||||
&self,
|
|
||||||
model_id: acp::ModelId,
|
|
||||||
should_be_favorite: bool,
|
|
||||||
fs: Arc<dyn Fs>,
|
|
||||||
cx: &App,
|
|
||||||
) {
|
|
||||||
let name = self.name();
|
|
||||||
update_settings_file(fs, cx, move |settings, _| {
|
|
||||||
let settings = settings
|
|
||||||
.agent_servers
|
|
||||||
.get_or_insert_default()
|
|
||||||
.custom
|
|
||||||
.entry(name.clone())
|
|
||||||
.or_insert_with(|| settings::CustomAgentServerSettings::Extension {
|
|
||||||
default_model: None,
|
|
||||||
default_mode: None,
|
|
||||||
favorite_models: Vec::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let favorite_models = match settings {
|
|
||||||
settings::CustomAgentServerSettings::Custom {
|
|
||||||
favorite_models, ..
|
|
||||||
}
|
|
||||||
| settings::CustomAgentServerSettings::Extension {
|
|
||||||
favorite_models, ..
|
|
||||||
} => favorite_models,
|
|
||||||
};
|
|
||||||
|
|
||||||
let model_id_str = model_id.to_string();
|
|
||||||
if should_be_favorite {
|
|
||||||
if !favorite_models.contains(&model_id_str) {
|
|
||||||
favorite_models.push(model_id_str);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
favorite_models.retain(|id| id != &model_id_str);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect(
|
fn connect(
|
||||||
&self,
|
&self,
|
||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
|
|||||||
@@ -460,7 +460,6 @@ pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
|
|||||||
ignore_system_version: None,
|
ignore_system_version: None,
|
||||||
default_mode: None,
|
default_mode: None,
|
||||||
default_model: None,
|
default_model: None,
|
||||||
favorite_models: vec![],
|
|
||||||
}),
|
}),
|
||||||
gemini: Some(crate::gemini::tests::local_command().into()),
|
gemini: Some(crate::gemini::tests::local_command().into()),
|
||||||
codex: Some(BuiltinAgentServerSettings {
|
codex: Some(BuiltinAgentServerSettings {
|
||||||
@@ -470,7 +469,6 @@ pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
|
|||||||
ignore_system_version: None,
|
ignore_system_version: None,
|
||||||
default_mode: None,
|
default_mode: None,
|
||||||
default_model: None,
|
default_model: None,
|
||||||
favorite_models: vec![],
|
|
||||||
}),
|
}),
|
||||||
custom: collections::HashMap::default(),
|
custom: collections::HashMap::default(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ workspace = true
|
|||||||
path = "src/agent_settings.rs"
|
path = "src/agent_settings.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
agent-client-protocol.workspace = true
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
cloud_llm_client.workspace = true
|
cloud_llm_client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ mod agent_profile;
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use agent_client_protocol::ModelId;
|
use collections::IndexMap;
|
||||||
use collections::{HashSet, IndexMap};
|
|
||||||
use gpui::{App, Pixels, px};
|
use gpui::{App, Pixels, px};
|
||||||
use language_model::LanguageModel;
|
use language_model::LanguageModel;
|
||||||
use project::DisableAiSettings;
|
use project::DisableAiSettings;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{
|
use settings::{
|
||||||
DefaultAgentView, DockPosition, DockSide, LanguageModelParameters, LanguageModelSelection,
|
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
|
||||||
NotifyWhenAgentWaiting, RegisterSetting, Settings,
|
NotifyWhenAgentWaiting, RegisterSetting, Settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,16 +24,13 @@ pub struct AgentSettings {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub button: bool,
|
pub button: bool,
|
||||||
pub dock: DockPosition,
|
pub dock: DockPosition,
|
||||||
pub agents_panel_dock: DockSide,
|
|
||||||
pub default_width: Pixels,
|
pub default_width: Pixels,
|
||||||
pub default_height: Pixels,
|
pub default_height: Pixels,
|
||||||
pub default_model: Option<LanguageModelSelection>,
|
pub default_model: Option<LanguageModelSelection>,
|
||||||
pub inline_assistant_model: Option<LanguageModelSelection>,
|
pub inline_assistant_model: Option<LanguageModelSelection>,
|
||||||
pub inline_assistant_use_streaming_tools: bool,
|
|
||||||
pub commit_message_model: Option<LanguageModelSelection>,
|
pub commit_message_model: Option<LanguageModelSelection>,
|
||||||
pub thread_summary_model: Option<LanguageModelSelection>,
|
pub thread_summary_model: Option<LanguageModelSelection>,
|
||||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||||
pub favorite_models: Vec<LanguageModelSelection>,
|
|
||||||
pub default_profile: AgentProfileId,
|
pub default_profile: AgentProfileId,
|
||||||
pub default_view: DefaultAgentView,
|
pub default_view: DefaultAgentView,
|
||||||
pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
|
pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
|
||||||
@@ -98,13 +94,6 @@ impl AgentSettings {
|
|||||||
pub fn set_message_editor_max_lines(&self) -> usize {
|
pub fn set_message_editor_max_lines(&self) -> usize {
|
||||||
self.message_editor_min_lines * 2
|
self.message_editor_min_lines * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn favorite_model_ids(&self) -> HashSet<ModelId> {
|
|
||||||
self.favorite_models
|
|
||||||
.iter()
|
|
||||||
.map(|sel| ModelId::new(format!("{}/{}", sel.provider.0, sel.model)))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||||
@@ -162,18 +151,13 @@ impl Settings for AgentSettings {
|
|||||||
enabled: agent.enabled.unwrap(),
|
enabled: agent.enabled.unwrap(),
|
||||||
button: agent.button.unwrap(),
|
button: agent.button.unwrap(),
|
||||||
dock: agent.dock.unwrap(),
|
dock: agent.dock.unwrap(),
|
||||||
agents_panel_dock: agent.agents_panel_dock.unwrap(),
|
|
||||||
default_width: px(agent.default_width.unwrap()),
|
default_width: px(agent.default_width.unwrap()),
|
||||||
default_height: px(agent.default_height.unwrap()),
|
default_height: px(agent.default_height.unwrap()),
|
||||||
default_model: Some(agent.default_model.unwrap()),
|
default_model: Some(agent.default_model.unwrap()),
|
||||||
inline_assistant_model: agent.inline_assistant_model,
|
inline_assistant_model: agent.inline_assistant_model,
|
||||||
inline_assistant_use_streaming_tools: agent
|
|
||||||
.inline_assistant_use_streaming_tools
|
|
||||||
.unwrap_or(true),
|
|
||||||
commit_message_model: agent.commit_message_model,
|
commit_message_model: agent.commit_message_model,
|
||||||
thread_summary_model: agent.thread_summary_model,
|
thread_summary_model: agent.thread_summary_model,
|
||||||
inline_alternatives: agent.inline_alternatives.unwrap_or_default(),
|
inline_alternatives: agent.inline_alternatives.unwrap_or_default(),
|
||||||
favorite_models: agent.favorite_models,
|
|
||||||
default_profile: AgentProfileId(agent.default_profile.unwrap()),
|
default_profile: AgentProfileId(agent.default_profile.unwrap()),
|
||||||
default_view: agent.default_view.unwrap(),
|
default_view: agent.default_view.unwrap(),
|
||||||
profiles: agent
|
profiles: agent
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ path = "src/agent_ui.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = ["assistant_text_thread/test-support", "eval_utils", "gpui/test-support", "language/test-support", "reqwest_client", "workspace/test-support", "agent/test-support"]
|
test-support = ["gpui/test-support", "language/test-support", "reqwest_client"]
|
||||||
unit-eval = []
|
unit-eval = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -40,7 +40,6 @@ component.workspace = true
|
|||||||
context_server.workspace = true
|
context_server.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
eval_utils = { workspace = true, optional = true }
|
|
||||||
extension.workspace = true
|
extension.workspace = true
|
||||||
extension_host.workspace = true
|
extension_host.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
@@ -72,7 +71,6 @@ postage.workspace = true
|
|||||||
project.workspace = true
|
project.workspace = true
|
||||||
prompt_store.workspace = true
|
prompt_store.workspace = true
|
||||||
proto.workspace = true
|
proto.workspace = true
|
||||||
rand.workspace = true
|
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
rules_library.workspace = true
|
rules_library.workspace = true
|
||||||
@@ -86,6 +84,7 @@ smol.workspace = true
|
|||||||
streaming_diff.workspace = true
|
streaming_diff.workspace = true
|
||||||
task.workspace = true
|
task.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
|
telemetry_events.workspace = true
|
||||||
terminal.workspace = true
|
terminal.workspace = true
|
||||||
terminal_view.workspace = true
|
terminal_view.workspace = true
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
@@ -121,6 +120,7 @@ language_model = { workspace = true, "features" = ["test-support"] }
|
|||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
semver.workspace = true
|
semver.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
reqwest_client.workspace = true
|
reqwest_client.workspace = true
|
||||||
tree-sitter-md.workspace = true
|
tree-sitter-md.workspace = true
|
||||||
unindent.workspace = true
|
unindent.workspace = true
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ use rope::Point;
|
|||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{cell::RefCell, fmt::Write, rc::Rc, sync::Arc};
|
use std::{cell::RefCell, fmt::Write, rc::Rc, sync::Arc};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{ContextMenu, prelude::*};
|
use ui::prelude::*;
|
||||||
use util::{ResultExt, debug_panic};
|
use util::{ResultExt, debug_panic};
|
||||||
use workspace::{CollaboratorId, Workspace};
|
use workspace::{CollaboratorId, Workspace};
|
||||||
use zed_actions::agent::{Chat, PasteRaw};
|
use zed_actions::agent::Chat;
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
mention_set: Entity<MentionSet>,
|
mention_set: Entity<MentionSet>,
|
||||||
@@ -132,21 +132,6 @@ impl MessageEditor {
|
|||||||
placement: Some(ContextMenuPlacement::Above),
|
placement: Some(ContextMenuPlacement::Above),
|
||||||
});
|
});
|
||||||
editor.register_addon(MessageEditorAddon::new());
|
editor.register_addon(MessageEditorAddon::new());
|
||||||
|
|
||||||
editor.set_custom_context_menu(|editor, _point, window, cx| {
|
|
||||||
let has_selection = editor.has_non_empty_selection(&editor.display_snapshot(cx));
|
|
||||||
|
|
||||||
Some(ContextMenu::build(window, cx, |menu, _, _| {
|
|
||||||
menu.action("Cut", Box::new(editor::actions::Cut))
|
|
||||||
.action_disabled_when(
|
|
||||||
!has_selection,
|
|
||||||
"Copy",
|
|
||||||
Box::new(editor::actions::Copy),
|
|
||||||
)
|
|
||||||
.action("Paste", Box::new(editor::actions::Paste))
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
let mention_set =
|
let mention_set =
|
||||||
@@ -558,9 +543,6 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Some(workspace) = self.workspace.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let editor_clipboard_selections = cx
|
let editor_clipboard_selections = cx
|
||||||
.read_from_clipboard()
|
.read_from_clipboard()
|
||||||
.and_then(|item| item.entries().first().cloned())
|
.and_then(|item| item.entries().first().cloned())
|
||||||
@@ -571,127 +553,133 @@ impl MessageEditor {
|
|||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Insert creases for pasted clipboard selections that:
|
let has_file_context = editor_clipboard_selections
|
||||||
// 1. Contain exactly one selection
|
.as_ref()
|
||||||
// 2. Have an associated file path
|
.is_some_and(|selections| {
|
||||||
// 3. Span multiple lines (not single-line selections)
|
selections
|
||||||
// 4. Belong to a file that exists in the current project
|
.iter()
|
||||||
let should_insert_creases = util::maybe!({
|
.any(|sel| sel.file_path.is_some() && sel.line_range.is_some())
|
||||||
let selections = editor_clipboard_selections.as_ref()?;
|
});
|
||||||
if selections.len() > 1 {
|
|
||||||
return Some(false);
|
|
||||||
}
|
|
||||||
let selection = selections.first()?;
|
|
||||||
let file_path = selection.file_path.as_ref()?;
|
|
||||||
let line_range = selection.line_range.as_ref()?;
|
|
||||||
|
|
||||||
if line_range.start() == line_range.end() {
|
if has_file_context {
|
||||||
return Some(false);
|
if let Some((workspace, selections)) =
|
||||||
}
|
self.workspace.upgrade().zip(editor_clipboard_selections)
|
||||||
|
{
|
||||||
Some(
|
let Some(first_selection) = selections.first() else {
|
||||||
workspace
|
return;
|
||||||
.read(cx)
|
};
|
||||||
.project()
|
if let Some(file_path) = &first_selection.file_path {
|
||||||
.read(cx)
|
// In case someone pastes selections from another window
|
||||||
.project_path_for_absolute_path(file_path, cx)
|
// with a different project, we don't want to insert the
|
||||||
.is_some(),
|
// crease (containing the absolute path) since the agent
|
||||||
)
|
// cannot access files outside the project.
|
||||||
})
|
let is_in_project = workspace
|
||||||
.unwrap_or(false);
|
.read(cx)
|
||||||
|
.project()
|
||||||
if should_insert_creases && let Some(selections) = editor_clipboard_selections {
|
.read(cx)
|
||||||
cx.stop_propagation();
|
.project_path_for_absolute_path(file_path, cx)
|
||||||
let insertion_target = self
|
.is_some();
|
||||||
.editor
|
if !is_in_project {
|
||||||
.read(cx)
|
return;
|
||||||
.selections
|
}
|
||||||
.newest_anchor()
|
|
||||||
.start
|
|
||||||
.text_anchor;
|
|
||||||
|
|
||||||
let project = workspace.read(cx).project().clone();
|
|
||||||
for selection in selections {
|
|
||||||
if let (Some(file_path), Some(line_range)) =
|
|
||||||
(selection.file_path, selection.line_range)
|
|
||||||
{
|
|
||||||
let crease_text =
|
|
||||||
acp_thread::selection_name(Some(file_path.as_ref()), &line_range);
|
|
||||||
|
|
||||||
let mention_uri = MentionUri::Selection {
|
|
||||||
abs_path: Some(file_path.clone()),
|
|
||||||
line_range: line_range.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mention_text = mention_uri.as_link().to_string();
|
|
||||||
let (excerpt_id, text_anchor, content_len) =
|
|
||||||
self.editor.update(cx, |editor, cx| {
|
|
||||||
let buffer = editor.buffer().read(cx);
|
|
||||||
let snapshot = buffer.snapshot(cx);
|
|
||||||
let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
|
|
||||||
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
|
|
||||||
|
|
||||||
editor.insert(&mention_text, window, cx);
|
|
||||||
editor.insert(" ", window, cx);
|
|
||||||
|
|
||||||
(*excerpt_id, text_anchor, mention_text.len())
|
|
||||||
});
|
|
||||||
|
|
||||||
let Some((crease_id, tx)) = insert_crease_for_mention(
|
|
||||||
excerpt_id,
|
|
||||||
text_anchor,
|
|
||||||
content_len,
|
|
||||||
crease_text.into(),
|
|
||||||
mention_uri.icon_path(cx),
|
|
||||||
None,
|
|
||||||
self.editor.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
drop(tx);
|
|
||||||
|
|
||||||
let mention_task = cx
|
|
||||||
.spawn({
|
|
||||||
let project = project.clone();
|
|
||||||
async move |_, cx| {
|
|
||||||
let project_path = project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.project_path_for_absolute_path(&file_path, cx)
|
|
||||||
})
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.ok_or_else(|| "project path not found".to_string())?;
|
|
||||||
|
|
||||||
let buffer = project
|
|
||||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
buffer
|
|
||||||
.update(cx, |buffer, cx| {
|
|
||||||
let start = Point::new(*line_range.start(), 0)
|
|
||||||
.min(buffer.max_point());
|
|
||||||
let end = Point::new(*line_range.end() + 1, 0)
|
|
||||||
.min(buffer.max_point());
|
|
||||||
let content = buffer.text_for_range(start..end).collect();
|
|
||||||
Mention::Text {
|
|
||||||
content,
|
|
||||||
tracked_buffers: vec![cx.entity()],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.shared();
|
|
||||||
|
|
||||||
self.mention_set.update(cx, |mention_set, _cx| {
|
|
||||||
mention_set.insert_mention(crease_id, mention_uri.clone(), mention_task)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.stop_propagation();
|
||||||
|
let insertion_target = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.selections
|
||||||
|
.newest_anchor()
|
||||||
|
.start
|
||||||
|
.text_anchor;
|
||||||
|
|
||||||
|
let project = workspace.read(cx).project().clone();
|
||||||
|
for selection in selections {
|
||||||
|
if let (Some(file_path), Some(line_range)) =
|
||||||
|
(selection.file_path, selection.line_range)
|
||||||
|
{
|
||||||
|
let crease_text =
|
||||||
|
acp_thread::selection_name(Some(file_path.as_ref()), &line_range);
|
||||||
|
|
||||||
|
let mention_uri = MentionUri::Selection {
|
||||||
|
abs_path: Some(file_path.clone()),
|
||||||
|
line_range: line_range.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mention_text = mention_uri.as_link().to_string();
|
||||||
|
let (excerpt_id, text_anchor, content_len) =
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
let buffer = editor.buffer().read(cx);
|
||||||
|
let snapshot = buffer.snapshot(cx);
|
||||||
|
let (excerpt_id, _, buffer_snapshot) =
|
||||||
|
snapshot.as_singleton().unwrap();
|
||||||
|
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
|
||||||
|
|
||||||
|
editor.insert(&mention_text, window, cx);
|
||||||
|
editor.insert(" ", window, cx);
|
||||||
|
|
||||||
|
(*excerpt_id, text_anchor, mention_text.len())
|
||||||
|
});
|
||||||
|
|
||||||
|
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||||
|
excerpt_id,
|
||||||
|
text_anchor,
|
||||||
|
content_len,
|
||||||
|
crease_text.into(),
|
||||||
|
mention_uri.icon_path(cx),
|
||||||
|
None,
|
||||||
|
self.editor.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
let mention_task = cx
|
||||||
|
.spawn({
|
||||||
|
let project = project.clone();
|
||||||
|
async move |_, cx| {
|
||||||
|
let project_path = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.project_path_for_absolute_path(&file_path, cx)
|
||||||
|
})
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.ok_or_else(|| "project path not found".to_string())?;
|
||||||
|
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_buffer(project_path, cx)
|
||||||
|
})
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
buffer
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
let start = Point::new(*line_range.start(), 0)
|
||||||
|
.min(buffer.max_point());
|
||||||
|
let end = Point::new(*line_range.end() + 1, 0)
|
||||||
|
.min(buffer.max_point());
|
||||||
|
let content =
|
||||||
|
buffer.text_for_range(start..end).collect();
|
||||||
|
Mention::Text {
|
||||||
|
content,
|
||||||
|
tracked_buffers: vec![cx.entity()],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.shared();
|
||||||
|
|
||||||
|
self.mention_set.update(cx, |mention_set, _cx| {
|
||||||
|
mention_set.insert_mention(crease_id, mention_uri.clone(), mention_task)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.prompt_capabilities.borrow().image
|
if self.prompt_capabilities.borrow().image
|
||||||
@@ -702,13 +690,6 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste_raw(&mut self, _: &PasteRaw, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let editor = self.editor.clone();
|
|
||||||
window.defer(cx, move |window, cx| {
|
|
||||||
editor.update(cx, |editor, cx| editor.paste(&Paste, window, cx));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_dragged_files(
|
pub fn insert_dragged_files(
|
||||||
&mut self,
|
&mut self,
|
||||||
paths: Vec<project::ProjectPath>,
|
paths: Vec<project::ProjectPath>,
|
||||||
@@ -986,7 +967,6 @@ impl Render for MessageEditor {
|
|||||||
.on_action(cx.listener(Self::chat))
|
.on_action(cx.listener(Self::chat))
|
||||||
.on_action(cx.listener(Self::chat_with_follow))
|
.on_action(cx.listener(Self::chat_with_follow))
|
||||||
.on_action(cx.listener(Self::cancel))
|
.on_action(cx.listener(Self::cancel))
|
||||||
.on_action(cx.listener(Self::paste_raw))
|
|
||||||
.capture_action(cx.listener(Self::paste))
|
.capture_action(cx.listener(Self::paste))
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child({
|
.child({
|
||||||
@@ -1385,7 +1365,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
message_editor.read(cx).focus_handle(cx).focus(window, cx);
|
message_editor.read(cx).focus_handle(cx).focus(window);
|
||||||
message_editor.read(cx).editor().clone()
|
message_editor.read(cx).editor().clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1607,7 +1587,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
message_editor.read(cx).focus_handle(cx).focus(window, cx);
|
message_editor.read(cx).focus_handle(cx).focus(window);
|
||||||
let editor = message_editor.read(cx).editor().clone();
|
let editor = message_editor.read(cx).editor().clone();
|
||||||
(message_editor, editor)
|
(message_editor, editor)
|
||||||
});
|
});
|
||||||
@@ -2335,7 +2315,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
message_editor.read(cx).focus_handle(cx).focus(window, cx);
|
message_editor.read(cx).focus_handle(cx).focus(window);
|
||||||
let editor = message_editor.read(cx).editor().clone();
|
let editor = message_editor.read(cx).editor().clone();
|
||||||
(message_editor, editor)
|
(message_editor, editor)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -186,17 +186,6 @@ impl Render for ModeSelector {
|
|||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.justify_between()
|
|
||||||
.child(Label::new("Toggle Mode Menu"))
|
|
||||||
.child(KeyBinding::for_action_in(
|
|
||||||
&ToggleProfileSelector,
|
|
||||||
&focus_handle,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.pb_1()
|
.pb_1()
|
||||||
@@ -211,6 +200,17 @@ impl Render for ModeSelector {
|
|||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.justify_between()
|
||||||
|
.child(Label::new("Toggle Mode Menu"))
|
||||||
|
.child(KeyBinding::for_action_in(
|
||||||
|
&ToggleProfileSelector,
|
||||||
|
&focus_handle,
|
||||||
|
cx,
|
||||||
|
)),
|
||||||
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
use std::{cmp::Reverse, rc::Rc, sync::Arc};
|
use std::{cmp::Reverse, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelList, AgentModelSelector};
|
use acp_thread::{AgentModelInfo, AgentModelList, AgentModelSelector};
|
||||||
use agent_client_protocol::ModelId;
|
|
||||||
use agent_servers::AgentServer;
|
use agent_servers::AgentServer;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::{HashSet, IndexMap};
|
use collections::IndexMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use fuzzy::{StringMatchCandidate, match_strings};
|
use fuzzy::{StringMatchCandidate, match_strings};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, FocusHandle, Subscription, Task,
|
Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, FocusHandle, Task, WeakEntity,
|
||||||
WeakEntity,
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use settings::SettingsStore;
|
use ui::{
|
||||||
use ui::{DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, prelude::*};
|
DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, KeyBinding, ListItem,
|
||||||
|
ListItemSpacing, prelude::*,
|
||||||
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use zed_actions::agent::OpenSettings;
|
use zed_actions::agent::OpenSettings;
|
||||||
|
|
||||||
use crate::ui::{HoldForDefault, ModelSelectorFooter, ModelSelectorHeader, ModelSelectorListItem};
|
use crate::ui::HoldForDefault;
|
||||||
|
|
||||||
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
|
pub type AcpModelSelector = Picker<AcpModelPickerDelegate>;
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ pub fn acp_model_selector(
|
|||||||
|
|
||||||
enum AcpModelPickerEntry {
|
enum AcpModelPickerEntry {
|
||||||
Separator(SharedString),
|
Separator(SharedString),
|
||||||
Model(AgentModelInfo, bool),
|
Model(AgentModelInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcpModelPickerDelegate {
|
pub struct AcpModelPickerDelegate {
|
||||||
@@ -54,9 +53,7 @@ pub struct AcpModelPickerDelegate {
|
|||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
selected_description: Option<(usize, SharedString, bool)>,
|
selected_description: Option<(usize, SharedString, bool)>,
|
||||||
selected_model: Option<AgentModelInfo>,
|
selected_model: Option<AgentModelInfo>,
|
||||||
favorites: HashSet<ModelId>,
|
|
||||||
_refresh_models_task: Task<()>,
|
_refresh_models_task: Task<()>,
|
||||||
_settings_subscription: Subscription,
|
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,19 +101,6 @@ impl AcpModelPickerDelegate {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let agent_server_for_subscription = agent_server.clone();
|
|
||||||
let settings_subscription =
|
|
||||||
cx.observe_global_in::<SettingsStore>(window, move |picker, window, cx| {
|
|
||||||
// Only refresh if the favorites actually changed to avoid redundant work
|
|
||||||
// when other settings are modified (e.g., user editing settings.json)
|
|
||||||
let new_favorites = agent_server_for_subscription.favorite_model_ids(cx);
|
|
||||||
if new_favorites != picker.delegate.favorites {
|
|
||||||
picker.delegate.favorites = new_favorites;
|
|
||||||
picker.refresh(window, cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let favorites = agent_server.favorite_model_ids(cx);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
selector,
|
selector,
|
||||||
agent_server,
|
agent_server,
|
||||||
@@ -126,9 +110,7 @@ impl AcpModelPickerDelegate {
|
|||||||
selected_model: None,
|
selected_model: None,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
selected_description: None,
|
selected_description: None,
|
||||||
favorites,
|
|
||||||
_refresh_models_task: refresh_models_task,
|
_refresh_models_task: refresh_models_task,
|
||||||
_settings_subscription: settings_subscription,
|
|
||||||
focus_handle,
|
focus_handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,64 +118,6 @@ impl AcpModelPickerDelegate {
|
|||||||
pub fn active_model(&self) -> Option<&AgentModelInfo> {
|
pub fn active_model(&self) -> Option<&AgentModelInfo> {
|
||||||
self.selected_model.as_ref()
|
self.selected_model.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn favorites_count(&self) -> usize {
|
|
||||||
self.favorites.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_favorite_models(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
|
||||||
if self.favorites.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(models) = &self.models else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let all_models: Vec<&AgentModelInfo> = match models {
|
|
||||||
AgentModelList::Flat(list) => list.iter().collect(),
|
|
||||||
AgentModelList::Grouped(index_map) => index_map.values().flatten().collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let favorite_models: Vec<_> = all_models
|
|
||||||
.into_iter()
|
|
||||||
.filter(|model| self.favorites.contains(&model.id))
|
|
||||||
.unique_by(|model| &model.id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if favorite_models.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_id = self.selected_model.as_ref().map(|m| &m.id);
|
|
||||||
|
|
||||||
let current_index_in_favorites = current_id
|
|
||||||
.and_then(|id| favorite_models.iter().position(|m| &m.id == id))
|
|
||||||
.unwrap_or(usize::MAX);
|
|
||||||
|
|
||||||
let next_index = if current_index_in_favorites == usize::MAX {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
(current_index_in_favorites + 1) % favorite_models.len()
|
|
||||||
};
|
|
||||||
|
|
||||||
let next_model = favorite_models[next_index].clone();
|
|
||||||
|
|
||||||
self.selector
|
|
||||||
.select_model(next_model.id.clone(), cx)
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
|
|
||||||
self.selected_model = Some(next_model);
|
|
||||||
|
|
||||||
// Keep the picker selection aligned with the newly-selected model
|
|
||||||
if let Some(new_index) = self.filtered_entries.iter().position(|entry| {
|
|
||||||
matches!(entry, AcpModelPickerEntry::Model(model_info, _) if self.selected_model.as_ref().is_some_and(|selected| model_info.id == selected.id))
|
|
||||||
}) {
|
|
||||||
self.set_selected_index(new_index, window, cx);
|
|
||||||
} else {
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for AcpModelPickerDelegate {
|
impl PickerDelegate for AcpModelPickerDelegate {
|
||||||
@@ -219,7 +143,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
_cx: &mut Context<Picker<Self>>,
|
_cx: &mut Context<Picker<Self>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self.filtered_entries.get(ix) {
|
match self.filtered_entries.get(ix) {
|
||||||
Some(AcpModelPickerEntry::Model(_, _)) => true,
|
Some(AcpModelPickerEntry::Model(_)) => true,
|
||||||
Some(AcpModelPickerEntry::Separator(_)) | None => false,
|
Some(AcpModelPickerEntry::Separator(_)) | None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,8 +158,6 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let favorites = self.favorites.clone();
|
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let filtered_models = match this
|
let filtered_models = match this
|
||||||
.read_with(cx, |this, cx| {
|
.read_with(cx, |this, cx| {
|
||||||
@@ -252,7 +174,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.delegate.filtered_entries =
|
this.delegate.filtered_entries =
|
||||||
info_list_to_picker_entries(filtered_models, &favorites);
|
info_list_to_picker_entries(filtered_models).collect();
|
||||||
// Finds the currently selected model in the list
|
// Finds the currently selected model in the list
|
||||||
let new_index = this
|
let new_index = this
|
||||||
.delegate
|
.delegate
|
||||||
@@ -260,7 +182,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|selected| {
|
.and_then(|selected| {
|
||||||
this.delegate.filtered_entries.iter().position(|entry| {
|
this.delegate.filtered_entries.iter().position(|entry| {
|
||||||
if let AcpModelPickerEntry::Model(model_info, _) = entry {
|
if let AcpModelPickerEntry::Model(model_info) = entry {
|
||||||
model_info.id == selected.id
|
model_info.id == selected.id
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -276,7 +198,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
if let Some(AcpModelPickerEntry::Model(model_info, _)) =
|
if let Some(AcpModelPickerEntry::Model(model_info)) =
|
||||||
self.filtered_entries.get(self.selected_index)
|
self.filtered_entries.get(self.selected_index)
|
||||||
{
|
{
|
||||||
if window.modifiers().secondary() {
|
if window.modifiers().secondary() {
|
||||||
@@ -319,57 +241,75 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
match self.filtered_entries.get(ix)? {
|
match self.filtered_entries.get(ix)? {
|
||||||
AcpModelPickerEntry::Separator(title) => {
|
AcpModelPickerEntry::Separator(title) => Some(
|
||||||
Some(ModelSelectorHeader::new(title, ix > 1).into_any_element())
|
div()
|
||||||
}
|
.px_2()
|
||||||
AcpModelPickerEntry::Model(model_info, is_favorite) => {
|
.pb_1()
|
||||||
|
.when(ix > 1, |this| {
|
||||||
|
this.mt_1()
|
||||||
|
.pt_2()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
Label::new(title)
|
||||||
|
.size(LabelSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
AcpModelPickerEntry::Model(model_info) => {
|
||||||
let is_selected = Some(model_info) == self.selected_model.as_ref();
|
let is_selected = Some(model_info) == self.selected_model.as_ref();
|
||||||
let default_model = self.agent_server.default_model(cx);
|
let default_model = self.agent_server.default_model(cx);
|
||||||
let is_default = default_model.as_ref() == Some(&model_info.id);
|
let is_default = default_model.as_ref() == Some(&model_info.id);
|
||||||
|
|
||||||
let is_favorite = *is_favorite;
|
let model_icon_color = if is_selected {
|
||||||
let handle_action_click = {
|
Color::Accent
|
||||||
let model_id = model_info.id.clone();
|
} else {
|
||||||
let fs = self.fs.clone();
|
Color::Muted
|
||||||
let agent_server = self.agent_server.clone();
|
|
||||||
|
|
||||||
cx.listener(move |_, _, _, cx| {
|
|
||||||
agent_server.toggle_favorite_model(
|
|
||||||
model_id.clone(),
|
|
||||||
!is_favorite,
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.id(("model-picker-menu-child", ix))
|
.id(("model-picker-menu-child", ix))
|
||||||
.when_some(model_info.description.clone(), |this, description| {
|
.when_some(model_info.description.clone(), |this, description| {
|
||||||
this.on_hover(cx.listener(move |menu, hovered, _, cx| {
|
this
|
||||||
if *hovered {
|
.on_hover(cx.listener(move |menu, hovered, _, cx| {
|
||||||
menu.delegate.selected_description =
|
if *hovered {
|
||||||
Some((ix, description.clone(), is_default));
|
menu.delegate.selected_description = Some((ix, description.clone(), is_default));
|
||||||
} else if matches!(menu.delegate.selected_description, Some((id, _, _)) if id == ix) {
|
} else if matches!(menu.delegate.selected_description, Some((id, _, _)) if id == ix) {
|
||||||
menu.delegate.selected_description = None;
|
menu.delegate.selected_description = None;
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
ModelSelectorListItem::new(ix, model_info.name.clone())
|
ListItem::new(ix)
|
||||||
.map(|this| match &model_info.icon {
|
.inset(true)
|
||||||
Some(AgentModelIcon::Path(path)) => this.icon_path(path.clone()),
|
.spacing(ListItemSpacing::Sparse)
|
||||||
Some(AgentModelIcon::Named(icon)) => this.icon(*icon),
|
.toggle_state(selected)
|
||||||
None => this,
|
.child(
|
||||||
})
|
h_flex()
|
||||||
.is_selected(is_selected)
|
.w_full()
|
||||||
.is_focused(selected)
|
.gap_1p5()
|
||||||
.is_favorite(is_favorite)
|
.when_some(model_info.icon, |this, icon| {
|
||||||
.on_toggle_favorite(handle_action_click),
|
this.child(
|
||||||
|
Icon::new(icon)
|
||||||
|
.color(model_icon_color)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(Label::new(model_info.name.clone()).truncate()),
|
||||||
|
)
|
||||||
|
.end_slot(div().pr_3().when(is_selected, |this| {
|
||||||
|
this.child(
|
||||||
|
Icon::new(IconName::Check)
|
||||||
|
.color(Color::Accent)
|
||||||
|
.size(IconSize::Small),
|
||||||
|
)
|
||||||
|
})),
|
||||||
)
|
)
|
||||||
.into_any_element(),
|
.into_any_element()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,7 +343,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
fn render_footer(
|
fn render_footer(
|
||||||
&self,
|
&self,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
_cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
@@ -411,57 +351,43 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ModelSelectorFooter::new(OpenSettings.boxed_clone(), focus_handle).into_any_element())
|
Some(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.p_1p5()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.child(
|
||||||
|
Button::new("configure", "Configure")
|
||||||
|
.full_width()
|
||||||
|
.style(ButtonStyle::Outlined)
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(&OpenSettings, &focus_handle, cx)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(OpenSettings.boxed_clone(), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_any(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info_list_to_picker_entries(
|
fn info_list_to_picker_entries(
|
||||||
model_list: AgentModelList,
|
model_list: AgentModelList,
|
||||||
favorites: &HashSet<ModelId>,
|
) -> impl Iterator<Item = AcpModelPickerEntry> {
|
||||||
) -> Vec<AcpModelPickerEntry> {
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
|
|
||||||
let all_models: Vec<_> = match &model_list {
|
|
||||||
AgentModelList::Flat(list) => list.iter().collect(),
|
|
||||||
AgentModelList::Grouped(index_map) => index_map.values().flatten().collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let favorite_models: Vec<_> = all_models
|
|
||||||
.iter()
|
|
||||||
.filter(|m| favorites.contains(&m.id))
|
|
||||||
.unique_by(|m| &m.id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let has_favorites = !favorite_models.is_empty();
|
|
||||||
if has_favorites {
|
|
||||||
entries.push(AcpModelPickerEntry::Separator("Favorite".into()));
|
|
||||||
for model in favorite_models {
|
|
||||||
entries.push(AcpModelPickerEntry::Model((*model).clone(), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match model_list {
|
match model_list {
|
||||||
AgentModelList::Flat(list) => {
|
AgentModelList::Flat(list) => {
|
||||||
if has_favorites {
|
itertools::Either::Left(list.into_iter().map(AcpModelPickerEntry::Model))
|
||||||
entries.push(AcpModelPickerEntry::Separator("All".into()));
|
|
||||||
}
|
|
||||||
for model in list {
|
|
||||||
let is_favorite = favorites.contains(&model.id);
|
|
||||||
entries.push(AcpModelPickerEntry::Model(model, is_favorite));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AgentModelList::Grouped(index_map) => {
|
AgentModelList::Grouped(index_map) => {
|
||||||
for (group_name, models) in index_map {
|
itertools::Either::Right(index_map.into_iter().flat_map(|(group_name, models)| {
|
||||||
entries.push(AcpModelPickerEntry::Separator(group_name.0));
|
std::iter::once(AcpModelPickerEntry::Separator(group_name.0))
|
||||||
for model in models {
|
.chain(models.into_iter().map(AcpModelPickerEntry::Model))
|
||||||
let is_favorite = favorites.contains(&model.id);
|
}))
|
||||||
entries.push(AcpModelPickerEntry::Model(model, is_favorite));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fuzzy_search(
|
async fn fuzzy_search(
|
||||||
@@ -477,7 +403,9 @@ async fn fuzzy_search(
|
|||||||
let candidates = model_list
|
let candidates = model_list
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, model)| StringMatchCandidate::new(ix, model.name.as_ref()))
|
.map(|(ix, model)| {
|
||||||
|
StringMatchCandidate::new(ix, &format!("{}/{}", model.id, model.name))
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut matches = match_strings(
|
let mut matches = match_strings(
|
||||||
&candidates,
|
&candidates,
|
||||||
@@ -583,33 +511,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_favorites(models: Vec<&str>) -> HashSet<ModelId> {
|
|
||||||
models
|
|
||||||
.into_iter()
|
|
||||||
.map(|m| ModelId::new(m.to_string()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_entry_model_ids(entries: &[AcpModelPickerEntry]) -> Vec<&str> {
|
|
||||||
entries
|
|
||||||
.iter()
|
|
||||||
.filter_map(|entry| match entry {
|
|
||||||
AcpModelPickerEntry::Model(info, _) => Some(info.id.0.as_ref()),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_entry_labels(entries: &[AcpModelPickerEntry]) -> Vec<&str> {
|
|
||||||
entries
|
|
||||||
.iter()
|
|
||||||
.map(|entry| match entry {
|
|
||||||
AcpModelPickerEntry::Model(info, _) => info.id.0.as_ref(),
|
|
||||||
AcpModelPickerEntry::Separator(s) => &s,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_fuzzy_match(cx: &mut TestAppContext) {
|
async fn test_fuzzy_match(cx: &mut TestAppContext) {
|
||||||
let models = create_model_list(vec![
|
let models = create_model_list(vec![
|
||||||
@@ -649,185 +550,4 @@ mod tests {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_favorites_section_appears_when_favorites_exist(_cx: &mut TestAppContext) {
|
|
||||||
let models = create_model_list(vec![
|
|
||||||
("zed", vec!["zed/claude", "zed/gemini"]),
|
|
||||||
("openai", vec!["openai/gpt-5"]),
|
|
||||||
]);
|
|
||||||
let favorites = create_favorites(vec!["zed/gemini"]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
entries.first(),
|
|
||||||
Some(AcpModelPickerEntry::Separator(s)) if s == "Favorite"
|
|
||||||
));
|
|
||||||
|
|
||||||
let model_ids = get_entry_model_ids(&entries);
|
|
||||||
assert_eq!(model_ids[0], "zed/gemini");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_no_favorites_section_when_no_favorites(_cx: &mut TestAppContext) {
|
|
||||||
let models = create_model_list(vec![("zed", vec!["zed/claude", "zed/gemini"])]);
|
|
||||||
let favorites = create_favorites(vec![]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
entries.first(),
|
|
||||||
Some(AcpModelPickerEntry::Separator(s)) if s == "zed"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_models_have_correct_actions(_cx: &mut TestAppContext) {
|
|
||||||
let models = create_model_list(vec![
|
|
||||||
("zed", vec!["zed/claude", "zed/gemini"]),
|
|
||||||
("openai", vec!["openai/gpt-5"]),
|
|
||||||
]);
|
|
||||||
let favorites = create_favorites(vec!["zed/claude"]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
|
|
||||||
for entry in &entries {
|
|
||||||
if let AcpModelPickerEntry::Model(info, is_favorite) = entry {
|
|
||||||
if info.id.0.as_ref() == "zed/claude" {
|
|
||||||
assert!(is_favorite, "zed/claude should be a favorite");
|
|
||||||
} else {
|
|
||||||
assert!(!is_favorite, "{} should not be a favorite", info.id.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_favorites_appear_in_both_sections(_cx: &mut TestAppContext) {
|
|
||||||
let models = create_model_list(vec![
|
|
||||||
("zed", vec!["zed/claude", "zed/gemini"]),
|
|
||||||
("openai", vec!["openai/gpt-5", "openai/gpt-4"]),
|
|
||||||
]);
|
|
||||||
let favorites = create_favorites(vec!["zed/gemini", "openai/gpt-5"]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
let model_ids = get_entry_model_ids(&entries);
|
|
||||||
|
|
||||||
assert_eq!(model_ids[0], "zed/gemini");
|
|
||||||
assert_eq!(model_ids[1], "openai/gpt-5");
|
|
||||||
|
|
||||||
assert!(model_ids[2..].contains(&"zed/gemini"));
|
|
||||||
assert!(model_ids[2..].contains(&"openai/gpt-5"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_favorites_are_not_duplicated_when_repeated_in_other_sections(_cx: &mut TestAppContext) {
|
|
||||||
let models = create_model_list(vec![
|
|
||||||
("Recommended", vec!["zed/claude", "anthropic/claude"]),
|
|
||||||
("Zed", vec!["zed/claude", "zed/gpt-5"]),
|
|
||||||
("Antropic", vec!["anthropic/claude"]),
|
|
||||||
("OpenAI", vec!["openai/gpt-5"]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let favorites = create_favorites(vec!["zed/claude"]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
let labels = get_entry_labels(&entries);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
labels,
|
|
||||||
vec![
|
|
||||||
"Favorite",
|
|
||||||
"zed/claude",
|
|
||||||
"Recommended",
|
|
||||||
"zed/claude",
|
|
||||||
"anthropic/claude",
|
|
||||||
"Zed",
|
|
||||||
"zed/claude",
|
|
||||||
"zed/gpt-5",
|
|
||||||
"Antropic",
|
|
||||||
"anthropic/claude",
|
|
||||||
"OpenAI",
|
|
||||||
"openai/gpt-5"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_flat_model_list_with_favorites(_cx: &mut TestAppContext) {
|
|
||||||
let models = AgentModelList::Flat(vec![
|
|
||||||
acp_thread::AgentModelInfo {
|
|
||||||
id: acp::ModelId::new("zed/claude".to_string()),
|
|
||||||
name: "Claude".into(),
|
|
||||||
description: None,
|
|
||||||
icon: None,
|
|
||||||
},
|
|
||||||
acp_thread::AgentModelInfo {
|
|
||||||
id: acp::ModelId::new("zed/gemini".to_string()),
|
|
||||||
name: "Gemini".into(),
|
|
||||||
description: None,
|
|
||||||
icon: None,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
let favorites = create_favorites(vec!["zed/gemini"]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
entries.first(),
|
|
||||||
Some(AcpModelPickerEntry::Separator(s)) if s == "Favorite"
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(entries.iter().any(|e| matches!(
|
|
||||||
e,
|
|
||||||
AcpModelPickerEntry::Separator(s) if s == "All"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_favorites_count_returns_correct_count(_cx: &mut TestAppContext) {
|
|
||||||
let empty_favorites: HashSet<ModelId> = HashSet::default();
|
|
||||||
assert_eq!(empty_favorites.len(), 0);
|
|
||||||
|
|
||||||
let one_favorite = create_favorites(vec!["model-a"]);
|
|
||||||
assert_eq!(one_favorite.len(), 1);
|
|
||||||
|
|
||||||
let multiple_favorites = create_favorites(vec!["model-a", "model-b", "model-c"]);
|
|
||||||
assert_eq!(multiple_favorites.len(), 3);
|
|
||||||
|
|
||||||
let with_duplicates = create_favorites(vec!["model-a", "model-a", "model-b"]);
|
|
||||||
assert_eq!(with_duplicates.len(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_is_favorite_flag_set_correctly_in_entries(_cx: &mut TestAppContext) {
|
|
||||||
let models = AgentModelList::Flat(vec![
|
|
||||||
acp_thread::AgentModelInfo {
|
|
||||||
id: acp::ModelId::new("favorite-model".to_string()),
|
|
||||||
name: "Favorite".into(),
|
|
||||||
description: None,
|
|
||||||
icon: None,
|
|
||||||
},
|
|
||||||
acp_thread::AgentModelInfo {
|
|
||||||
id: acp::ModelId::new("regular-model".to_string()),
|
|
||||||
name: "Regular".into(),
|
|
||||||
description: None,
|
|
||||||
icon: None,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
let favorites = create_favorites(vec!["favorite-model"]);
|
|
||||||
|
|
||||||
let entries = info_list_to_picker_entries(models, &favorites);
|
|
||||||
|
|
||||||
for entry in &entries {
|
|
||||||
if let AcpModelPickerEntry::Model(info, is_favorite) = entry {
|
|
||||||
if info.id.0.as_ref() == "favorite-model" {
|
|
||||||
assert!(*is_favorite, "favorite-model should have is_favorite=true");
|
|
||||||
} else if info.id.0.as_ref() == "regular-model" {
|
|
||||||
assert!(!*is_favorite, "regular-model should have is_favorite=false");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelSelector};
|
use acp_thread::{AgentModelInfo, AgentModelSelector};
|
||||||
|
use agent_servers::AgentServer;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Entity, FocusHandle};
|
use gpui::{Entity, FocusHandle};
|
||||||
use picker::popover_menu::PickerPopoverMenu;
|
use picker::popover_menu::PickerPopoverMenu;
|
||||||
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
|
use ui::{
|
||||||
|
ButtonLike, Context, IntoElement, PopoverMenuHandle, SharedString, TintColor, Tooltip, Window,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use zed_actions::agent::ToggleModelSelector;
|
||||||
|
|
||||||
use crate::acp::{AcpModelSelector, model_selector::acp_model_selector};
|
use crate::acp::{AcpModelSelector, model_selector::acp_model_selector};
|
||||||
use crate::ui::ModelSelectorTooltip;
|
|
||||||
|
|
||||||
pub struct AcpModelSelectorPopover {
|
pub struct AcpModelSelectorPopover {
|
||||||
selector: Entity<AcpModelSelector>,
|
selector: Entity<AcpModelSelector>,
|
||||||
@@ -19,7 +23,7 @@ pub struct AcpModelSelectorPopover {
|
|||||||
impl AcpModelSelectorPopover {
|
impl AcpModelSelectorPopover {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
selector: Rc<dyn AgentModelSelector>,
|
selector: Rc<dyn AgentModelSelector>,
|
||||||
agent_server: Rc<dyn agent_servers::AgentServer>,
|
agent_server: Rc<dyn AgentServer>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
menu_handle: PopoverMenuHandle<AcpModelSelector>,
|
menu_handle: PopoverMenuHandle<AcpModelSelector>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
@@ -50,24 +54,17 @@ impl AcpModelSelectorPopover {
|
|||||||
pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> {
|
pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> {
|
||||||
self.selector.read(cx).delegate.active_model()
|
self.selector.read(cx).delegate.active_model()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
self.selector.update(cx, |selector, cx| {
|
|
||||||
selector.delegate.cycle_favorite_models(window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AcpModelSelectorPopover {
|
impl Render for AcpModelSelectorPopover {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let selector = self.selector.read(cx);
|
let model = self.selector.read(cx).delegate.active_model();
|
||||||
let model = selector.delegate.active_model();
|
|
||||||
let model_name = model
|
let model_name = model
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|model| model.name.clone())
|
.map(|model| model.name.clone())
|
||||||
.unwrap_or_else(|| SharedString::from("Select a Model"));
|
.unwrap_or_else(|| SharedString::from("Select a Model"));
|
||||||
|
|
||||||
let model_icon = model.as_ref().and_then(|model| model.icon.clone());
|
let model_icon = model.as_ref().and_then(|model| model.icon);
|
||||||
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
@@ -77,29 +74,12 @@ impl Render for AcpModelSelectorPopover {
|
|||||||
(Color::Muted, IconName::ChevronDown)
|
(Color::Muted, IconName::ChevronDown)
|
||||||
};
|
};
|
||||||
|
|
||||||
let show_cycle_row = selector.delegate.favorites_count() > 1;
|
|
||||||
|
|
||||||
let tooltip = Tooltip::element({
|
|
||||||
move |_, _cx| {
|
|
||||||
ModelSelectorTooltip::new(focus_handle.clone())
|
|
||||||
.show_cycle_row(show_cycle_row)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
PickerPopoverMenu::new(
|
PickerPopoverMenu::new(
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
.when_some(model_icon, |this, icon| {
|
.when_some(model_icon, |this, icon| {
|
||||||
this.child(
|
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
||||||
match icon {
|
|
||||||
AgentModelIcon::Path(path) => Icon::from_external_svg(path),
|
|
||||||
AgentModelIcon::Named(icon_name) => Icon::new(icon_name),
|
|
||||||
}
|
|
||||||
.color(color)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
Label::new(model_name)
|
Label::new(model_name)
|
||||||
@@ -108,7 +88,9 @@ impl Render for AcpModelSelectorPopover {
|
|||||||
.ml_0p5(),
|
.ml_0p5(),
|
||||||
)
|
)
|
||||||
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
|
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
|
||||||
tooltip,
|
move |_window, cx| {
|
||||||
|
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||||
|
},
|
||||||
gpui::Corner::BottomRight,
|
gpui::Corner::BottomRight,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::acp::AcpThreadView;
|
use crate::acp::AcpThreadView;
|
||||||
use crate::{AgentPanel, RemoveHistory, RemoveSelectedThread};
|
use crate::{AgentPanel, RemoveHistory, RemoveSelectedThread};
|
||||||
use agent::{HistoryEntry, HistoryStore};
|
use agent::{HistoryEntry, HistoryStore};
|
||||||
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta, Utc};
|
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
|
||||||
use editor::{Editor, EditorEvent};
|
use editor::{Editor, EditorEvent};
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -402,22 +402,7 @@ impl AcpThreadHistory {
|
|||||||
let selected = ix == self.selected_index;
|
let selected = ix == self.selected_index;
|
||||||
let hovered = Some(ix) == self.hovered_index;
|
let hovered = Some(ix) == self.hovered_index;
|
||||||
let timestamp = entry.updated_at().timestamp();
|
let timestamp = entry.updated_at().timestamp();
|
||||||
|
let thread_timestamp = format.format_timestamp(timestamp, self.local_timezone);
|
||||||
let display_text = match format {
|
|
||||||
EntryTimeFormat::DateAndTime => {
|
|
||||||
let entry_time = entry.updated_at();
|
|
||||||
let now = Utc::now();
|
|
||||||
let duration = now.signed_duration_since(entry_time);
|
|
||||||
let days = duration.num_days();
|
|
||||||
|
|
||||||
format!("{}d", days)
|
|
||||||
}
|
|
||||||
EntryTimeFormat::TimeOnly => format.format_timestamp(timestamp, self.local_timezone),
|
|
||||||
};
|
|
||||||
|
|
||||||
let title = entry.title().clone();
|
|
||||||
let full_date =
|
|
||||||
EntryTimeFormat::DateAndTime.format_timestamp(timestamp, self.local_timezone);
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -438,14 +423,11 @@ impl AcpThreadHistory {
|
|||||||
.truncate(),
|
.truncate(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Label::new(display_text)
|
Label::new(thread_timestamp)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.size(LabelSize::XSmall),
|
.size(LabelSize::XSmall),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.tooltip(move |_, cx| {
|
|
||||||
Tooltip::with_meta(title.clone(), None, full_date.clone(), cx)
|
|
||||||
})
|
|
||||||
.on_hover(cx.listener(move |this, is_hovered, _window, cx| {
|
.on_hover(cx.listener(move |this, is_hovered, _window, cx| {
|
||||||
if *is_hovered {
|
if *is_hovered {
|
||||||
this.hovered_index = Some(ix);
|
this.hovered_index = Some(ix);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use language_model::{
|
use language_model::{
|
||||||
IconOrSvg, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry,
|
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
|
||||||
ZED_CLOUD_PROVIDER_ID,
|
|
||||||
};
|
};
|
||||||
use language_models::AllLanguageModelSettings;
|
use language_models::AllLanguageModelSettings;
|
||||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||||
@@ -35,9 +34,9 @@ use project::{
|
|||||||
};
|
};
|
||||||
use settings::{Settings, SettingsStore, update_settings_file};
|
use settings::{Settings, SettingsStore, update_settings_file};
|
||||||
use ui::{
|
use ui::{
|
||||||
ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure, Divider,
|
Button, ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure,
|
||||||
DividerColor, ElevationIndex, Indicator, LabelSize, PopoverMenu, Switch, Tooltip,
|
Divider, DividerColor, ElevationIndex, IconName, IconPosition, IconSize, Indicator, LabelSize,
|
||||||
WithScrollbar, prelude::*,
|
PopoverMenu, Switch, Tooltip, WithScrollbar, prelude::*,
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{Workspace, create_and_open_local_file};
|
use workspace::{Workspace, create_and_open_local_file};
|
||||||
@@ -118,7 +117,7 @@ impl AgentConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let providers = LanguageModelRegistry::read_global(cx).visible_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, window, cx);
|
||||||
}
|
}
|
||||||
@@ -262,12 +261,9 @@ impl AgentConfiguration {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.child(
|
.child(
|
||||||
match provider.icon() {
|
Icon::new(provider.icon())
|
||||||
IconOrSvg::Svg(path) => Icon::from_external_svg(path),
|
.size(IconSize::Small)
|
||||||
IconOrSvg::Icon(name) => Icon::new(name),
|
.color(Color::Muted),
|
||||||
}
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -420,7 +416,7 @@ impl AgentConfiguration {
|
|||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let providers = LanguageModelRegistry::read_global(cx).visible_providers();
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||||
|
|
||||||
let popover_menu = PopoverMenu::new("add-provider-popover")
|
let popover_menu = PopoverMenu::new("add-provider-popover")
|
||||||
.trigger(
|
.trigger(
|
||||||
@@ -979,7 +975,7 @@ impl AgentConfiguration {
|
|||||||
let icon = if let Some(icon_path) = agent_server_store.agent_icon(&name) {
|
let icon = if let Some(icon_path) = agent_server_store.agent_icon(&name) {
|
||||||
AgentIcon::Path(icon_path)
|
AgentIcon::Path(icon_path)
|
||||||
} else {
|
} else {
|
||||||
AgentIcon::Name(IconName::Sparkle)
|
AgentIcon::Name(IconName::Ai)
|
||||||
};
|
};
|
||||||
let display_name = agent_server_store
|
let display_name = agent_server_store
|
||||||
.agent_display_name(&name)
|
.agent_display_name(&name)
|
||||||
@@ -1141,7 +1137,6 @@ impl AgentConfiguration {
|
|||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
let display_name = display_name.into();
|
let display_name = display_name.into();
|
||||||
|
|
||||||
let icon = match icon {
|
let icon = match icon {
|
||||||
AgentIcon::Name(icon_name) => Icon::new(icon_name)
|
AgentIcon::Name(icon_name) => Icon::new(icon_name)
|
||||||
.size(IconSize::Small)
|
.size(IconSize::Small)
|
||||||
@@ -1370,7 +1365,6 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
|||||||
env: Some(HashMap::default()),
|
env: Some(HashMap::default()),
|
||||||
default_mode: None,
|
default_mode: None,
|
||||||
default_model: None,
|
default_model: None,
|
||||||
favorite_models: vec![],
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,17 +446,17 @@ impl AddLlmProviderModal {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_tab(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
|
fn on_tab(&mut self, _: &menu::SelectNext, window: &mut Window, _: &mut Context<Self>) {
|
||||||
window.focus_next(cx);
|
window.focus_next();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_tab_prev(
|
fn on_tab_prev(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &menu::SelectPrevious,
|
_: &menu::SelectPrevious,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
_: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
window.focus_prev(cx);
|
window.focus_prev();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +493,7 @@ impl Render for AddLlmProviderModal {
|
|||||||
.on_action(cx.listener(Self::on_tab))
|
.on_action(cx.listener(Self::on_tab))
|
||||||
.on_action(cx.listener(Self::on_tab_prev))
|
.on_action(cx.listener(Self::on_tab_prev))
|
||||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
this.focus_handle(cx).focus(window, cx);
|
this.focus_handle(cx).focus(window);
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
Modal::new("configure-context-server", None)
|
Modal::new("configure-context-server", None)
|
||||||
|
|||||||
@@ -831,7 +831,7 @@ impl Render for ConfigureContextServerModal {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
this.focus_handle(cx).focus(window, cx);
|
this.focus_handle(cx).focus(window);
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
Modal::new("configure-context-server", None)
|
Modal::new("configure-context-server", None)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use editor::Editor;
|
|||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
|
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
|
||||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||||
use settings::SettingsStore;
|
|
||||||
use settings::{
|
use settings::{
|
||||||
LanguageModelProviderSetting, LanguageModelSelection, Settings as _, update_settings_file,
|
LanguageModelProviderSetting, LanguageModelSelection, Settings as _, update_settings_file,
|
||||||
};
|
};
|
||||||
@@ -95,7 +94,6 @@ pub struct ViewProfileMode {
|
|||||||
configure_default_model: NavigableEntry,
|
configure_default_model: NavigableEntry,
|
||||||
configure_tools: NavigableEntry,
|
configure_tools: NavigableEntry,
|
||||||
configure_mcps: NavigableEntry,
|
configure_mcps: NavigableEntry,
|
||||||
delete_profile: NavigableEntry,
|
|
||||||
cancel_item: NavigableEntry,
|
cancel_item: NavigableEntry,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +109,6 @@ pub struct ManageProfilesModal {
|
|||||||
active_model: Option<Arc<dyn LanguageModel>>,
|
active_model: Option<Arc<dyn LanguageModel>>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
_settings_subscription: Subscription,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManageProfilesModal {
|
impl ManageProfilesModal {
|
||||||
@@ -151,29 +148,18 @@ impl ManageProfilesModal {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
// Keep this modal in sync with settings changes (including profile deletion).
|
|
||||||
let settings_subscription =
|
|
||||||
cx.observe_global_in::<SettingsStore>(window, |this, window, cx| {
|
|
||||||
if matches!(this.mode, Mode::ChooseProfile(_)) {
|
|
||||||
this.mode = Mode::choose_profile(window, cx);
|
|
||||||
this.focus_handle(cx).focus(window, cx);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
fs,
|
fs,
|
||||||
active_model,
|
active_model,
|
||||||
context_server_registry,
|
context_server_registry,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
mode: Mode::choose_profile(window, cx),
|
mode: Mode::choose_profile(window, cx),
|
||||||
_settings_subscription: settings_subscription,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_profile(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn choose_profile(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.mode = Mode::choose_profile(window, cx);
|
self.mode = Mode::choose_profile(window, cx);
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_profile(
|
fn new_profile(
|
||||||
@@ -191,7 +177,7 @@ impl ManageProfilesModal {
|
|||||||
name_editor,
|
name_editor,
|
||||||
base_profile_id,
|
base_profile_id,
|
||||||
});
|
});
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_profile(
|
pub fn view_profile(
|
||||||
@@ -206,10 +192,9 @@ impl ManageProfilesModal {
|
|||||||
configure_default_model: NavigableEntry::focusable(cx),
|
configure_default_model: NavigableEntry::focusable(cx),
|
||||||
configure_tools: NavigableEntry::focusable(cx),
|
configure_tools: NavigableEntry::focusable(cx),
|
||||||
configure_mcps: NavigableEntry::focusable(cx),
|
configure_mcps: NavigableEntry::focusable(cx),
|
||||||
delete_profile: NavigableEntry::focusable(cx),
|
|
||||||
cancel_item: NavigableEntry::focusable(cx),
|
cancel_item: NavigableEntry::focusable(cx),
|
||||||
});
|
});
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_default_model(
|
fn configure_default_model(
|
||||||
@@ -222,6 +207,7 @@ impl ManageProfilesModal {
|
|||||||
let profile_id_for_closure = profile_id.clone();
|
let profile_id_for_closure = profile_id.clone();
|
||||||
|
|
||||||
let model_picker = cx.new(|cx| {
|
let model_picker = cx.new(|cx| {
|
||||||
|
let fs = fs.clone();
|
||||||
let profile_id = profile_id_for_closure.clone();
|
let profile_id = profile_id_for_closure.clone();
|
||||||
|
|
||||||
language_model_selector(
|
language_model_selector(
|
||||||
@@ -249,36 +235,22 @@ impl ManageProfilesModal {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
move |model, cx| {
|
||||||
let fs = fs.clone();
|
let provider = model.provider_id().0.to_string();
|
||||||
move |model, cx| {
|
let model_id = model.id().0.to_string();
|
||||||
let provider = model.provider_id().0.to_string();
|
let profile_id = profile_id.clone();
|
||||||
let model_id = model.id().0.to_string();
|
|
||||||
let profile_id = profile_id.clone();
|
|
||||||
|
|
||||||
update_settings_file(fs.clone(), cx, move |settings, _cx| {
|
update_settings_file(fs.clone(), cx, move |settings, _cx| {
|
||||||
let agent_settings = settings.agent.get_or_insert_default();
|
let agent_settings = settings.agent.get_or_insert_default();
|
||||||
if let Some(profiles) = agent_settings.profiles.as_mut() {
|
if let Some(profiles) = agent_settings.profiles.as_mut() {
|
||||||
if let Some(profile) = profiles.get_mut(profile_id.0.as_ref()) {
|
if let Some(profile) = profiles.get_mut(profile_id.0.as_ref()) {
|
||||||
profile.default_model = Some(LanguageModelSelection {
|
profile.default_model = Some(LanguageModelSelection {
|
||||||
provider: LanguageModelProviderSetting(provider.clone()),
|
provider: LanguageModelProviderSetting(provider.clone()),
|
||||||
model: model_id.clone(),
|
model: model_id.clone(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
let fs = fs.clone();
|
|
||||||
move |model, should_be_favorite, cx| {
|
|
||||||
crate::favorite_models::toggle_in_settings(
|
|
||||||
model,
|
|
||||||
should_be_favorite,
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
false, // Do not use popover styles for the model picker
|
false, // Do not use popover styles for the model picker
|
||||||
self.focus_handle.clone(),
|
self.focus_handle.clone(),
|
||||||
@@ -300,7 +272,7 @@ impl ManageProfilesModal {
|
|||||||
model_picker,
|
model_picker,
|
||||||
_subscription: dismiss_subscription,
|
_subscription: dismiss_subscription,
|
||||||
};
|
};
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_mcp_tools(
|
fn configure_mcp_tools(
|
||||||
@@ -336,7 +308,7 @@ impl ManageProfilesModal {
|
|||||||
tool_picker,
|
tool_picker,
|
||||||
_subscription: dismiss_subscription,
|
_subscription: dismiss_subscription,
|
||||||
};
|
};
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_builtin_tools(
|
fn configure_builtin_tools(
|
||||||
@@ -377,7 +349,7 @@ impl ManageProfilesModal {
|
|||||||
tool_picker,
|
tool_picker,
|
||||||
_subscription: dismiss_subscription,
|
_subscription: dismiss_subscription,
|
||||||
};
|
};
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn confirm(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
@@ -397,42 +369,6 @@ impl ManageProfilesModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_profile(
|
|
||||||
&mut self,
|
|
||||||
profile_id: AgentProfileId,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
if builtin_profiles::is_builtin(&profile_id) {
|
|
||||||
self.view_profile(profile_id, window, cx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fs = self.fs.clone();
|
|
||||||
|
|
||||||
update_settings_file(fs, cx, move |settings, _cx| {
|
|
||||||
let Some(agent_settings) = settings.agent.as_mut() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(profiles) = agent_settings.profiles.as_mut() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
profiles.shift_remove(profile_id.0.as_ref());
|
|
||||||
|
|
||||||
if agent_settings
|
|
||||||
.default_profile
|
|
||||||
.as_deref()
|
|
||||||
.is_some_and(|default_profile| default_profile == profile_id.0.as_ref())
|
|
||||||
{
|
|
||||||
agent_settings.default_profile = Some(AgentProfileId::default().0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.choose_profile(window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::ChooseProfile { .. } => {
|
Mode::ChooseProfile { .. } => {
|
||||||
@@ -820,40 +756,6 @@ impl ManageProfilesModal {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.id("delete-profile")
|
|
||||||
.track_focus(&mode.delete_profile.focus_handle)
|
|
||||||
.on_action({
|
|
||||||
let profile_id = mode.profile_id.clone();
|
|
||||||
cx.listener(move |this, _: &menu::Confirm, window, cx| {
|
|
||||||
this.delete_profile(profile_id.clone(), window, cx);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
ListItem::new("delete-profile")
|
|
||||||
.toggle_state(
|
|
||||||
mode.delete_profile
|
|
||||||
.focus_handle
|
|
||||||
.contains_focused(window, cx),
|
|
||||||
)
|
|
||||||
.inset(true)
|
|
||||||
.spacing(ListItemSpacing::Sparse)
|
|
||||||
.start_slot(
|
|
||||||
Icon::new(IconName::Trash)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Error),
|
|
||||||
)
|
|
||||||
.child(Label::new("Delete Profile").color(Color::Error))
|
|
||||||
.disabled(builtin_profiles::is_builtin(&mode.profile_id))
|
|
||||||
.on_click({
|
|
||||||
let profile_id = mode.profile_id.clone();
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
this.delete_profile(profile_id.clone(), window, cx);
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(ListSeparator)
|
.child(ListSeparator)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
@@ -903,7 +805,6 @@ impl ManageProfilesModal {
|
|||||||
.entry(mode.configure_default_model)
|
.entry(mode.configure_default_model)
|
||||||
.entry(mode.configure_tools)
|
.entry(mode.configure_tools)
|
||||||
.entry(mode.configure_mcps)
|
.entry(mode.configure_mcps)
|
||||||
.entry(mode.delete_profile)
|
|
||||||
.entry(mode.cancel_item)
|
.entry(mode.cancel_item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -951,7 +852,7 @@ impl Render for ManageProfilesModal {
|
|||||||
.on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
|
.on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
|
||||||
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
|
.on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
|
||||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
this.focus_handle(cx).focus(window, cx);
|
this.focus_handle(cx).focus(window);
|
||||||
}))
|
}))
|
||||||
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
||||||
.child(match &self.mode {
|
.child(match &self.mode {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use gpui::{
|
|||||||
Global, SharedString, Subscription, Task, WeakEntity, Window, prelude::*,
|
Global, SharedString, Subscription, Task, WeakEntity, Window, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use language::{Buffer, Capability, OffsetRangeExt, Point};
|
use language::{Buffer, Capability, DiskState, OffsetRangeExt, Point};
|
||||||
use multi_buffer::PathKey;
|
use multi_buffer::PathKey;
|
||||||
use project::{Project, ProjectItem, ProjectPath};
|
use project::{Project, ProjectItem, ProjectPath};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
@@ -130,12 +130,7 @@ impl AgentDiffPane {
|
|||||||
.action_log()
|
.action_log()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.changed_buffers(cx);
|
.changed_buffers(cx);
|
||||||
let mut paths_to_delete = self
|
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
|
||||||
.multibuffer
|
|
||||||
.read(cx)
|
|
||||||
.paths()
|
|
||||||
.cloned()
|
|
||||||
.collect::<HashSet<_>>();
|
|
||||||
|
|
||||||
for (buffer, diff_handle) in changed_buffers {
|
for (buffer, diff_handle) in changed_buffers {
|
||||||
if buffer.read(cx).file().is_none() {
|
if buffer.read(cx).file().is_none() {
|
||||||
@@ -192,7 +187,7 @@ impl AgentDiffPane {
|
|||||||
&& buffer
|
&& buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
.is_some_and(|file| file.disk_state().is_deleted())
|
.is_some_and(|file| file.disk_state() == DiskState::Deleted)
|
||||||
{
|
{
|
||||||
editor.fold_buffer(snapshot.text.remote_id(), cx)
|
editor.fold_buffer(snapshot.text.remote_id(), cx)
|
||||||
}
|
}
|
||||||
@@ -212,10 +207,10 @@ impl AgentDiffPane {
|
|||||||
.focus_handle(cx)
|
.focus_handle(cx)
|
||||||
.contains_focused(window, cx)
|
.contains_focused(window, cx)
|
||||||
{
|
{
|
||||||
self.focus_handle.focus(window, cx);
|
self.focus_handle.focus(window);
|
||||||
} else if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).is_empty() {
|
} else if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).is_empty() {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
editor.focus_handle(cx).focus(window, cx);
|
editor.focus_handle(cx).focus(window);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,12 +869,12 @@ impl AgentDiffToolbar {
|
|||||||
match active_item {
|
match active_item {
|
||||||
AgentDiffToolbarItem::Pane(agent_diff) => {
|
AgentDiffToolbarItem::Pane(agent_diff) => {
|
||||||
if let Some(agent_diff) = agent_diff.upgrade() {
|
if let Some(agent_diff) = agent_diff.upgrade() {
|
||||||
agent_diff.focus_handle(cx).focus(window, cx);
|
agent_diff.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AgentDiffToolbarItem::Editor { editor, .. } => {
|
AgentDiffToolbarItem::Editor { editor, .. } => {
|
||||||
if let Some(editor) = editor.upgrade() {
|
if let Some(editor) = editor.upgrade() {
|
||||||
editor.read(cx).focus_handle(cx).focus(window, cx);
|
editor.read(cx).focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ModelUsageContext,
|
ModelUsageContext,
|
||||||
language_model_selector::{LanguageModelSelector, language_model_selector},
|
language_model_selector::{LanguageModelSelector, language_model_selector},
|
||||||
ui::ModelSelectorTooltip,
|
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Entity, FocusHandle, SharedString};
|
use gpui::{Entity, FocusHandle, SharedString};
|
||||||
use language_model::IconOrSvg;
|
|
||||||
use picker::popover_menu::PickerPopoverMenu;
|
use picker::popover_menu::PickerPopoverMenu;
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
|
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
|
||||||
|
use zed_actions::agent::ToggleModelSelector;
|
||||||
|
|
||||||
pub struct AgentModelSelector {
|
pub struct AgentModelSelector {
|
||||||
selector: Entity<LanguageModelSelector>,
|
selector: Entity<LanguageModelSelector>,
|
||||||
@@ -30,39 +29,26 @@ impl AgentModelSelector {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
selector: cx.new(move |cx| {
|
selector: cx.new(move |cx| {
|
||||||
|
let fs = fs.clone();
|
||||||
language_model_selector(
|
language_model_selector(
|
||||||
{
|
{
|
||||||
let model_context = model_usage_context.clone();
|
let model_context = model_usage_context.clone();
|
||||||
move |cx| model_context.configured_model(cx)
|
move |cx| model_context.configured_model(cx)
|
||||||
},
|
},
|
||||||
{
|
move |model, cx| {
|
||||||
let fs = fs.clone();
|
let provider = model.provider_id().0.to_string();
|
||||||
move |model, cx| {
|
let model_id = model.id().0.to_string();
|
||||||
let provider = model.provider_id().0.to_string();
|
match &model_usage_context {
|
||||||
let model_id = model.id().0.to_string();
|
ModelUsageContext::InlineAssistant => {
|
||||||
match &model_usage_context {
|
update_settings_file(fs.clone(), cx, move |settings, _cx| {
|
||||||
ModelUsageContext::InlineAssistant => {
|
settings
|
||||||
update_settings_file(fs.clone(), cx, move |settings, _cx| {
|
.agent
|
||||||
settings
|
.get_or_insert_default()
|
||||||
.agent
|
.set_inline_assistant_model(provider.clone(), model_id);
|
||||||
.get_or_insert_default()
|
});
|
||||||
.set_inline_assistant_model(provider.clone(), model_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
let fs = fs.clone();
|
|
||||||
move |model, should_be_favorite, cx| {
|
|
||||||
crate::favorite_models::toggle_in_settings(
|
|
||||||
model,
|
|
||||||
should_be_favorite,
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true, // Use popover styles for picker
|
true, // Use popover styles for picker
|
||||||
focus_handle_clone,
|
focus_handle_clone,
|
||||||
window,
|
window,
|
||||||
@@ -81,12 +67,6 @@ impl AgentModelSelector {
|
|||||||
pub fn active_model(&self, cx: &App) -> Option<language_model::ConfiguredModel> {
|
pub fn active_model(&self, cx: &App) -> Option<language_model::ConfiguredModel> {
|
||||||
self.selector.read(cx).delegate.active_model(cx)
|
self.selector.read(cx).delegate.active_model(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
self.selector.update(cx, |selector, cx| {
|
|
||||||
selector.delegate.cycle_favorite_models(window, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentModelSelector {
|
impl Render for AgentModelSelector {
|
||||||
@@ -104,30 +84,13 @@ impl Render for AgentModelSelector {
|
|||||||
Color::Muted
|
Color::Muted
|
||||||
};
|
};
|
||||||
|
|
||||||
let show_cycle_row = self.selector.read(cx).delegate.favorites_count() > 1;
|
|
||||||
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
let tooltip = Tooltip::element({
|
|
||||||
move |_, _cx| {
|
|
||||||
ModelSelectorTooltip::new(focus_handle.clone())
|
|
||||||
.show_cycle_row(show_cycle_row)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
PickerPopoverMenu::new(
|
PickerPopoverMenu::new(
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
.when_some(provider_icon, |this, icon| {
|
.when_some(provider_icon, |this, icon| {
|
||||||
this.child(
|
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
||||||
match icon {
|
|
||||||
IconOrSvg::Svg(path) => Icon::from_external_svg(path),
|
|
||||||
IconOrSvg::Icon(name) => Icon::new(name),
|
|
||||||
}
|
|
||||||
.color(color)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
.child(
|
.child(
|
||||||
@@ -139,9 +102,11 @@ impl Render for AgentModelSelector {
|
|||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::ChevronDown)
|
Icon::new(IconName::ChevronDown)
|
||||||
.color(color)
|
.color(color)
|
||||||
.size(IconSize::XSmall),
|
.size(IconSize::Small),
|
||||||
),
|
),
|
||||||
tooltip,
|
move |_window, cx| {
|
||||||
|
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||||
|
},
|
||||||
gpui::Corner::TopRight,
|
gpui::Corner::TopRight,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::{ops::Range, path::Path, rc::Rc, sync::Arc, time::Duration};
|
|||||||
|
|
||||||
use acp_thread::AcpThread;
|
use acp_thread::AcpThread;
|
||||||
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
|
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
|
||||||
use agent_servers::AgentServer;
|
|
||||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||||
use project::{
|
use project::{
|
||||||
ExternalAgentServerName,
|
ExternalAgentServerName,
|
||||||
@@ -260,7 +259,7 @@ impl AgentType {
|
|||||||
Self::Gemini => Some(IconName::AiGemini),
|
Self::Gemini => Some(IconName::AiGemini),
|
||||||
Self::ClaudeCode => Some(IconName::AiClaude),
|
Self::ClaudeCode => Some(IconName::AiClaude),
|
||||||
Self::Codex => Some(IconName::AiOpenAi),
|
Self::Codex => Some(IconName::AiOpenAi),
|
||||||
Self::Custom { .. } => Some(IconName::Sparkle),
|
Self::Custom { .. } => Some(IconName::Terminal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,7 +287,7 @@ impl ActiveView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn native_agent(
|
pub fn native_agent(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
history_store: Entity<agent::HistoryStore>,
|
history_store: Entity<agent::HistoryStore>,
|
||||||
@@ -443,7 +442,6 @@ pub struct AgentPanel {
|
|||||||
pending_serialization: Option<Task<Result<()>>>,
|
pending_serialization: Option<Task<Result<()>>>,
|
||||||
onboarding: Entity<AgentPanelOnboarding>,
|
onboarding: Entity<AgentPanelOnboarding>,
|
||||||
selected_agent: AgentType,
|
selected_agent: AgentType,
|
||||||
show_trust_workspace_message: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentPanel {
|
impl AgentPanel {
|
||||||
@@ -694,7 +692,6 @@ impl AgentPanel {
|
|||||||
history_store,
|
history_store,
|
||||||
selected_agent: AgentType::default(),
|
selected_agent: AgentType::default(),
|
||||||
loading: false,
|
loading: false,
|
||||||
show_trust_workspace_message: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial sync of agent servers from extensions
|
// Initial sync of agent servers from extensions
|
||||||
@@ -822,7 +819,7 @@ impl AgentPanel {
|
|||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
text_thread_editor.focus_handle(cx).focus(window, cx);
|
text_thread_editor.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn external_thread(
|
fn external_thread(
|
||||||
@@ -888,21 +885,36 @@ impl AgentPanel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let server = ext_agent.server(fs, history);
|
let server = ext_agent.server(fs, history);
|
||||||
this.update_in(cx, |agent_panel, window, cx| {
|
|
||||||
agent_panel._external_thread(
|
this.update_in(cx, |this, window, cx| {
|
||||||
server,
|
let selected_agent = ext_agent.into();
|
||||||
resume_thread,
|
if this.selected_agent != selected_agent {
|
||||||
summarize_thread,
|
this.selected_agent = selected_agent;
|
||||||
workspace,
|
this.serialize(cx);
|
||||||
project,
|
}
|
||||||
loading,
|
|
||||||
ext_agent,
|
let thread_view = cx.new(|cx| {
|
||||||
|
crate::acp::AcpThreadView::new(
|
||||||
|
server,
|
||||||
|
resume_thread,
|
||||||
|
summarize_thread,
|
||||||
|
workspace.clone(),
|
||||||
|
project,
|
||||||
|
this.history_store.clone(),
|
||||||
|
this.prompt_store.clone(),
|
||||||
|
!loading,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set_active_view(
|
||||||
|
ActiveView::ExternalAgentThread { thread_view },
|
||||||
|
!loading,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})?;
|
})
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
@@ -935,7 +947,7 @@ impl AgentPanel {
|
|||||||
if let Some(thread_view) = self.active_thread_view() {
|
if let Some(thread_view) = self.active_thread_view() {
|
||||||
thread_view.update(cx, |view, cx| {
|
thread_view.update(cx, |view, cx| {
|
||||||
view.expand_message_editor(&ExpandMessageEditor, window, cx);
|
view.expand_message_editor(&ExpandMessageEditor, window, cx);
|
||||||
view.focus_handle(cx).focus(window, cx);
|
view.focus_handle(cx).focus(window);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1016,12 +1028,12 @@ impl AgentPanel {
|
|||||||
|
|
||||||
match &self.active_view {
|
match &self.active_view {
|
||||||
ActiveView::ExternalAgentThread { thread_view } => {
|
ActiveView::ExternalAgentThread { thread_view } => {
|
||||||
thread_view.focus_handle(cx).focus(window, cx);
|
thread_view.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
ActiveView::TextThread {
|
ActiveView::TextThread {
|
||||||
text_thread_editor, ..
|
text_thread_editor, ..
|
||||||
} => {
|
} => {
|
||||||
text_thread_editor.focus_handle(cx).focus(window, cx);
|
text_thread_editor.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
ActiveView::History | ActiveView::Configuration => {}
|
ActiveView::History | ActiveView::Configuration => {}
|
||||||
}
|
}
|
||||||
@@ -1169,7 +1181,7 @@ impl AgentPanel {
|
|||||||
Self::handle_agent_configuration_event,
|
Self::handle_agent_configuration_event,
|
||||||
));
|
));
|
||||||
|
|
||||||
configuration.focus_handle(cx).focus(window, cx);
|
configuration.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1305,7 +1317,7 @@ impl AgentPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if focus {
|
if focus {
|
||||||
self.focus_handle(cx).focus(window, cx);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1465,47 +1477,6 @@ impl AgentPanel {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _external_thread(
|
|
||||||
&mut self,
|
|
||||||
server: Rc<dyn AgentServer>,
|
|
||||||
resume_thread: Option<DbThreadMetadata>,
|
|
||||||
summarize_thread: Option<DbThreadMetadata>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
|
||||||
project: Entity<Project>,
|
|
||||||
loading: bool,
|
|
||||||
ext_agent: ExternalAgent,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let selected_agent = AgentType::from(ext_agent);
|
|
||||||
if self.selected_agent != selected_agent {
|
|
||||||
self.selected_agent = selected_agent;
|
|
||||||
self.serialize(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
let thread_view = cx.new(|cx| {
|
|
||||||
crate::acp::AcpThreadView::new(
|
|
||||||
server,
|
|
||||||
resume_thread,
|
|
||||||
summarize_thread,
|
|
||||||
workspace.clone(),
|
|
||||||
project,
|
|
||||||
self.history_store.clone(),
|
|
||||||
self.prompt_store.clone(),
|
|
||||||
!loading,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.set_active_view(
|
|
||||||
ActiveView::ExternalAgentThread { thread_view },
|
|
||||||
!loading,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for AgentPanel {
|
impl Focusable for AgentPanel {
|
||||||
@@ -1620,19 +1591,14 @@ impl AgentPanel {
|
|||||||
|
|
||||||
let content = match &self.active_view {
|
let content = match &self.active_view {
|
||||||
ActiveView::ExternalAgentThread { thread_view } => {
|
ActiveView::ExternalAgentThread { thread_view } => {
|
||||||
let is_generating_title = thread_view
|
|
||||||
.read(cx)
|
|
||||||
.as_native_thread(cx)
|
|
||||||
.map_or(false, |t| t.read(cx).is_generating_title());
|
|
||||||
|
|
||||||
if let Some(title_editor) = thread_view.read(cx).title_editor() {
|
if let Some(title_editor) = thread_view.read(cx).title_editor() {
|
||||||
let container = div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.on_action({
|
.on_action({
|
||||||
let thread_view = thread_view.downgrade();
|
let thread_view = thread_view.downgrade();
|
||||||
move |_: &menu::Confirm, window, cx| {
|
move |_: &menu::Confirm, window, cx| {
|
||||||
if let Some(thread_view) = thread_view.upgrade() {
|
if let Some(thread_view) = thread_view.upgrade() {
|
||||||
thread_view.focus_handle(cx).focus(window, cx);
|
thread_view.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1640,25 +1606,12 @@ impl AgentPanel {
|
|||||||
let thread_view = thread_view.downgrade();
|
let thread_view = thread_view.downgrade();
|
||||||
move |_: &editor::actions::Cancel, window, cx| {
|
move |_: &editor::actions::Cancel, window, cx| {
|
||||||
if let Some(thread_view) = thread_view.upgrade() {
|
if let Some(thread_view) = thread_view.upgrade() {
|
||||||
thread_view.focus_handle(cx).focus(window, cx);
|
thread_view.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.child(title_editor);
|
.child(title_editor)
|
||||||
|
.into_any_element()
|
||||||
if is_generating_title {
|
|
||||||
container
|
|
||||||
.with_animation(
|
|
||||||
"generating_title",
|
|
||||||
Animation::new(Duration::from_secs(2))
|
|
||||||
.repeat()
|
|
||||||
.with_easing(pulsating_between(0.4, 0.8)),
|
|
||||||
|div, delta| div.opacity(delta),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
} else {
|
|
||||||
container.into_any_element()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Label::new(thread_view.read(cx).title(cx))
|
Label::new(thread_view.read(cx).title(cx))
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
@@ -1688,13 +1641,6 @@ impl AgentPanel {
|
|||||||
Label::new(LOADING_SUMMARY_PLACEHOLDER)
|
Label::new(LOADING_SUMMARY_PLACEHOLDER)
|
||||||
.truncate()
|
.truncate()
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.with_animation(
|
|
||||||
"generating_title",
|
|
||||||
Animation::new(Duration::from_secs(2))
|
|
||||||
.repeat()
|
|
||||||
.with_easing(pulsating_between(0.4, 0.8)),
|
|
||||||
|label, delta| label.alpha(delta),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1738,25 +1684,6 @@ impl AgentPanel {
|
|||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_regenerate_thread_title(thread_view: Entity<AcpThreadView>, cx: &mut App) {
|
|
||||||
thread_view.update(cx, |thread_view, cx| {
|
|
||||||
if let Some(thread) = thread_view.as_native_thread(cx) {
|
|
||||||
thread.update(cx, |thread, cx| {
|
|
||||||
thread.generate_title(cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_regenerate_text_thread_title(
|
|
||||||
text_thread_editor: Entity<TextThreadEditor>,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
text_thread_editor.update(cx, |text_thread_editor, cx| {
|
|
||||||
text_thread_editor.regenerate_summary(cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_panel_options_menu(
|
fn render_panel_options_menu(
|
||||||
&self,
|
&self,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
@@ -1776,35 +1703,6 @@ impl AgentPanel {
|
|||||||
|
|
||||||
let selected_agent = self.selected_agent.clone();
|
let selected_agent = self.selected_agent.clone();
|
||||||
|
|
||||||
let text_thread_view = match &self.active_view {
|
|
||||||
ActiveView::TextThread {
|
|
||||||
text_thread_editor, ..
|
|
||||||
} => Some(text_thread_editor.clone()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let text_thread_with_messages = match &self.active_view {
|
|
||||||
ActiveView::TextThread {
|
|
||||||
text_thread_editor, ..
|
|
||||||
} => text_thread_editor
|
|
||||||
.read(cx)
|
|
||||||
.text_thread()
|
|
||||||
.read(cx)
|
|
||||||
.messages(cx)
|
|
||||||
.any(|message| message.role == language_model::Role::Assistant),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let thread_view = match &self.active_view {
|
|
||||||
ActiveView::ExternalAgentThread { thread_view } => Some(thread_view.clone()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let thread_with_messages = match &self.active_view {
|
|
||||||
ActiveView::ExternalAgentThread { thread_view } => {
|
|
||||||
thread_view.read(cx).has_user_submitted_prompt(cx)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
PopoverMenu::new("agent-options-menu")
|
PopoverMenu::new("agent-options-menu")
|
||||||
.trigger_with_tooltip(
|
.trigger_with_tooltip(
|
||||||
IconButton::new("agent-options-menu", IconName::Ellipsis)
|
IconButton::new("agent-options-menu", IconName::Ellipsis)
|
||||||
@@ -1827,7 +1725,6 @@ impl AgentPanel {
|
|||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
|
Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
|
||||||
menu = menu.context(focus_handle.clone());
|
menu = menu.context(focus_handle.clone());
|
||||||
|
|
||||||
if let Some(usage) = usage {
|
if let Some(usage) = usage {
|
||||||
menu = menu
|
menu = menu
|
||||||
.header_with_link("Prompt Usage", "Manage", account_url.clone())
|
.header_with_link("Prompt Usage", "Manage", account_url.clone())
|
||||||
@@ -1865,38 +1762,6 @@ impl AgentPanel {
|
|||||||
.separator()
|
.separator()
|
||||||
}
|
}
|
||||||
|
|
||||||
if thread_with_messages | text_thread_with_messages {
|
|
||||||
menu = menu.header("Current Thread");
|
|
||||||
|
|
||||||
if let Some(text_thread_view) = text_thread_view.as_ref() {
|
|
||||||
menu = menu
|
|
||||||
.entry("Regenerate Thread Title", None, {
|
|
||||||
let text_thread_view = text_thread_view.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
Self::handle_regenerate_text_thread_title(
|
|
||||||
text_thread_view.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.separator();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(thread_view) = thread_view.as_ref() {
|
|
||||||
menu = menu
|
|
||||||
.entry("Regenerate Thread Title", None, {
|
|
||||||
let thread_view = thread_view.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
Self::handle_regenerate_thread_title(
|
|
||||||
thread_view.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.separator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = menu
|
menu = menu
|
||||||
.header("MCP Servers")
|
.header("MCP Servers")
|
||||||
.action(
|
.action(
|
||||||
@@ -1986,17 +1851,14 @@ impl AgentPanel {
|
|||||||
let agent_server_store = self.project.read(cx).agent_server_store().clone();
|
let agent_server_store = self.project.read(cx).agent_server_store().clone();
|
||||||
let focus_handle = self.focus_handle(cx);
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
let (selected_agent_custom_icon, selected_agent_label) =
|
// Get custom icon path for selected agent before building menu (to avoid borrow issues)
|
||||||
|
let selected_agent_custom_icon =
|
||||||
if let AgentType::Custom { name, .. } = &self.selected_agent {
|
if let AgentType::Custom { name, .. } = &self.selected_agent {
|
||||||
let store = agent_server_store.read(cx);
|
agent_server_store
|
||||||
let icon = store.agent_icon(&ExternalAgentServerName(name.clone()));
|
.read(cx)
|
||||||
|
.agent_icon(&ExternalAgentServerName(name.clone()))
|
||||||
let label = store
|
|
||||||
.agent_display_name(&ExternalAgentServerName(name.clone()))
|
|
||||||
.unwrap_or_else(|| self.selected_agent.label());
|
|
||||||
(icon, label)
|
|
||||||
} else {
|
} else {
|
||||||
(None, self.selected_agent.label())
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let active_thread = match &self.active_view {
|
let active_thread = match &self.active_view {
|
||||||
@@ -2228,7 +2090,7 @@ impl AgentPanel {
|
|||||||
if let Some(icon_path) = icon_path {
|
if let Some(icon_path) = icon_path {
|
||||||
entry = entry.custom_icon_svg(icon_path);
|
entry = entry.custom_icon_svg(icon_path);
|
||||||
} else {
|
} else {
|
||||||
entry = entry.icon(IconName::Sparkle);
|
entry = entry.icon(IconName::Terminal);
|
||||||
}
|
}
|
||||||
entry = entry
|
entry = entry
|
||||||
.when(
|
.when(
|
||||||
@@ -2292,6 +2154,8 @@ impl AgentPanel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let selected_agent_label = self.selected_agent.label();
|
||||||
|
|
||||||
let is_thread_loading = self
|
let is_thread_loading = self
|
||||||
.active_thread_view()
|
.active_thread_view()
|
||||||
.map(|thread| thread.read(cx).is_loading())
|
.map(|thread| thread.read(cx).is_loading())
|
||||||
@@ -2428,7 +2292,7 @@ impl AgentPanel {
|
|||||||
let history_is_empty = self.history_store.read(cx).is_empty(cx);
|
let history_is_empty = self.history_store.read(cx).is_empty(cx);
|
||||||
|
|
||||||
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
|
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
|
||||||
.visible_providers()
|
.providers()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|provider| {
|
.any(|provider| {
|
||||||
provider.is_authenticated(cx)
|
provider.is_authenticated(cx)
|
||||||
@@ -2692,38 +2556,6 @@ impl AgentPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_workspace_trust_message(&self, cx: &Context<Self>) -> Option<impl IntoElement> {
|
|
||||||
if !self.show_trust_workspace_message {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = "To protect your system, third-party code—like MCP servers—won't run until you mark this workspace as safe.";
|
|
||||||
|
|
||||||
Some(
|
|
||||||
Callout::new()
|
|
||||||
.icon(IconName::Warning)
|
|
||||||
.severity(Severity::Warning)
|
|
||||||
.border_position(ui::BorderPosition::Bottom)
|
|
||||||
.title("You're in Restricted Mode")
|
|
||||||
.description(description)
|
|
||||||
.actions_slot(
|
|
||||||
Button::new("open-trust-modal", "Configure Project Trust")
|
|
||||||
.label_size(LabelSize::Small)
|
|
||||||
.style(ButtonStyle::Outlined)
|
|
||||||
.on_click({
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
this.workspace
|
|
||||||
.update(cx, |workspace, cx| {
|
|
||||||
workspace
|
|
||||||
.show_worktree_trust_security_modal(true, window, cx)
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_context(&self) -> KeyContext {
|
fn key_context(&self) -> KeyContext {
|
||||||
let mut key_context = KeyContext::new_with_defaults();
|
let mut key_context = KeyContext::new_with_defaults();
|
||||||
key_context.add("AgentPanel");
|
key_context.add("AgentPanel");
|
||||||
@@ -2776,7 +2608,6 @@ impl Render for AgentPanel {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.child(self.render_toolbar(window, cx))
|
.child(self.render_toolbar(window, cx))
|
||||||
.children(self.render_workspace_trust_message(cx))
|
|
||||||
.children(self.render_onboarding(window, cx))
|
.children(self.render_onboarding(window, cx))
|
||||||
.map(|parent| match &self.active_view {
|
.map(|parent| match &self.active_view {
|
||||||
ActiveView::ExternalAgentThread { thread_view, .. } => parent
|
ActiveView::ExternalAgentThread { thread_view, .. } => parent
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pub mod acp;
|
mod acp;
|
||||||
mod agent_configuration;
|
mod agent_configuration;
|
||||||
mod agent_diff;
|
mod agent_diff;
|
||||||
mod agent_model_selector;
|
mod agent_model_selector;
|
||||||
@@ -7,7 +7,8 @@ mod buffer_codegen;
|
|||||||
mod completion_provider;
|
mod completion_provider;
|
||||||
mod context;
|
mod context;
|
||||||
mod context_server_configuration;
|
mod context_server_configuration;
|
||||||
mod favorite_models;
|
#[cfg(test)]
|
||||||
|
mod evals;
|
||||||
mod inline_assistant;
|
mod inline_assistant;
|
||||||
mod inline_prompt_editor;
|
mod inline_prompt_editor;
|
||||||
mod language_model_selector;
|
mod language_model_selector;
|
||||||
@@ -27,7 +28,7 @@ use agent_settings::{AgentProfileId, AgentSettings};
|
|||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _};
|
use feature_flags::FeatureFlagAppExt as _;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Action, App, Entity, SharedString, actions};
|
use gpui::{Action, App, Entity, SharedString, actions};
|
||||||
use language::{
|
use language::{
|
||||||
@@ -68,8 +69,6 @@ actions!(
|
|||||||
ToggleProfileSelector,
|
ToggleProfileSelector,
|
||||||
/// Cycles through available session modes.
|
/// Cycles through available session modes.
|
||||||
CycleModeSelector,
|
CycleModeSelector,
|
||||||
/// Cycles through favorited models in the ACP model selector.
|
|
||||||
CycleFavoriteModels,
|
|
||||||
/// Expands the message editor to full size.
|
/// Expands the message editor to full size.
|
||||||
ExpandMessageEditor,
|
ExpandMessageEditor,
|
||||||
/// Removes all thread history.
|
/// Removes all thread history.
|
||||||
@@ -217,7 +216,7 @@ pub fn init(
|
|||||||
is_eval: bool,
|
is_eval: bool,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
assistant_text_thread::init(client, cx);
|
assistant_text_thread::init(client.clone(), cx);
|
||||||
rules_library::init(cx);
|
rules_library::init(cx);
|
||||||
if !is_eval {
|
if !is_eval {
|
||||||
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
// Initializing the language model from the user settings messes with the eval, so we only initialize them when
|
||||||
@@ -230,8 +229,13 @@ pub fn init(
|
|||||||
TextThreadEditor::init(cx);
|
TextThreadEditor::init(cx);
|
||||||
|
|
||||||
register_slash_commands(cx);
|
register_slash_commands(cx);
|
||||||
inline_assistant::init(fs.clone(), prompt_builder.clone(), cx);
|
inline_assistant::init(
|
||||||
terminal_inline_assistant::init(fs.clone(), prompt_builder, cx);
|
fs.clone(),
|
||||||
|
prompt_builder.clone(),
|
||||||
|
client.telemetry().clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
terminal_inline_assistant::init(fs.clone(), prompt_builder, client.telemetry().clone(), cx);
|
||||||
cx.observe_new(move |workspace, window, cx| {
|
cx.observe_new(move |workspace, window, cx| {
|
||||||
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
||||||
})
|
})
|
||||||
@@ -247,31 +251,23 @@ pub fn init(
|
|||||||
update_command_palette_filter(app_cx);
|
update_command_palette_filter(app_cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.on_flags_ready(|_, cx| {
|
|
||||||
update_command_palette_filter(cx);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_command_palette_filter(cx: &mut App) {
|
fn update_command_palette_filter(cx: &mut App) {
|
||||||
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
|
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
|
||||||
let agent_enabled = AgentSettings::get_global(cx).enabled;
|
let agent_enabled = AgentSettings::get_global(cx).enabled;
|
||||||
let agent_v2_enabled = cx.has_flag::<AgentV2FeatureFlag>();
|
|
||||||
let edit_prediction_provider = AllLanguageSettings::get_global(cx)
|
let edit_prediction_provider = AllLanguageSettings::get_global(cx)
|
||||||
.edit_predictions
|
.edit_predictions
|
||||||
.provider;
|
.provider;
|
||||||
|
|
||||||
CommandPaletteFilter::update_global(cx, |filter, _| {
|
CommandPaletteFilter::update_global(cx, |filter, _| {
|
||||||
use editor::actions::{
|
use editor::actions::{
|
||||||
AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction,
|
AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
|
||||||
NextEditPrediction, PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
|
PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
|
||||||
};
|
};
|
||||||
let edit_prediction_actions = [
|
let edit_prediction_actions = [
|
||||||
TypeId::of::<AcceptEditPrediction>(),
|
TypeId::of::<AcceptEditPrediction>(),
|
||||||
TypeId::of::<AcceptNextWordEditPrediction>(),
|
TypeId::of::<AcceptPartialEditPrediction>(),
|
||||||
TypeId::of::<AcceptNextLineEditPrediction>(),
|
|
||||||
TypeId::of::<AcceptEditPrediction>(),
|
|
||||||
TypeId::of::<ShowEditPrediction>(),
|
TypeId::of::<ShowEditPrediction>(),
|
||||||
TypeId::of::<NextEditPrediction>(),
|
TypeId::of::<NextEditPrediction>(),
|
||||||
TypeId::of::<PreviousEditPrediction>(),
|
TypeId::of::<PreviousEditPrediction>(),
|
||||||
@@ -280,7 +276,6 @@ fn update_command_palette_filter(cx: &mut App) {
|
|||||||
|
|
||||||
if disable_ai {
|
if disable_ai {
|
||||||
filter.hide_namespace("agent");
|
filter.hide_namespace("agent");
|
||||||
filter.hide_namespace("agents");
|
|
||||||
filter.hide_namespace("assistant");
|
filter.hide_namespace("assistant");
|
||||||
filter.hide_namespace("copilot");
|
filter.hide_namespace("copilot");
|
||||||
filter.hide_namespace("supermaven");
|
filter.hide_namespace("supermaven");
|
||||||
@@ -292,10 +287,8 @@ fn update_command_palette_filter(cx: &mut App) {
|
|||||||
} else {
|
} else {
|
||||||
if agent_enabled {
|
if agent_enabled {
|
||||||
filter.show_namespace("agent");
|
filter.show_namespace("agent");
|
||||||
filter.show_namespace("agents");
|
|
||||||
} else {
|
} else {
|
||||||
filter.hide_namespace("agent");
|
filter.hide_namespace("agent");
|
||||||
filter.hide_namespace("agents");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.show_namespace("assistant");
|
filter.show_namespace("assistant");
|
||||||
@@ -331,9 +324,6 @@ fn update_command_palette_filter(cx: &mut App) {
|
|||||||
|
|
||||||
filter.show_namespace("zed_predict_onboarding");
|
filter.show_namespace("zed_predict_onboarding");
|
||||||
filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
|
filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
|
||||||
if !agent_v2_enabled {
|
|
||||||
filter.hide_action_types(&[TypeId::of::<zed_actions::agent::ToggleAgentPane>()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -348,8 +338,7 @@ fn init_language_model_settings(cx: &mut App) {
|
|||||||
|_, event: &language_model::Event, cx| match event {
|
|_, event: &language_model::Event, cx| match event {
|
||||||
language_model::Event::ProviderStateChanged(_)
|
language_model::Event::ProviderStateChanged(_)
|
||||||
| language_model::Event::AddedProvider(_)
|
| language_model::Event::AddedProvider(_)
|
||||||
| language_model::Event::RemovedProvider(_)
|
| language_model::Event::RemovedProvider(_) => {
|
||||||
| language_model::Event::ProvidersChanged => {
|
|
||||||
update_active_language_model_from_settings(cx);
|
update_active_language_model_from_settings(cx);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -433,7 +422,7 @@ mod tests {
|
|||||||
use gpui::{BorrowAppContext, TestAppContext, px};
|
use gpui::{BorrowAppContext, TestAppContext, px};
|
||||||
use project::DisableAiSettings;
|
use project::DisableAiSettings;
|
||||||
use settings::{
|
use settings::{
|
||||||
DefaultAgentView, DockPosition, DockSide, NotifyWhenAgentWaiting, Settings, SettingsStore,
|
DefaultAgentView, DockPosition, NotifyWhenAgentWaiting, Settings, SettingsStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@@ -452,16 +441,13 @@ mod tests {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
button: true,
|
button: true,
|
||||||
dock: DockPosition::Right,
|
dock: DockPosition::Right,
|
||||||
agents_panel_dock: DockSide::Left,
|
|
||||||
default_width: px(300.),
|
default_width: px(300.),
|
||||||
default_height: px(600.),
|
default_height: px(600.),
|
||||||
default_model: None,
|
default_model: None,
|
||||||
inline_assistant_model: None,
|
inline_assistant_model: None,
|
||||||
inline_assistant_use_streaming_tools: false,
|
|
||||||
commit_message_model: None,
|
commit_message_model: None,
|
||||||
thread_summary_model: None,
|
thread_summary_model: None,
|
||||||
inline_alternatives: vec![],
|
inline_alternatives: vec![],
|
||||||
favorite_models: vec![],
|
|
||||||
default_profile: AgentProfileId::default(),
|
default_profile: AgentProfileId::default(),
|
||||||
default_view: DefaultAgentView::Thread,
|
default_view: DefaultAgentView::Thread,
|
||||||
profiles: Default::default(),
|
profiles: Default::default(),
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
use crate::{context::LoadedContext, inline_prompt_editor::CodegenStatus};
|
use crate::{context::LoadedContext, inline_prompt_editor::CodegenStatus};
|
||||||
use agent_settings::AgentSettings;
|
use agent_settings::AgentSettings;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use uuid::Uuid;
|
use client::telemetry::Telemetry;
|
||||||
|
|
||||||
use cloud_llm_client::CompletionIntent;
|
use cloud_llm_client::CompletionIntent;
|
||||||
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 feature_flags::{FeatureFlagAppExt as _, InlineAssistantUseToolFeatureFlag};
|
use feature_flags::{FeatureFlagAppExt as _, InlineAssistantV2FeatureFlag};
|
||||||
use futures::{
|
use futures::{
|
||||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
future::{LocalBoxFuture, Shared},
|
future::{LocalBoxFuture, Shared},
|
||||||
join,
|
join,
|
||||||
stream::BoxStream,
|
|
||||||
};
|
};
|
||||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
|
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
|
||||||
use language::{Buffer, IndentKind, LanguageName, Point, TransactionId, line_diff};
|
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
LanguageModel, LanguageModelCompletionError, LanguageModelRegistry, LanguageModelRequest,
|
||||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelTextStream, Role,
|
||||||
LanguageModelRequestTool, LanguageModelTextStream, LanguageModelToolChoice,
|
report_assistant_event,
|
||||||
LanguageModelToolUse, Role, TokenUsage,
|
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -28,7 +25,6 @@ use prompt_store::PromptBuilder;
|
|||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
|
||||||
use smol::future::FutureExt;
|
use smol::future::FutureExt;
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
@@ -41,24 +37,28 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||||
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
|
use ui::SharedString;
|
||||||
|
|
||||||
/// Use this tool when you cannot or should not make a rewrite. This includes:
|
/// Use this tool to provide a message to the user when you're unable to complete a task.
|
||||||
/// - The user's request is unclear, ambiguous, or nonsensical
|
|
||||||
/// - The requested change cannot be made by only editing the <rewrite_this> section
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct FailureMessageInput {
|
pub struct FailureMessageInput {
|
||||||
/// A brief message to the user explaining why you're unable to fulfill the request or to ask a question about the request.
|
/// A brief message to the user explaining why you're unable to fulfill the request or to ask a question about the request.
|
||||||
#[serde(default)]
|
///
|
||||||
|
/// The message may use markdown formatting if you wish.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.
|
/// Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.
|
||||||
/// Only use this tool when you are confident you understand the user's request and can fulfill it
|
|
||||||
/// by editing the marked section.
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct RewriteSectionInput {
|
pub struct RewriteSectionInput {
|
||||||
|
/// A brief description of the edit you have made.
|
||||||
|
///
|
||||||
|
/// The description may use markdown formatting if you wish.
|
||||||
|
/// This is optional - if the edit is simple or obvious, you should leave it empty.
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
/// The text to replace the section with.
|
/// The text to replace the section with.
|
||||||
#[serde(default)]
|
|
||||||
pub replacement_text: String,
|
pub replacement_text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,20 +70,17 @@ pub struct BufferCodegen {
|
|||||||
buffer: Entity<MultiBuffer>,
|
buffer: Entity<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
initial_transaction_id: Option<TransactionId>,
|
initial_transaction_id: Option<TransactionId>,
|
||||||
|
telemetry: Arc<Telemetry>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
pub is_insertion: bool,
|
pub is_insertion: bool,
|
||||||
session_id: Uuid,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const REWRITE_SECTION_TOOL_NAME: &str = "rewrite_section";
|
|
||||||
pub const FAILURE_MESSAGE_TOOL_NAME: &str = "failure_message";
|
|
||||||
|
|
||||||
impl BufferCodegen {
|
impl BufferCodegen {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
buffer: Entity<MultiBuffer>,
|
buffer: Entity<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
initial_transaction_id: Option<TransactionId>,
|
initial_transaction_id: Option<TransactionId>,
|
||||||
session_id: Uuid,
|
telemetry: Arc<Telemetry>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -92,8 +89,8 @@ impl BufferCodegen {
|
|||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
false,
|
false,
|
||||||
|
Some(telemetry.clone()),
|
||||||
builder.clone(),
|
builder.clone(),
|
||||||
session_id,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -106,8 +103,8 @@ impl BufferCodegen {
|
|||||||
buffer,
|
buffer,
|
||||||
range,
|
range,
|
||||||
initial_transaction_id,
|
initial_transaction_id,
|
||||||
|
telemetry,
|
||||||
builder,
|
builder,
|
||||||
session_id,
|
|
||||||
};
|
};
|
||||||
this.activate(0, cx);
|
this.activate(0, cx);
|
||||||
this
|
this
|
||||||
@@ -130,10 +127,6 @@ impl BufferCodegen {
|
|||||||
&self.alternatives[self.active_alternative]
|
&self.alternatives[self.active_alternative]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_name(&self, cx: &App) -> Option<LanguageName> {
|
|
||||||
self.active_alternative().read(cx).language_name(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status<'a>(&self, cx: &'a App) -> &'a CodegenStatus {
|
pub fn status<'a>(&self, cx: &'a App) -> &'a CodegenStatus {
|
||||||
&self.active_alternative().read(cx).status
|
&self.active_alternative().read(cx).status
|
||||||
}
|
}
|
||||||
@@ -192,8 +185,8 @@ impl BufferCodegen {
|
|||||||
self.buffer.clone(),
|
self.buffer.clone(),
|
||||||
self.range.clone(),
|
self.range.clone(),
|
||||||
false,
|
false,
|
||||||
|
Some(self.telemetry.clone()),
|
||||||
self.builder.clone(),
|
self.builder.clone(),
|
||||||
self.session_id,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
@@ -256,10 +249,6 @@ impl BufferCodegen {
|
|||||||
pub fn selected_text<'a>(&self, cx: &'a App) -> Option<&'a str> {
|
pub fn selected_text<'a>(&self, cx: &'a App) -> Option<&'a str> {
|
||||||
self.active_alternative().read(cx).selected_text()
|
self.active_alternative().read(cx).selected_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_id(&self) -> Uuid {
|
|
||||||
self.session_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||||
@@ -275,6 +264,7 @@ pub struct CodegenAlternative {
|
|||||||
status: CodegenStatus,
|
status: CodegenStatus,
|
||||||
generation: Task<()>,
|
generation: Task<()>,
|
||||||
diff: Diff,
|
diff: Diff,
|
||||||
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
_subscription: gpui::Subscription,
|
_subscription: gpui::Subscription,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
active: bool,
|
active: bool,
|
||||||
@@ -284,9 +274,7 @@ pub struct CodegenAlternative {
|
|||||||
completion: Option<String>,
|
completion: Option<String>,
|
||||||
selected_text: Option<String>,
|
selected_text: Option<String>,
|
||||||
pub message_id: Option<String>,
|
pub message_id: Option<String>,
|
||||||
session_id: Uuid,
|
pub model_explanation: Option<SharedString>,
|
||||||
pub description: Option<String>,
|
|
||||||
pub failure: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
||||||
@@ -296,8 +284,8 @@ impl CodegenAlternative {
|
|||||||
buffer: Entity<MultiBuffer>,
|
buffer: Entity<MultiBuffer>,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
active: bool,
|
active: bool,
|
||||||
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
builder: Arc<PromptBuilder>,
|
builder: Arc<PromptBuilder>,
|
||||||
session_id: Uuid,
|
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let snapshot = buffer.read(cx).snapshot(cx);
|
let snapshot = buffer.read(cx).snapshot(cx);
|
||||||
@@ -336,6 +324,7 @@ impl CodegenAlternative {
|
|||||||
status: CodegenStatus::Idle,
|
status: CodegenStatus::Idle,
|
||||||
generation: Task::ready(()),
|
generation: Task::ready(()),
|
||||||
diff: Diff::default(),
|
diff: Diff::default(),
|
||||||
|
telemetry,
|
||||||
builder,
|
builder,
|
||||||
active: active,
|
active: active,
|
||||||
edits: Vec::new(),
|
edits: Vec::new(),
|
||||||
@@ -344,20 +333,11 @@ impl CodegenAlternative {
|
|||||||
elapsed_time: None,
|
elapsed_time: None,
|
||||||
completion: None,
|
completion: None,
|
||||||
selected_text: None,
|
selected_text: None,
|
||||||
session_id,
|
model_explanation: None,
|
||||||
description: None,
|
|
||||||
failure: None,
|
|
||||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_name(&self, cx: &App) -> Option<LanguageName> {
|
|
||||||
self.old_buffer
|
|
||||||
.read(cx)
|
|
||||||
.language()
|
|
||||||
.map(|language| language.name())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active(&mut self, active: bool, cx: &mut Context<Self>) {
|
pub fn set_active(&mut self, active: bool, cx: &mut Context<Self>) {
|
||||||
if active != self.active {
|
if active != self.active {
|
||||||
self.active = active;
|
self.active = active;
|
||||||
@@ -399,12 +379,6 @@ impl CodegenAlternative {
|
|||||||
&self.last_equal_ranges
|
&self.last_equal_ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_streaming_tools(model: &dyn LanguageModel, cx: &App) -> bool {
|
|
||||||
model.supports_streaming_tools()
|
|
||||||
&& cx.has_flag::<InlineAssistantUseToolFeatureFlag>()
|
|
||||||
&& AgentSettings::get_global(cx).inline_assistant_use_streaming_tools
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(
|
pub fn start(
|
||||||
&mut self,
|
&mut self,
|
||||||
user_prompt: String,
|
user_prompt: String,
|
||||||
@@ -412,9 +386,6 @@ impl CodegenAlternative {
|
|||||||
model: Arc<dyn LanguageModel>,
|
model: Arc<dyn LanguageModel>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Clear the model explanation since the user has started a new generation.
|
|
||||||
self.description = None;
|
|
||||||
|
|
||||||
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.undo_transaction(transformation_transaction_id, cx);
|
buffer.undo_transaction(transformation_transaction_id, cx);
|
||||||
@@ -423,35 +394,33 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
|
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
|
||||||
|
|
||||||
if Self::use_streaming_tools(model.as_ref(), cx) {
|
let api_key = model.api_key(cx);
|
||||||
|
let telemetry_id = model.telemetry_id();
|
||||||
|
let provider_id = model.provider_id();
|
||||||
|
|
||||||
|
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||||
let completion_events = cx.spawn({
|
let tool_use =
|
||||||
let model = model.clone();
|
cx.spawn(async move |_, cx| model.stream_completion_tool(request.await, cx).await);
|
||||||
async move |_, cx| model.stream_completion(request.await, cx).await
|
self.handle_tool_use(telemetry_id, provider_id.to_string(), api_key, tool_use, cx);
|
||||||
});
|
|
||||||
self.generation = self.handle_completion(model, completion_events, cx);
|
|
||||||
} else {
|
} else {
|
||||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||||
if user_prompt.trim().to_lowercase() == "delete" {
|
if user_prompt.trim().to_lowercase() == "delete" {
|
||||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||||
} else {
|
} else {
|
||||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||||
cx.spawn({
|
cx.spawn(async move |_, cx| {
|
||||||
let model = model.clone();
|
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||||
async move |_, cx| {
|
|
||||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
};
|
};
|
||||||
self.generation =
|
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||||
self.handle_stream(model, /* strip_invalid_spans: */ true, stream, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_request_tools(
|
fn build_request_v2(
|
||||||
&self,
|
&self,
|
||||||
model: &Arc<dyn LanguageModel>,
|
model: &Arc<dyn LanguageModel>,
|
||||||
user_prompt: String,
|
user_prompt: String,
|
||||||
@@ -487,7 +456,7 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
let system_prompt = self
|
let system_prompt = self
|
||||||
.builder
|
.builder
|
||||||
.generate_inline_transformation_prompt_tools(
|
.generate_inline_transformation_prompt_v2(
|
||||||
language_name,
|
language_name,
|
||||||
buffer,
|
buffer,
|
||||||
range.start.0..range.end.0,
|
range.start.0..range.end.0,
|
||||||
@@ -497,9 +466,6 @@ impl CodegenAlternative {
|
|||||||
let temperature = AgentSettings::temperature_for_model(model, cx);
|
let temperature = AgentSettings::temperature_for_model(model, cx);
|
||||||
|
|
||||||
let tool_input_format = model.tool_input_format();
|
let tool_input_format = model.tool_input_format();
|
||||||
let tool_choice = model
|
|
||||||
.supports_tool_choice(LanguageModelToolChoice::Any)
|
|
||||||
.then_some(LanguageModelToolChoice::Any);
|
|
||||||
|
|
||||||
Ok(cx.spawn(async move |_cx| {
|
Ok(cx.spawn(async move |_cx| {
|
||||||
let mut messages = vec![LanguageModelRequestMessage {
|
let mut messages = vec![LanguageModelRequestMessage {
|
||||||
@@ -525,12 +491,12 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
let tools = vec![
|
let tools = vec![
|
||||||
LanguageModelRequestTool {
|
LanguageModelRequestTool {
|
||||||
name: REWRITE_SECTION_TOOL_NAME.to_string(),
|
name: "rewrite_section".to_string(),
|
||||||
description: "Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.".to_string(),
|
description: "Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.".to_string(),
|
||||||
input_schema: language_model::tool_schema::root_schema_for::<RewriteSectionInput>(tool_input_format).to_value(),
|
input_schema: language_model::tool_schema::root_schema_for::<RewriteSectionInput>(tool_input_format).to_value(),
|
||||||
},
|
},
|
||||||
LanguageModelRequestTool {
|
LanguageModelRequestTool {
|
||||||
name: FAILURE_MESSAGE_TOOL_NAME.to_string(),
|
name: "failure_message".to_string(),
|
||||||
description: "Use this tool to provide a message to the user when you're unable to complete a task.".to_string(),
|
description: "Use this tool to provide a message to the user when you're unable to complete a task.".to_string(),
|
||||||
input_schema: language_model::tool_schema::root_schema_for::<FailureMessageInput>(tool_input_format).to_value(),
|
input_schema: language_model::tool_schema::root_schema_for::<FailureMessageInput>(tool_input_format).to_value(),
|
||||||
},
|
},
|
||||||
@@ -542,7 +508,7 @@ impl CodegenAlternative {
|
|||||||
intent: Some(CompletionIntent::InlineAssist),
|
intent: Some(CompletionIntent::InlineAssist),
|
||||||
mode: None,
|
mode: None,
|
||||||
tools,
|
tools,
|
||||||
tool_choice,
|
tool_choice: None,
|
||||||
stop: Vec::new(),
|
stop: Vec::new(),
|
||||||
temperature,
|
temperature,
|
||||||
messages,
|
messages,
|
||||||
@@ -558,8 +524,8 @@ impl CodegenAlternative {
|
|||||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<Task<LanguageModelRequest>> {
|
) -> Result<Task<LanguageModelRequest>> {
|
||||||
if Self::use_streaming_tools(model.as_ref(), cx) {
|
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||||
return self.build_request_tools(model, user_prompt, context_task, cx);
|
return self.build_request_v2(model, user_prompt, context_task, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
@@ -632,15 +598,12 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
pub fn handle_stream(
|
pub fn handle_stream(
|
||||||
&mut self,
|
&mut self,
|
||||||
model: Arc<dyn LanguageModel>,
|
model_telemetry_id: String,
|
||||||
strip_invalid_spans: bool,
|
model_provider_id: 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 Context<Self>,
|
||||||
) -> Task<()> {
|
) {
|
||||||
let anthropic_reporter = language_model::AnthropicEventReporter::new(&model, cx);
|
|
||||||
let session_id = self.session_id;
|
|
||||||
let model_telemetry_id = model.telemetry_id();
|
|
||||||
let model_provider_id = model.provider_id().to_string();
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Make a new snapshot and re-resolve anchor in case the document was modified.
|
// Make a new snapshot and re-resolve anchor in case the document was modified.
|
||||||
@@ -678,6 +641,8 @@ impl CodegenAlternative {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let http_client = cx.http_client();
|
||||||
|
let telemetry = self.telemetry.clone();
|
||||||
let language_name = {
|
let language_name = {
|
||||||
let multibuffer = self.buffer.read(cx);
|
let multibuffer = self.buffer.read(cx);
|
||||||
let snapshot = multibuffer.snapshot(cx);
|
let snapshot = multibuffer.snapshot(cx);
|
||||||
@@ -694,8 +659,7 @@ impl CodegenAlternative {
|
|||||||
let completion = Arc::new(Mutex::new(String::new()));
|
let completion = Arc::new(Mutex::new(String::new()));
|
||||||
let completion_clone = completion.clone();
|
let completion_clone = completion.clone();
|
||||||
|
|
||||||
cx.notify();
|
self.generation = cx.spawn(async move |codegen, cx| {
|
||||||
cx.spawn(async move |codegen, cx| {
|
|
||||||
let stream = stream.await;
|
let stream = stream.await;
|
||||||
|
|
||||||
let token_usage = stream
|
let token_usage = stream
|
||||||
@@ -710,25 +674,17 @@ impl CodegenAlternative {
|
|||||||
let model_telemetry_id = model_telemetry_id.clone();
|
let model_telemetry_id = model_telemetry_id.clone();
|
||||||
let model_provider_id = model_provider_id.clone();
|
let model_provider_id = model_provider_id.clone();
|
||||||
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
let message_id = message_id.clone();
|
let message_id = message_id.clone();
|
||||||
let line_based_stream_diff: Task<anyhow::Result<()>> = cx.background_spawn({
|
let line_based_stream_diff: Task<anyhow::Result<()>> =
|
||||||
let anthropic_reporter = anthropic_reporter.clone();
|
cx.background_spawn(async move {
|
||||||
let language_name = language_name.clone();
|
|
||||||
async move {
|
|
||||||
let mut response_latency = None;
|
let mut response_latency = None;
|
||||||
let request_start = Instant::now();
|
let request_start = Instant::now();
|
||||||
let diff = async {
|
let diff = async {
|
||||||
let raw_stream = stream?.stream.map_err(|error| error.into());
|
let chunks = StripInvalidSpans::new(
|
||||||
|
stream?.stream.map_err(|error| error.into()),
|
||||||
let stripped;
|
);
|
||||||
let mut chunks: Pin<Box<dyn Stream<Item = Result<String>> + Send>> =
|
futures::pin_mut!(chunks);
|
||||||
if strip_invalid_spans {
|
|
||||||
stripped = StripInvalidSpans::new(raw_stream);
|
|
||||||
Box::pin(stripped)
|
|
||||||
} else {
|
|
||||||
Box::pin(raw_stream)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||||
let mut line_diff = LineDiff::default();
|
let mut line_diff = LineDiff::default();
|
||||||
|
|
||||||
@@ -817,30 +773,27 @@ impl CodegenAlternative {
|
|||||||
let result = diff.await;
|
let result = diff.await;
|
||||||
|
|
||||||
let error_message = result.as_ref().err().map(|error| error.to_string());
|
let error_message = result.as_ref().err().map(|error| error.to_string());
|
||||||
telemetry::event!(
|
report_assistant_event(
|
||||||
"Assistant Responded",
|
AssistantEventData {
|
||||||
kind = "inline",
|
conversation_id: None,
|
||||||
phase = "response",
|
message_id,
|
||||||
session_id = session_id.to_string(),
|
kind: AssistantKind::Inline,
|
||||||
model = model_telemetry_id,
|
phase: AssistantPhase::Response,
|
||||||
model_provider = model_provider_id,
|
model: model_telemetry_id,
|
||||||
language_name = language_name.as_ref().map(|n| n.to_string()),
|
model_provider: model_provider_id,
|
||||||
message_id = message_id.as_deref(),
|
response_latency,
|
||||||
response_latency = response_latency,
|
error_message,
|
||||||
error_message = error_message.as_deref(),
|
language_name: language_name.map(|name| name.to_proto()),
|
||||||
|
},
|
||||||
|
telemetry,
|
||||||
|
http_client,
|
||||||
|
model_api_key,
|
||||||
|
&executor,
|
||||||
);
|
);
|
||||||
|
|
||||||
anthropic_reporter.report(language_model::AnthropicEventData {
|
|
||||||
completion_type: language_model::AnthropicCompletionType::Editor,
|
|
||||||
event: language_model::AnthropicEventType::Response,
|
|
||||||
language_name: language_name.map(|n| n.to_string()),
|
|
||||||
message_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
while let Some((char_ops, line_ops)) = diff_rx.next().await {
|
||||||
codegen.update(cx, |codegen, cx| {
|
codegen.update(cx, |codegen, cx| {
|
||||||
@@ -923,23 +876,14 @@ impl CodegenAlternative {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
});
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_completion(&self) -> Option<String> {
|
pub fn current_completion(&self) -> Option<String> {
|
||||||
self.completion.clone()
|
self.completion.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn current_description(&self) -> Option<String> {
|
|
||||||
self.description.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn current_failure(&self) -> Option<String> {
|
|
||||||
self.failure.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected_text(&self) -> Option<&str> {
|
pub fn selected_text(&self) -> Option<&str> {
|
||||||
self.selected_text.as_deref()
|
self.selected_text.as_deref()
|
||||||
}
|
}
|
||||||
@@ -1116,27 +1060,21 @@ impl CodegenAlternative {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_completion(
|
fn handle_tool_use(
|
||||||
&mut self,
|
&mut self,
|
||||||
model: Arc<dyn LanguageModel>,
|
_telemetry_id: String,
|
||||||
completion_stream: Task<
|
_provider_id: String,
|
||||||
Result<
|
_api_key: Option<String>,
|
||||||
BoxStream<
|
tool_use: impl 'static
|
||||||
'static,
|
+ Future<
|
||||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
Output = Result<language_model::LanguageModelToolUse, LanguageModelCompletionError>,
|
||||||
>,
|
|
||||||
LanguageModelCompletionError,
|
|
||||||
>,
|
|
||||||
>,
|
>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<()> {
|
) {
|
||||||
self.diff = Diff::default();
|
self.diff = Diff::default();
|
||||||
self.status = CodegenStatus::Pending;
|
self.status = CodegenStatus::Pending;
|
||||||
|
|
||||||
cx.notify();
|
self.generation = cx.spawn(async move |codegen, cx| {
|
||||||
// Leaving this in generation so that STOP equivalent events are respected even
|
|
||||||
// while we're still pre-processing the completion event
|
|
||||||
cx.spawn(async move |codegen, cx| {
|
|
||||||
let finish_with_status = |status: CodegenStatus, cx: &mut AsyncApp| {
|
let finish_with_status = |status: CodegenStatus, cx: &mut AsyncApp| {
|
||||||
let _ = codegen.update(cx, |this, cx| {
|
let _ = codegen.update(cx, |this, cx| {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
@@ -1145,193 +1083,76 @@ impl CodegenAlternative {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut completion_events = match completion_stream.await {
|
let tool_use = tool_use.await;
|
||||||
Ok(events) => events,
|
|
||||||
Err(err) => {
|
|
||||||
finish_with_status(CodegenStatus::Error(err.into()), cx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ToolUseOutput {
|
match tool_use {
|
||||||
Rewrite {
|
Ok(tool_use) if tool_use.name.as_ref() == "rewrite_section" => {
|
||||||
text: String,
|
// Parse the input JSON into RewriteSectionInput
|
||||||
description: Option<String>,
|
match serde_json::from_value::<RewriteSectionInput>(tool_use.input) {
|
||||||
},
|
Ok(input) => {
|
||||||
Failure(String),
|
// Store the description if non-empty
|
||||||
}
|
let description = if !input.description.trim().is_empty() {
|
||||||
|
Some(input.description.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
enum ModelUpdate {
|
// Apply the replacement text to the buffer and compute diff
|
||||||
Description(String),
|
let batch_diff_task = codegen
|
||||||
Failure(String),
|
.update(cx, |this, cx| {
|
||||||
}
|
this.model_explanation = description.map(Into::into);
|
||||||
|
let range = this.range.clone();
|
||||||
|
this.apply_edits(
|
||||||
|
std::iter::once((range, input.replacement_text)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
this.reapply_batch_diff(cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
let chars_read_so_far = Arc::new(Mutex::new(0usize));
|
// Wait for the diff computation to complete
|
||||||
let process_tool_use = move |tool_use: LanguageModelToolUse| -> Option<ToolUseOutput> {
|
if let Some(diff_task) = batch_diff_task {
|
||||||
let mut chars_read_so_far = chars_read_so_far.lock();
|
diff_task.await;
|
||||||
match tool_use.name.as_ref() {
|
|
||||||
REWRITE_SECTION_TOOL_NAME => {
|
|
||||||
let Ok(input) =
|
|
||||||
serde_json::from_value::<RewriteSectionInput>(tool_use.input)
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let text = input.replacement_text[*chars_read_so_far..].to_string();
|
|
||||||
*chars_read_so_far = input.replacement_text.len();
|
|
||||||
Some(ToolUseOutput::Rewrite {
|
|
||||||
text,
|
|
||||||
description: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
FAILURE_MESSAGE_TOOL_NAME => {
|
|
||||||
let Ok(mut input) =
|
|
||||||
serde_json::from_value::<FailureMessageInput>(tool_use.input)
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some(ToolUseOutput::Failure(std::mem::take(&mut input.message)))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded::<ModelUpdate>();
|
|
||||||
|
|
||||||
cx.spawn({
|
|
||||||
let codegen = codegen.clone();
|
|
||||||
async move |cx| {
|
|
||||||
while let Some(update) = message_rx.next().await {
|
|
||||||
let _ = codegen.update(cx, |this, _cx| match update {
|
|
||||||
ModelUpdate::Description(d) => this.description = Some(d),
|
|
||||||
ModelUpdate::Failure(f) => this.failure = Some(f),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
let mut message_id = None;
|
|
||||||
let mut first_text = None;
|
|
||||||
let last_token_usage = Arc::new(Mutex::new(TokenUsage::default()));
|
|
||||||
let total_text = Arc::new(Mutex::new(String::new()));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some(first_event) = completion_events.next().await {
|
|
||||||
match first_event {
|
|
||||||
Ok(LanguageModelCompletionEvent::StartMessage { message_id: id }) => {
|
|
||||||
message_id = Some(id);
|
|
||||||
}
|
|
||||||
Ok(LanguageModelCompletionEvent::ToolUse(tool_use)) => {
|
|
||||||
if let Some(output) = process_tool_use(tool_use) {
|
|
||||||
let (text, update) = match output {
|
|
||||||
ToolUseOutput::Rewrite { text, description } => {
|
|
||||||
(Some(text), description.map(ModelUpdate::Description))
|
|
||||||
}
|
|
||||||
ToolUseOutput::Failure(message) => {
|
|
||||||
(None, Some(ModelUpdate::Failure(message)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(update) = update {
|
|
||||||
let _ = message_tx.unbounded_send(update);
|
|
||||||
}
|
|
||||||
first_text = text;
|
|
||||||
if first_text.is_some() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(LanguageModelCompletionEvent::UsageUpdate(token_usage)) => {
|
finish_with_status(CodegenStatus::Done, cx);
|
||||||
*last_token_usage.lock() = token_usage;
|
return;
|
||||||
}
|
|
||||||
Ok(LanguageModelCompletionEvent::Text(text)) => {
|
|
||||||
let mut lock = total_text.lock();
|
|
||||||
lock.push_str(&text);
|
|
||||||
}
|
|
||||||
Ok(e) => {
|
|
||||||
log::warn!("Unexpected event: {:?}", e);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(tool_use) if tool_use.name.as_ref() == "failure_message" => {
|
||||||
|
// Handle failure message tool use
|
||||||
let Some(first_text) = first_text else {
|
match serde_json::from_value::<FailureMessageInput>(tool_use.input) {
|
||||||
finish_with_status(CodegenStatus::Done, cx);
|
Ok(input) => {
|
||||||
return;
|
let _ = codegen.update(cx, |this, _cx| {
|
||||||
};
|
// Store the failure message as the tool description
|
||||||
|
this.model_explanation = Some(input.message.into());
|
||||||
let move_last_token_usage = last_token_usage.clone();
|
});
|
||||||
|
finish_with_status(CodegenStatus::Done, cx);
|
||||||
let text_stream = Box::pin(futures::stream::once(async { Ok(first_text) }).chain(
|
return;
|
||||||
completion_events.filter_map(move |e| {
|
}
|
||||||
let process_tool_use = process_tool_use.clone();
|
Err(e) => {
|
||||||
let last_token_usage = move_last_token_usage.clone();
|
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||||
let total_text = total_text.clone();
|
return;
|
||||||
let mut message_tx = message_tx.clone();
|
|
||||||
async move {
|
|
||||||
match e {
|
|
||||||
Ok(LanguageModelCompletionEvent::ToolUse(tool_use)) => {
|
|
||||||
let Some(output) = process_tool_use(tool_use) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let (text, update) = match output {
|
|
||||||
ToolUseOutput::Rewrite { text, description } => {
|
|
||||||
(Some(text), description.map(ModelUpdate::Description))
|
|
||||||
}
|
|
||||||
ToolUseOutput::Failure(message) => {
|
|
||||||
(None, Some(ModelUpdate::Failure(message)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(update) = update {
|
|
||||||
let _ = message_tx.send(update).await;
|
|
||||||
}
|
|
||||||
text.map(Ok)
|
|
||||||
}
|
|
||||||
Ok(LanguageModelCompletionEvent::UsageUpdate(token_usage)) => {
|
|
||||||
*last_token_usage.lock() = token_usage;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Ok(LanguageModelCompletionEvent::Text(text)) => {
|
|
||||||
let mut lock = total_text.lock();
|
|
||||||
lock.push_str(&text);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Ok(LanguageModelCompletionEvent::Stop(_reason)) => None,
|
|
||||||
e => {
|
|
||||||
log::error!("UNEXPECTED EVENT {:?}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
));
|
Ok(_tool_use) => {
|
||||||
|
// Unexpected tool.
|
||||||
let language_model_text_stream = LanguageModelTextStream {
|
finish_with_status(CodegenStatus::Done, cx);
|
||||||
message_id: message_id,
|
return;
|
||||||
stream: text_stream,
|
}
|
||||||
last_token_usage,
|
Err(e) => {
|
||||||
};
|
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||||
|
return;
|
||||||
let Some(task) = codegen
|
}
|
||||||
.update(cx, move |codegen, cx| {
|
}
|
||||||
codegen.handle_stream(
|
});
|
||||||
model,
|
cx.notify();
|
||||||
/* strip_invalid_spans: */ false,
|
|
||||||
async { Ok(language_model_text_stream) },
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
task.await;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1495,11 +1316,7 @@ mod tests {
|
|||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{Buffer, Point};
|
use language::{Buffer, Point};
|
||||||
use language_model::fake_provider::FakeLanguageModel;
|
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||||
use language_model::{
|
|
||||||
LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelRegistry,
|
|
||||||
LanguageModelToolUse, StopReason, TokenUsage,
|
|
||||||
};
|
|
||||||
use languages::rust_lang;
|
use languages::rust_lang;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@@ -1529,8 +1346,8 @@ mod tests {
|
|||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
Uuid::new_v4(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1591,8 +1408,8 @@ mod tests {
|
|||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
Uuid::new_v4(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1655,8 +1472,8 @@ mod tests {
|
|||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
Uuid::new_v4(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1719,8 +1536,8 @@ mod tests {
|
|||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
Uuid::new_v4(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1771,8 +1588,8 @@ mod tests {
|
|||||||
buffer.clone(),
|
buffer.clone(),
|
||||||
range.clone(),
|
range.clone(),
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
Uuid::new_v4(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1811,51 +1628,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When not streaming tool calls, we strip backticks as part of parsing the model's
|
|
||||||
// plain text response. This is a regression test for a bug where we stripped
|
|
||||||
// backticks incorrectly.
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_allows_model_to_output_backticks(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
let text = "- Improved; `cmd+click` behavior. Now requires `cmd` to be pressed before the click starts or it doesn't run. ([#44579](https://github.com/zed-industries/zed/pull/44579); thanks [Zachiah](https://github.com/Zachiah))";
|
|
||||||
let buffer = cx.new(|cx| Buffer::local("", cx));
|
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
|
||||||
let range = buffer.read_with(cx, |buffer, cx| {
|
|
||||||
let snapshot = buffer.snapshot(cx);
|
|
||||||
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(0, 0))
|
|
||||||
});
|
|
||||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
|
||||||
let codegen = cx.new(|cx| {
|
|
||||||
CodegenAlternative::new(
|
|
||||||
buffer.clone(),
|
|
||||||
range.clone(),
|
|
||||||
true,
|
|
||||||
prompt_builder,
|
|
||||||
Uuid::new_v4(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let events_tx = simulate_tool_based_completion(&codegen, cx);
|
|
||||||
let chunk_len = text.find('`').unwrap();
|
|
||||||
events_tx
|
|
||||||
.unbounded_send(rewrite_tool_use("tool_1", &text[..chunk_len], false))
|
|
||||||
.unwrap();
|
|
||||||
events_tx
|
|
||||||
.unbounded_send(rewrite_tool_use("tool_2", &text, true))
|
|
||||||
.unwrap();
|
|
||||||
events_tx
|
|
||||||
.unbounded_send(LanguageModelCompletionEvent::Stop(StopReason::EndTurn))
|
|
||||||
.unwrap();
|
|
||||||
drop(events_tx);
|
|
||||||
cx.run_until_parked();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
|
|
||||||
text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_strip_invalid_spans_from_codeblock() {
|
async fn test_strip_invalid_spans_from_codeblock() {
|
||||||
assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await;
|
assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await;
|
||||||
@@ -1906,11 +1678,11 @@ mod tests {
|
|||||||
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();
|
||||||
let model = Arc::new(FakeLanguageModel::default());
|
|
||||||
codegen.update(cx, |codegen, cx| {
|
codegen.update(cx, |codegen, cx| {
|
||||||
codegen.generation = codegen.handle_stream(
|
codegen.handle_stream(
|
||||||
model,
|
String::new(),
|
||||||
/* strip_invalid_spans: */ false,
|
String::new(),
|
||||||
|
None,
|
||||||
future::ready(Ok(LanguageModelTextStream {
|
future::ready(Ok(LanguageModelTextStream {
|
||||||
message_id: None,
|
message_id: None,
|
||||||
stream: chunks_rx.map(Ok).boxed(),
|
stream: chunks_rx.map(Ok).boxed(),
|
||||||
@@ -1921,39 +1693,4 @@ mod tests {
|
|||||||
});
|
});
|
||||||
chunks_tx
|
chunks_tx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simulate_tool_based_completion(
|
|
||||||
codegen: &Entity<CodegenAlternative>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) -> mpsc::UnboundedSender<LanguageModelCompletionEvent> {
|
|
||||||
let (events_tx, events_rx) = mpsc::unbounded();
|
|
||||||
let model = Arc::new(FakeLanguageModel::default());
|
|
||||||
codegen.update(cx, |codegen, cx| {
|
|
||||||
let completion_stream = Task::ready(Ok(events_rx.map(Ok).boxed()
|
|
||||||
as BoxStream<
|
|
||||||
'static,
|
|
||||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
|
||||||
>));
|
|
||||||
codegen.generation = codegen.handle_completion(model, completion_stream, cx);
|
|
||||||
});
|
|
||||||
events_tx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rewrite_tool_use(
|
|
||||||
id: &str,
|
|
||||||
replacement_text: &str,
|
|
||||||
is_complete: bool,
|
|
||||||
) -> LanguageModelCompletionEvent {
|
|
||||||
let input = RewriteSectionInput {
|
|
||||||
replacement_text: replacement_text.into(),
|
|
||||||
};
|
|
||||||
LanguageModelCompletionEvent::ToolUse(LanguageModelToolUse {
|
|
||||||
id: id.into(),
|
|
||||||
name: REWRITE_SECTION_TOOL_NAME.into(),
|
|
||||||
raw_input: serde_json::to_string(&input).unwrap(),
|
|
||||||
input: serde_json::to_value(&input).unwrap(),
|
|
||||||
is_input_complete: is_complete,
|
|
||||||
thought_signature: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user