Compare commits

..

49 Commits

Author SHA1 Message Date
Richard Feldman
ac63ea504a wip 2025-04-16 11:10:25 -04:00
Richard Feldman
11aaea964f Use ToolOutput in rendering 2025-04-09 11:05:37 -04:00
Richard Feldman
cd9a45a262 Merge remote-tracking branch 'origin/main' into tool-rendering 2025-04-09 10:23:27 -04:00
Richard Feldman
1965029528 Refactor tool rendering API 2025-04-09 10:14:27 -04:00
Richard Feldman
46c738d724 Delete unused impl 2025-04-08 16:49:52 -04:00
Richard Feldman
b18bd2d67b Revert "Use erased_serde for ToolOutput serialization"
This reverts commit cad3ee8def.
2025-04-08 16:33:55 -04:00
Richard Feldman
7e0a97fabe Revert "wip"
This reverts commit 8ba9429e60.
2025-04-08 16:33:50 -04:00
Richard Feldman
8ba9429e60 wip 2025-04-08 16:33:49 -04:00
Piotr Osiewicz
0b75c13034 chore: Replace as_any functions with trait upcasting (#28221)
Closes #ISSUE

Release Notes:

- N/A
2025-04-08 22:16:27 +02:00
Ben Kunkle
38ec45008c project: Workaround invalid code action edits from pyright (#28354)
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Piotr Osiewicz <piotr@zed.dev>

fixes issue where:

In a two line python file like so
```

Path()
```

If the user asks for code actions on `Path` and they select (`From
pathlib import path`)
the result they get is
```

Pathfrom pathlib import Path


Path()
```
Instead of 

```

from pathlib import Path



Path()
```

This is due to a non-lsp-spec-compliant response from pyright below

```json
{"jsonrpc":"2.0","id":40,"result":[{"title":"from pathlib import Path","edit":{"changes":{"file:///Users/neb/Zed/example-project/pyright-project/main.py":[{"range":{"start":{"line":2,"character":0},"end":{"line":2,"character":4}},"newText":"Path"},{"range":{"start":{"line":2,"character":0},"end":{"line":2,"character":0}},"newText":"from pathlib import Path\n\n\n"}]}},"kind":"quickfix"}]}
```

Release Notes:

- Fixed an issue when using auto-import code actions provided by pyright
(or basedpyright) where the import would be jumbled with the scoped
import resulting in an invalid result

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-04-08 20:13:44 +00:00
Antonio Scandurra
97641c3298 Use tree-sitter when returning symbols to the model for a given file (#28352)
This also increases the threshold for when we return an outline during
`read_file`.

Release Notes:

- Fixed an issue that caused the agent to fail reading large files if
the LSP hadn't started yet.
2025-04-08 16:11:05 -04:00
Joseph T. Lyons
ca8f6e8a3f Tell the model not to remove tests (#28349)
Release Notes:

- Adjusted system prompt to direct it to never remove tests as a way to
have the test suite pass, unless the user directly asks for test
removal.
2025-04-08 19:26:43 +00:00
Piotr Osiewicz
db53da49e1 debugger: Respect initialize_args from user profiles (#28347)
Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
2025-04-08 21:15:05 +02:00
Peter Tripp
df94dcdea6 ci: Only run workspace_hack when tests run (#28346)
Skip `workspace_hack` for PRs that don't trigger tests (docs-only,
.github issue templates, etc).

Release Notes:

- N/A
2025-04-08 18:55:54 +00:00
Richard Feldman
1c85901440 Tell the model not to create .bak files (#28244)
Release Notes:

- Adjusted system prompt to avoid having the agent create backup files
unnecessarily.
2025-04-08 18:45:35 +00:00
Peter Tripp
9fb77ad176 Refine GitHub Issue templates (#28345)
Make various improvements to our github issue templates.

- Adjust line lengths to not wrap in constrained new issue view (85
cols) not just full screen view (95 columns)
- Remove reference to drag/drop logs to upload (recently multiple issues
with dead upload links)
- Cleanup list view

Release Notes:

- N/A
2025-04-08 14:41:55 -04:00
Richard Feldman
cad3ee8def Use erased_serde for ToolOutput serialization 2025-04-08 14:19:54 -04:00
Michael Sloan
feafad2f9d Improve comments on shader code for dashed borders (#28341)
Improvements from going over the code with @as-cii 

Release Notes:

- N/A
2025-04-08 18:08:22 +00:00
Richard Feldman
42cb40170e Implement missing traits 2025-04-08 14:07:50 -04:00
Richard Feldman
7dc3a39edf Break cyclic dep 2025-04-08 14:07:40 -04:00
Piotr Osiewicz
86ef00054b pylsp: Upgrade existing installation if possible (#28338)
Closes #ISSUE

Release Notes:

- Zed-managed pylsp installations will now correctly upgrade themselves
2025-04-08 20:01:09 +02:00
Conrad Irwin
9e8afa8daa Fix local task dropped on the wrong thread (#28290)
Release Notes:

- Fixed a panic during shutdown of the remote server
2025-04-08 11:54:51 -06:00
Richard Feldman
fc1b3889c2 wip 2025-04-08 13:37:59 -04:00
Danilo Leal
698cdc4d1a agent: Display "generating" label in the active thread (#28297)
Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-04-08 14:31:32 -03:00
Agus Zubiaga
85c5d8af3a agent: Truncate bash tool output (#28291)
The bash tool will now truncate its output to 8192 bytes (or the last
newline before that).

We also added a global limit for any tool that produces a clearly large
output that wouldn't fit the context window.

Release Notes:

- agent: Truncate bash tool output

---------

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2025-04-08 16:55:35 +00:00
Richard Feldman
34c6fee959 Add ToolOutput 2025-04-08 12:52:18 -04:00
Anthony Eid
1774cad933 debugger: Add close button and coloring to debug panel session's menu (#28310)
This PR adds colors to debug panel's session menu that indicate the
state of each respective session. It also adds a close button to each
entry.

green - running
yellow - stopped
red - terminated/ended 


Release Notes:

- N/A
2025-04-08 16:35:33 +00:00
Conrad Irwin
ee7b1ec7f2 Fix deafening new participants (#28330)
Release Notes:

- Fixed an issue where new participants were not muted when the room was
deafened
2025-04-08 16:01:27 +00:00
Bennet Bo Fenner
14b43d573c agent: Navigate to line when clicking on filepath in markdown codeblock (#28329)
Release Notes:

- N/A
2025-04-08 15:41:34 +00:00
Max Brunsfeld
d39e1e03b8 Demote buffer-diff to a dev dependency of collab (#28295)
This will speed up our collab deployments a little

Release Notes:

- N/A
2025-04-08 08:36:44 -07:00
Peter Tripp
9e504a1ed9 git: Fix logging FromUtf8Error when diffing (#28276)
If you attempt to load a git diff which includes a non utf-8 file,
previously
(1) the entire contents of the file was logged as ordinals and
(2) a second spurious error was logged

```
2025-04-07T16:21:28.392845-04:00 [ERROR] FromUtf8Error { bytes: [0, 1, 0, 0, 0, 19, 1, 0, 0, 4, 0, 48, 68, 83, 73, 71, 0, 0, 0, 1, 0, 2, 241, 204, 0, 0, 0, 8, 71, 68, 69, 70, 164, 172, 164, ...

[2025-04-07T17:12:16-04:00 ERROR git::repository] Error loading index text: invalid utf-8 sequence of 1 bytes from index 35
```

Having 1MB binary file in a commit would generate ~3MB-5MB of log
output.

Discovered while investigating
https://github.com/zed-industries/zed/issues/28241

Release Notes:

- git: Fixed an issue where non-UTF8 files in a git diff would generate
log spam.
2025-04-08 11:28:34 -04:00
Richard Feldman
b25ec65b48 Add custom render method to Tool trait 2025-04-08 11:26:08 -04:00
Conrad Irwin
ca4cc4764b Upgrade async-tungstenite to tokio (#26193)
We're seeing panics caused by a buggy implementation of AsyncWrite
that is being passed to rustls: 

https://github.com/rustls/rustls/issues/2316#issuecomment-2662838186

One hypothesis was that we're using (comparatively) non-standard async
tools for connecting over websockets; so this attempts to make us be
(comparitvely) more standard.

Release Notes:

- N/A
2025-04-08 09:17:08 -06:00
5brian
ea33d78ae4 vim: Fix visual line yank on newline char (#28005)
Problem:
When yanking in visual line on the newline char, the next line gets
yanked as well:


https://github.com/user-attachments/assets/40f332dd-19f5-445f-a30f-39d50167c46f

Changes:
Similar to visual delete, exclude the newline char from the selection in
line mode.

Release Notes:

- vim: Fixed visual line yank while on the newline character yanking
following line
2025-04-08 09:15:34 -06:00
5brian
e36a2f2739 vim: Add indent-wise motions (#28044)
Taken from:
https://github.com/jeetsukumaran/vim-indentwise?tab=readme-ov-file#movements-by-relative-indent-depth



> [- : Move to previous line of lesser indent than the current line.
> [+ : Move to previous line of greater indent than the current line.
> [= : Move to previous line of same indent as the current line that is
separated from the current line by lines of different indents.
> ]- : Move to next line of lesser indent than the current line.
> ]+ : Move to next line of greater indent than the current line.
> ]= : Move to next line of same indent as the current line that is
separated from the current line by lines of different indents.



Release Notes:

- vim: Added indent-wise motions `] -/+/=`
2025-04-08 09:07:37 -06:00
jneem
cbd9b4cc39 Switch back to the default mode after paste (#28304)
Now that the flaky tests are disabled, this should work...

Release Notes:

- Fixed vim paste action to switch back to the configured default mode.
2025-04-08 09:03:55 -06:00
James Falade
64f8b1e739 docs: Add missing comma in the Tasks page (#28324)
Added a missing command in the same tasks.json file

Release Notes:

- N/A
2025-04-08 15:03:42 +00:00
5brian
4f936d8100 vim: Fix exchange showing ccx in pending keys (#28303)
|Before|After|
|--|--|

|![image](https://github.com/user-attachments/assets/cac97981-518e-46d2-8540-a22496bc948e)|![image](https://github.com/user-attachments/assets/5158b17f-d14e-42a2-8a94-ad98d1b1c33c)|

Changes:
Add vim exchange to the clear stack

Release Notes:

- N/A
2025-04-08 09:03:24 -06:00
Bennet Bo Fenner
97abf21a28 agent: Add support for Google Gemini 2.5 preview (#28326)
Adds support for the new `gemini-2.5-pro-preview-03-25`

Release Notes:

- Added support for `gemini-2.5-pro-preview-03-25` in the assistant
2025-04-08 15:00:23 +00:00
Piotr Osiewicz
5fb1411e4d debugger: Add scrollbars, fix papercuts with completions menu and jumps (#28321)
Closes #ISSUE

Release Notes:

- N/A
2025-04-08 16:55:18 +02:00
0x2CA
b04f7a4c7c vim: Fix visual object expands (#28301)
Closes #28198

Release Notes:

- Fixed visual object expands
2025-04-08 08:51:41 -06:00
Marshall Bowers
61b7a05792 language_models: Allow overriding Zed completions URL via environment variable (#28323)
This PR adds support for overriding the Zed completions URL via the
`ZED_COMPLETIONS_URL` environment variable.

Release Notes:

- N/A
2025-04-08 14:46:15 +00:00
Peter Tripp
cc15598e09 keymap: Document conflicting macos ctrl-shift-space shortcut (#28325)
Closes https://github.com/zed-industries/zed/issues/26261

Release Notes:

- N/A
2025-04-08 14:46:03 +00:00
Smit Barmase
7ee9109ade project_panel: Do not allow creating empty file/dir or file/dir with only whitespaces (#28240)
- Do not allow creating empty file or empty directory.
- Do not allow creating file or directory with just whitespace.
- Show error only in case whitespace.

<img width="352" alt="image"
src="https://github.com/user-attachments/assets/f6040332-59a6-4d09-bf07-2b4b1b8b9e03"
/>

Release Notes:

- N/A
2025-04-08 18:00:01 +05:30
Piotr Osiewicz
c21fdd212b python: Bump PET version (#28319)
Speculative fix for #27518


Release Notes:

- N/A
2025-04-08 11:04:57 +00:00
tidely
a28929592e gpui: Depend on workspace image crate (#28313)
Make gpui depend on the image crate on the workspace level

Release Notes:

- N/A
2025-04-08 12:21:24 +02:00
Smit Barmase
3b787e85a4 Fix scrolling too fast on selection for editor and terminal (#28309) 2025-04-08 12:16:18 +05:30
Kirill Bulatov
1264e7a200 Properly store editor restoration data (#28296)
We cannot compare versions and anchors between different `Buffer`s with
different `BufferId`s.

Release Notes:

- Fixed Zed panicking on editor reopen

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2025-04-08 01:25:43 +00:00
Richard Feldman
bfe08e449f Tell the system prompt not to write incomplete code (#28245)
Sometimes agents do this. I've had some success responding by telling it
not to do this, so trying out having it in the system prompt.

Release Notes:

- Adjusted the system prompt to avoid incomplete code generation.
2025-04-07 20:59:52 -04:00
111 changed files with 1927 additions and 1161 deletions

36
.github/ISSUE_TEMPLATE/01_bug_agent.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Bug Report (Agent Panel)
description: Zed Agent Panel Bugs
type: "Bug"
labels: ["agent", "ai"]
title: "Agent Panel: <a short description of the Agent Panel bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
<!-- Please include the LLM provider and model name you are using -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
name: Edit Predictions Bug Report
description: There is a bug related to Edit Predictions in Zed
name: Bug Report (Edit Predictions)
description: Zed Edit Predictions bugs
type: "Bug"
labels: ["ai", "inline completion", "zeta"]
title: "Edit Predictions: <a short description of the Edit Prediction bug>"
@@ -10,19 +10,21 @@ body:
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
SUMMARY_SENTENCE_HERE
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
<!-- Please include the LLM provider and model name you are using -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
@@ -32,20 +34,3 @@ body:
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary>
<!-- Click below this line and paste or drag-and-drop your log-->
```
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

35
.github/ISSUE_TEMPLATE/03_bug_git.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Bug Report (Git)
description: Zed Git-Related Bugs
type: "Bug"
labels: ["git"]
title: "Git: <a short description of the Git bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true

View File

@@ -1,46 +1,44 @@
name: Bug Report
name: Bug Report (Other)
description: |
Something is broken in Zed (exclude crashing).
Something else is broken in Zed (exclude crashing).
type: "Bug"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
description: Provide a one sentence summary and detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
<!-- Begin your issue with a one sentence summary -->
SUMMARY_SENTENCE_HERE
<!-- Be verbose: Include all steps necessary to reproduce from a clean Zed installation. -->
<!-- Code snippets are better than images, a repository link that reproduces the issue is ideal. -->
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install.
- Any code must be sufficient to reproduce (include context!)
- Code must as text, not just as a screenshot.
- Issues with insufficient detail may be summarily closed.
-->
Steps to trigger the problem:
Steps to reproduce:
1.
2.
3.
4.
Expected Behavior:
Actual Behavior:
Expected Behavior:
<!-- Before Submitting, did you:
1. Include settings.json, keymap.json, .editorconfig if relevant?
2. Check your Zed.log for relevant errors? (please include!)
3. Click Preview to ensure everything looks right?
4. Hide videos, large images and logs in ``` inside collapsible blocks:
<!--
Is there anything additional necessary to reproduce this issue?
- settings.json, keymap.json, .editorconfig etc?
- Does it happen intermittently or only with specific projects / file types?
- Have you found a workaround?
<details><summary>click to expand</summary>
Did you check your Zed.log to see if there is any relevant details there?
- When including large items (videos, screenshots, logs, configs) please wrap with:
```json
<details><summary>See inside for XXXXYYY</summary>
```shell
code
```
</details>
```
</details>
-->
validations:
@@ -50,7 +48,8 @@ body:
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
description: |
Open Zed, from the command palette select "zed: Copy System Specs Into Clipboard"
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:

View File

@@ -5,10 +5,12 @@ body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
description: Summarize the issue with detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
<!-- Begin your issue with a one sentence summary -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
Steps to trigger the problem:
1.
@@ -16,7 +18,6 @@ body:
3.
Actual Behavior:
Expected Behavior:
validations:
@@ -40,10 +41,11 @@ body:
value: |
<details><summary>Zed.log</summary>
<!-- Click below this line and paste or drag-and-drop your log-->
```
<!-- Paste your log inside the code block. -->
```log
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
</details>
validations:
required: false

View File

@@ -4,9 +4,6 @@ contact_links:
- name: Feature Request
url: https://github.com/zed-industries/zed/discussions/new/choose
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
- name: Zed Discussion Forum
url: https://github.com/zed-industries/zed/discussions
about: A community discussion forum
- name: "Zed Discord: #Support Channel"
- name: "Zed Discord"
url: https://zed.dev/community-links
about: Real-time discussion and user support

View File

@@ -114,7 +114,9 @@ jobs:
timeout-minutes: 60
name: Check workspace-hack crate
needs: [job_spec]
if: github.repository_owner == 'zed-industries'
if: |
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- buildjet-8vcpu-ubuntu-2204
steps:

143
Cargo.lock generated
View File

@@ -746,7 +746,6 @@ dependencies = [
"itertools 0.14.0",
"language",
"language_model",
"lsp",
"open",
"project",
"rand 0.8.5",
@@ -913,18 +912,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-native-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec"
dependencies = [
"futures-util",
"native-tls",
"thiserror 1.0.69",
"url",
]
[[package]]
name = "async-net"
version = "2.0.0"
@@ -1094,18 +1081,6 @@ version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-tls"
version = "0.13.0"
source = "git+https://github.com/zed-industries/async-tls?rev=1e759a4b5e370f87dc15e40756ac4f8815b61d9d#1e759a4b5e370f87dc15e40756ac4f8815b61d9d"
dependencies = [
"futures-core",
"futures-io",
"rustls 0.23.25",
"rustls-pemfile 2.2.0",
"webpki-roots",
]
[[package]]
name = "async-trait"
version = "0.1.88"
@@ -1119,12 +1094,10 @@ dependencies = [
[[package]]
name = "async-tungstenite"
version = "0.28.2"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c348fb0b6d132c596eca3dcd941df48fb597aafcb07a738ec41c004b087dc99"
checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886"
dependencies = [
"async-std",
"async-tls",
"atomic-waker",
"futures-core",
"futures-io",
@@ -1132,7 +1105,10 @@ dependencies = [
"futures-util",
"log",
"pin-project-lite",
"tungstenite 0.24.0",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.2",
"tungstenite 0.26.2",
]
[[package]]
@@ -2838,7 +2814,6 @@ name = "client"
version = "0.1.0"
dependencies = [
"anyhow",
"async-native-tls",
"async-recursion 0.3.2",
"async-tungstenite",
"chrono",
@@ -2849,6 +2824,7 @@ dependencies = [
"feature_flags",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"http_client",
"http_client_tls",
"log",
@@ -2870,6 +2846,7 @@ dependencies = [
"thiserror 2.0.12",
"time",
"tiny_http",
"tokio",
"tokio-socks",
"url",
"util",
@@ -7452,8 +7429,7 @@ dependencies = [
[[package]]
name = "jupyter-protocol"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9ae6296f9476658b3550293c113996daf75fa542cd8d078abb4c60207bded14"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"async-trait",
@@ -7468,8 +7444,7 @@ dependencies = [
[[package]]
name = "jupyter-websocket-client"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c1ba895c5271ff8dcae51c347fd3588905ba0025a57e20955fd231fe1228cc"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"async-trait",
@@ -8776,8 +8751,7 @@ dependencies = [
[[package]]
name = "nbformat"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "244c1673f02b4d5f3c51b6f8ed28d57182cb166a50a6dbf651a3d53e23dc81c0"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"chrono",
@@ -9556,15 +9530,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-src"
version = "300.4.2+3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.107"
@@ -9573,7 +9538,6 @@ checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@@ -9997,7 +9961,7 @@ dependencies = [
[[package]]
name = "pet"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"clap",
"env_logger 0.10.2",
@@ -10034,7 +9998,7 @@ dependencies = [
[[package]]
name = "pet-conda"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -10053,7 +10017,7 @@ dependencies = [
[[package]]
name = "pet-core"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"clap",
"lazy_static",
@@ -10068,7 +10032,7 @@ dependencies = [
[[package]]
name = "pet-env-var-path"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -10084,7 +10048,7 @@ dependencies = [
[[package]]
name = "pet-fs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10093,7 +10057,7 @@ dependencies = [
[[package]]
name = "pet-global-virtualenvs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10106,7 +10070,7 @@ dependencies = [
[[package]]
name = "pet-homebrew"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -10124,7 +10088,7 @@ dependencies = [
[[package]]
name = "pet-jsonrpc"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -10137,7 +10101,7 @@ dependencies = [
[[package]]
name = "pet-linux-global-python"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10150,7 +10114,7 @@ dependencies = [
[[package]]
name = "pet-mac-commandlinetools"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10163,7 +10127,7 @@ dependencies = [
[[package]]
name = "pet-mac-python-org"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10176,7 +10140,7 @@ dependencies = [
[[package]]
name = "pet-mac-xcode"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10189,7 +10153,7 @@ dependencies = [
[[package]]
name = "pet-pipenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10202,7 +10166,7 @@ dependencies = [
[[package]]
name = "pet-pixi"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10214,7 +10178,7 @@ dependencies = [
[[package]]
name = "pet-poetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"base64 0.22.1",
"lazy_static",
@@ -10235,7 +10199,7 @@ dependencies = [
[[package]]
name = "pet-pyenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -10253,7 +10217,7 @@ dependencies = [
[[package]]
name = "pet-python-utils"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -10270,7 +10234,7 @@ dependencies = [
[[package]]
name = "pet-reporter"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -10284,7 +10248,7 @@ dependencies = [
[[package]]
name = "pet-telemetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -10299,7 +10263,7 @@ dependencies = [
[[package]]
name = "pet-venv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10311,7 +10275,7 @@ dependencies = [
[[package]]
name = "pet-virtualenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10323,7 +10287,7 @@ dependencies = [
[[package]]
name = "pet-virtualenvwrapper"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -10336,7 +10300,7 @@ dependencies = [
[[package]]
name = "pet-windows-registry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -10348,13 +10312,13 @@ dependencies = [
"pet-virtualenv",
"pet-windows-store",
"regex",
"winreg 0.52.0",
"winreg 0.55.0",
]
[[package]]
name = "pet-windows-store"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0#1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
dependencies = [
"lazy_static",
"log",
@@ -10364,7 +10328,7 @@ dependencies = [
"pet-python-utils",
"pet-virtualenv",
"regex",
"winreg 0.52.0",
"winreg 0.55.0",
]
[[package]]
@@ -12102,8 +12066,7 @@ dependencies = [
[[package]]
name = "runtimelib"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af6ed9fd10d7ee940676945510c197c2a472806bb652096a713985c44ffd643"
source = "git+https://github.com/ConradIrwin/runtimed?rev=7130c804216b6914355d15d0b91ea91f6babd734#7130c804216b6914355d15d0b91ea91f6babd734"
dependencies = [
"anyhow",
"async-dispatcher",
@@ -15330,24 +15293,6 @@ dependencies = [
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
dependencies = [
"byteorder",
"bytes 1.10.1",
"data-encoding",
"http 1.3.1",
"httparse",
"log",
"rand 0.8.5",
"sha1",
"thiserror 1.0.69",
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.26.2"
@@ -17251,6 +17196,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "winreg"
version = "0.55.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
dependencies = [
"cfg-if",
"windows-sys 0.59.0",
]
[[package]]
name = "winresource"
version = "0.1.20"
@@ -17701,7 +17656,6 @@ dependencies = [
"scopeguard",
"sea-orm",
"sea-query-binder",
"security-framework 2.11.1",
"security-framework 3.2.0",
"security-framework-sys",
"semver",
@@ -17732,6 +17686,7 @@ dependencies = [
"toml_edit",
"tracing",
"tracing-core",
"tungstenite 0.26.2",
"unicode-properties",
"url",
"uuid",

View File

@@ -396,7 +396,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
async-recursion = "1.0.0"
async-tar = "0.5.0"
async-trait = "0.1"
async-tungstenite = "0.28"
async-tungstenite = "0.29.1"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
@@ -453,8 +453,8 @@ indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
jsonwebtoken = "9.3"
jupyter-protocol = { version = "0.6.0" }
jupyter-websocket-client = { version = "0.9.0" }
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
@@ -463,7 +463,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
mlua = { version = "0.10", features = ["lua54", "vendored", "async", "send"] }
nanoid = "0.4"
nbformat = { version = "0.10.0" }
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
objc = "0.2"
open = "5.0.0"
@@ -472,13 +472,13 @@ ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
@@ -501,7 +501,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.25.0", default-features = false, features = [
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
@@ -661,7 +661,6 @@ features = [
# TODO livekit https://github.com/RustAudio/cpal/pull/891
[patch.crates-io]
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" }
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }

View File

@@ -525,7 +525,7 @@
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
// Using `ctrl-space` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowWordCompletions",
@@ -1003,6 +1003,8 @@
"cmd-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom",
"cmd-end": "terminal::ScrollToBottom",
// Using `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
"ctrl-shift-space": "terminal::ToggleViMode",
"ctrl-k up": "pane::SplitUp",
"ctrl-k down": "pane::SplitDown",

View File

@@ -44,6 +44,12 @@
"[ /": "vim::PreviousComment",
"] *": "vim::NextComment",
"] /": "vim::NextComment",
"[ -": "vim::PreviousLesserIndent",
"[ +": "vim::PreviousGreaterIndent",
"[ =": "vim::PreviousSameIndent",
"] -": "vim::NextLesserIndent",
"] +": "vim::NextGreaterIndent",
"] =": "vim::NextSameIndent",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",

View File

@@ -6,9 +6,16 @@ You are an AI assistant integrated into a code editor. You have the programming
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
You should only perform actions that modify the user's system if explicitly requested by the user:
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the users system without explicit instruction.
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user's system without explicit instruction.
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
When answering questions, it's okay to give incomplete examples containing comments about what would go there in a real version. When being asked to directly perform tasks on the code base, you must ALWAYS make fully working code. You may never "simplify" the code by omitting or deleting functionality you know the user has requested, and you must NEVER write comments like "in a full version, this would..." - instead, you must actually implement the real version. Don't be lazy!
Note that project files are automatically backed up. The user can always get them back later if anything goes wrong, so there's
no need to create backup files (e.g. `.bak` files) because these files will just take up unnecessary space on the user's disk.
When attempting to resolve issues around failing tests, never simply remove the failing tests. Unless the user explicitly asks you to remove tests, ALWAYS attempt to fix the code causing the tests to fail.
<style>
Editing code:
- Make sure to take previous edits into account.

View File

@@ -21,10 +21,14 @@ use gpui::{
linear_color_stop, linear_gradient, list, percentage, pulsating_between,
};
use language::{Buffer, LanguageRegistry};
use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role};
use language_model::{
ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role, StringToolOutput,
ToolOutput,
};
use markdown::parser::CodeBlockKind;
use markdown::{Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, without_fences};
use project::ProjectItem as _;
use rope::Point;
use settings::{Settings as _, update_settings_file};
use std::ops::Range;
use std::path::Path;
@@ -35,6 +39,7 @@ use text::ToPoint;
use theme::ThemeSettings;
use ui::{Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*};
use util::ResultExt as _;
use util::markdown::MarkdownString;
use workspace::{OpenOptions, Workspace};
use crate::context_store::ContextStore;
@@ -58,7 +63,7 @@ pub struct ActiveThread {
expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
last_error: Option<ThreadError>,
notifications: Vec<WindowHandle<AgentNotification>>,
copied_code_block_ids: HashSet<usize>,
copied_code_block_ids: HashSet<(MessageId, usize)>,
_subscriptions: Vec<Subscription>,
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
feedback_message_editor: Option<Entity<Editor>>,
@@ -73,7 +78,7 @@ struct RenderedMessage {
struct RenderedToolUse {
label: Entity<Markdown>,
input: Entity<Markdown>,
output: Entity<Markdown>,
output: ToolOutput,
}
impl RenderedMessage {
@@ -289,7 +294,8 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
}
fn render_markdown_code_block(
id: usize,
message_id: MessageId,
ix: usize,
kind: &CodeBlockKind,
parsed_markdown: &ParsedMarkdown,
codeblock_range: Range<usize>,
@@ -366,7 +372,7 @@ fn render_markdown_code_block(
};
h_flex()
.id(("code-block-header-label", id))
.id(("code-block-header-label", ix))
.w_full()
.max_w_full()
.px_1()
@@ -397,8 +403,40 @@ fn render_markdown_code_block(
.read(cx)
.find_project_path(&path_range.path, cx)
{
workspace
.open_path(project_path, None, true, window, cx)
let target = path_range.range.as_ref().map(|range| {
Point::new(
// Line number is 1-based
range.start.line.saturating_sub(1),
range.start.col.unwrap_or(0),
)
});
let open_task = workspace.open_path(
project_path,
None,
true,
window,
cx,
);
window
.spawn(cx, async move |cx| {
let item = open_task.await?;
if let Some(target) = target {
if let Some(active_editor) =
item.downcast::<Editor>()
{
active_editor
.downgrade()
.update_in(cx, |editor, window, cx| {
editor
.go_to_singleton_buffer_point(
target, window, cx,
);
})
.log_err();
}
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
@@ -416,7 +454,10 @@ fn render_markdown_code_block(
.element_background
.blend(cx.theme().colors().editor_foreground.opacity(0.01));
let codeblock_was_copied = active_thread.read(cx).copied_code_block_ids.contains(&id);
let codeblock_was_copied = active_thread
.read(cx)
.copied_code_block_ids
.contains(&(message_id, ix));
let codeblock_header = h_flex()
.p_1()
@@ -429,7 +470,7 @@ fn render_markdown_code_block(
.children(label)
.child(
IconButton::new(
("copy-markdown-code", id),
("copy-markdown-code", ix),
if codeblock_was_copied {
IconName::Check
} else {
@@ -439,13 +480,12 @@ fn render_markdown_code_block(
.icon_color(Color::Muted)
.shape(ui::IconButtonShape::Square)
.tooltip(Tooltip::text("Copy Code"))
.visible_on_hover("markdown-code-block")
.on_click({
let active_thread = active_thread.clone();
let parsed_markdown = parsed_markdown.clone();
move |_event, _window, cx| {
active_thread.update(cx, |this, cx| {
this.copied_code_block_ids.insert(id);
this.copied_code_block_ids.insert((message_id, ix));
let code =
without_fences(&parsed_markdown.source()[codeblock_range.clone()])
@@ -458,7 +498,7 @@ fn render_markdown_code_block(
cx.update(|cx| {
this.update(cx, |this, cx| {
this.copied_code_block_ids.remove(&id);
this.copied_code_block_ids.remove(&(message_id, ix));
cx.notify();
})
})
@@ -693,21 +733,28 @@ impl ActiveThread {
tool_use_id: LanguageModelToolUseId,
tool_label: impl Into<SharedString>,
tool_input: &serde_json::Value,
tool_output: SharedString,
tool_output: ToolOutput,
cx: &mut Context<Self>,
) {
let rendered = RenderedToolUse {
label: render_tool_use_markdown(tool_label.into(), self.language_registry.clone(), cx),
input: render_tool_use_markdown(
format!(
"```json\n{}\n```",
serde_json::to_string_pretty(tool_input).unwrap_or_default()
MarkdownString::code_block(
"json",
&serde_json::to_string_pretty(tool_input).unwrap_or_default(),
)
.to_string()
.into(),
self.language_registry.clone(),
cx,
),
output: render_tool_use_markdown(tool_output, self.language_registry.clone(), cx),
output: render_tool_use_markdown(
tool_output.clone(),
self.language_registry.clone(),
cx,
),
output: StringToolOutput::new(tool_output, language_registry: Arc<LanguageRegistry>),
};
self.rendered_tool_uses
.insert(tool_use_id.clone(), rendered);
@@ -1163,16 +1210,62 @@ impl ActiveThread {
let context_store = self.context_store.clone();
let workspace = self.workspace.clone();
let thread = self.thread.read(cx);
// Get all the data we need from thread before we start using it in closures
let checkpoint = thread.checkpoint_for_message(message_id);
let context = thread.context_for_message(message_id).collect::<Vec<_>>();
let tool_uses = thread.tool_uses_for_message(message_id, cx);
let has_tool_uses = !tool_uses.is_empty();
let is_generating = thread.is_generating();
let is_first_message = ix == 0;
let is_last_message = ix == self.messages.len() - 1;
let show_feedback = is_last_message && message.role != Role::User;
let needs_confirmation = tool_uses.iter().any(|tool_use| tool_use.needs_confirmation);
let generating_label = (is_generating && is_last_message).then(|| {
Label::new("Generating")
.color(Color::Muted)
.size(LabelSize::Small)
.with_animation(
"generating-label",
Animation::new(Duration::from_secs(1)).repeat(),
|mut label, delta| {
let text = match delta {
d if d < 0.25 => "Generating",
d if d < 0.5 => "Generating.",
d if d < 0.75 => "Generating..",
_ => "Generating...",
};
label.set_text(text);
label
},
)
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.6, 1.)),
|label, delta| label.map_element(|label| label.alpha(delta)),
)
});
// Don't render user messages that are just there for returning tool results.
if message.role == Role::User && thread.message_has_tool_results(message_id) {
if let Some(generating_label) = generating_label {
return h_flex()
.w_full()
.h_10()
.py_1p5()
.pl_4()
.pb_3()
.child(generating_label)
.into_any_element();
}
return Empty.into_any();
}
@@ -1184,9 +1277,6 @@ impl ActiveThread {
.filter(|(id, _)| *id == message_id)
.map(|(_, state)| state.editor.clone());
let first_message = ix == 0;
let show_feedback = ix == self.messages.len() - 1 && message.role != Role::User;
let colors = cx.theme().colors();
let active_color = colors.element_active;
let editor_bg_color = colors.editor_background;
@@ -1355,7 +1445,7 @@ impl ActiveThread {
Role::User => v_flex()
.id(("message-container", ix))
.map(|this| {
if first_message {
if is_first_message {
this.pt_2()
} else {
this.pt_4()
@@ -1473,15 +1563,11 @@ impl ActiveThread {
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.children(message_content)
.gap_2p5()
.pb_2p5()
.when(!tool_uses.is_empty(), |parent| {
parent.child(
div().children(
tool_uses
.into_iter()
.map(|tool_use| self.render_tool_use(tool_use, window, cx)),
),
.when(has_tool_uses, |parent| {
parent.children(
tool_uses
.into_iter()
.map(|tool_use| self.render_tool_use(tool_use, window, cx)),
)
}),
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
@@ -1494,9 +1580,6 @@ impl ActiveThread {
v_flex()
.w_full()
.when(first_message, |parent| {
parent.child(self.render_rules_item(cx))
})
.when_some(checkpoint, |parent, checkpoint| {
let mut is_pending = false;
let mut error = None;
@@ -1566,65 +1649,56 @@ impl ActiveThread {
.child(ui::Divider::horizontal()),
)
})
.when(is_first_message, |parent| {
parent.child(self.render_rules_item(cx))
})
.child(styled_message)
.when(
show_feedback && !self.thread.read(cx).is_generating(),
|parent| {
parent.child(feedback_items).when_some(
self.feedback_message_editor.clone(),
|parent, feedback_editor| {
let focus_handle = feedback_editor.focus_handle(cx);
parent.child(
v_flex()
.key_context("AgentFeedbackMessageEditor")
.on_action(cx.listener(|this, _: &menu::Cancel, _, cx| {
this.feedback_message_editor = None;
cx.notify();
}))
.on_action(cx.listener(|this, _: &menu::Confirm, _, cx| {
this.submit_feedback_message(cx);
cx.notify();
}))
.on_action(cx.listener(Self::confirm_editing_message))
.mx_4()
.mb_3()
.p_2()
.rounded_md()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.child(feedback_editor)
.child(
h_flex()
.gap_1()
.justify_end()
.child(
Button::new("dismiss-feedback-message", "Cancel")
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, _, cx| {
this.feedback_message_editor = None;
cx.notify();
})),
)
.child(
Button::new(
"submit-feedback-message",
"Share Feedback",
)
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.when(!needs_confirmation && generating_label.is_some(), |this| {
this.child(
h_flex()
.h_8()
.mt_2()
.mb_4()
.ml_4()
.py_1p5()
.child(generating_label.unwrap()),
)
})
.when(show_feedback && !is_generating, |parent| {
parent.child(feedback_items).when_some(
self.feedback_message_editor.clone(),
|parent, feedback_editor| {
let focus_handle = feedback_editor.focus_handle(cx);
parent.child(
v_flex()
.key_context("AgentFeedbackMessageEditor")
.on_action(cx.listener(|this, _: &menu::Cancel, _, cx| {
this.feedback_message_editor = None;
cx.notify();
}))
.on_action(cx.listener(|this, _: &menu::Confirm, _, cx| {
this.submit_feedback_message(cx);
cx.notify();
}))
.on_action(cx.listener(Self::confirm_editing_message))
.my_3()
.mx_4()
.p_2()
.rounded_md()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.child(feedback_editor)
.child(
h_flex()
.gap_1()
.justify_end()
.child(
Button::new("dismiss-feedback-message", "Cancel")
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&menu::Cancel,
&focus_handle,
window,
cx,
@@ -1632,16 +1706,38 @@ impl ActiveThread {
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, _, cx| {
this.submit_feedback_message(cx);
this.feedback_message_editor = None;
cx.notify();
})),
)
.child(
Button::new(
"submit-feedback-message",
"Share Feedback",
)
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(
cx.listener(|this, _, _, cx| {
this.submit_feedback_message(cx);
cx.notify();
}),
),
),
)
},
)
},
)
),
),
)
},
)
})
.into_any()
}
@@ -1700,6 +1796,7 @@ impl ActiveThread {
let active_thread = cx.entity();
move |id, kind, parsed_markdown, range, window, cx| {
render_markdown_code_block(
message_id,
id,
kind,
parsed_markdown,
@@ -2008,24 +2105,50 @@ impl ActiveThread {
results_content_container()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
Label::new("Result")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(div().w_full().text_ui_sm(cx).children(
rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx);
let tool_name = tool_use.name.to_string();
let tool_registry = assistant_tool::ToolRegistry::global(cx);
if let Some(_tool) = tool_registry.tool(&tool_name) {
// Tool doesn't have a render method, but ToolOutput does
match rendered.output.render(window, cx) {
Some(rendered) => rendered,
None => {
// Default to rendering the output as markdown
div()
.child(
Label::new("Result")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
)
.child(
div().w_full().text_ui_sm(cx).child(
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
open_markdown_link(
text,
workspace.clone(),
window,
cx,
);
}
}),
),
)
.into_any_element()
}
}
})
} else {
log::error!("Tool not found: {tool_name}");
gpui::Empty.into_any_element()
}
}),
)),
),
@@ -2072,16 +2195,30 @@ impl ActiveThread {
div()
.text_ui_sm(cx)
.children(rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx);
}
})
let tool_name = tool_use.name.to_string();
let tool_registry = assistant_tool::ToolRegistry::global(cx);
tool_registry
.tool(&tool_name)
.and_then(|_tool| None) // Tool doesn't have a render method, but ToolOutput does
.unwrap_or_else(|| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
open_markdown_link(
text,
workspace.clone(),
window,
cx,
);
}
})
.into_any_element()
})
})),
),
),
@@ -2123,6 +2260,7 @@ impl ActiveThread {
if !tool_use.needs_confirmation {
element.child(
v_flex()
.my_1p5()
.child(
h_flex()
.group("disclosure-header")
@@ -2194,6 +2332,7 @@ impl ActiveThread {
)
} else {
v_flex()
.my_3()
.rounded_lg()
.border_1()
.border_color(self.tool_card_border_color(cx))
@@ -2296,7 +2435,32 @@ impl ActiveThread {
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.rounded_b_lg()
.child(Label::new("Action Confirmation").color(Color::Muted).size(LabelSize::Small))
.child(
Label::new("Waiting for Confirmation…")
.color(Color::Muted)
.size(LabelSize::Small)
.with_animation(
"generating-label",
Animation::new(Duration::from_secs(1)).repeat(),
|mut label, delta| {
let text = match delta {
d if d < 0.25 => "Waiting for Confirmation",
d if d < 0.5 => "Waiting for Confirmation.",
d if d < 0.75 => "Waiting for Confirmation..",
_ => "Waiting for Confirmation...",
};
label.set_text(text);
label
},
)
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.6, 1.)),
|label, delta| label.map_element(|label| label.alpha(delta)),
),
)
.child(
h_flex()
.gap_0p5()
@@ -2411,7 +2575,7 @@ impl ActiveThread {
};
div()
.pt_1()
.pt_2()
.px_2p5()
.child(
h_flex()

View File

@@ -1487,6 +1487,7 @@ impl Thread {
tool_use_id.clone(),
tool_name,
output,
cx,
);
cx.emit(ThreadEvent::ToolFinished {
@@ -1831,7 +1832,7 @@ impl Thread {
));
self.tool_use
.insert_tool_output(tool_use_id.clone(), tool_name, err);
.insert_tool_output(tool_use_id.clone(), tool_name, err, cx);
cx.emit(ThreadEvent::ToolFinished {
tool_use_id,

View File

@@ -6,11 +6,13 @@ use collections::HashMap;
use futures::FutureExt as _;
use futures::future::Shared;
use gpui::{App, SharedString, Task};
use language_model::ToolOutput;
use language_model::{
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role,
LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolResult,
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
};
use ui::IconName;
use util::truncate_lines_to_byte_limit;
use crate::thread::MessageId;
use crate::thread_store::SerializedMessage;
@@ -31,7 +33,7 @@ pub enum ToolUseStatus {
NeedsConfirmation,
Pending,
Running,
Finished(SharedString),
Finished(ToolOutput),
Error(SharedString),
}
@@ -130,6 +132,7 @@ impl ToolUseState {
tool_name: tool_use.clone(),
is_error: tool_result.is_error,
content: tool_result.content.clone(),
tool_output: None,
},
);
}
@@ -152,6 +155,7 @@ impl ToolUseState {
tool_name: tool_use.name.clone(),
content: "Tool canceled by user".into(),
is_error: true,
tool_output: None,
},
);
pending_tools.push(tool_use.clone());
@@ -330,17 +334,44 @@ impl ToolUseState {
&mut self,
tool_use_id: LanguageModelToolUseId,
tool_name: Arc<str>,
output: Result<String>,
output: Result<ToolOutput>,
cx: &App,
) -> Option<PendingToolUse> {
match output {
Ok(tool_result) => {
let model_registry = LanguageModelRegistry::read_global(cx);
const BYTES_PER_TOKEN_ESTIMATE: usize = 3;
// Protect from clearly large output
let tool_output_limit = model_registry
.default_model()
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
.unwrap_or(usize::MAX);
// Get string representation of the tool result
let response_text = tool_result.response_for_model();
// Check length and truncate if needed
let final_tool_result = if response_text.len() <= tool_output_limit {
response_text.to_string()
} else {
let response_string = response_text.to_string();
let truncated =
truncate_lines_to_byte_limit(&response_string, tool_output_limit);
let truncated_len = truncated.len();
format!("Tool result too long. The first {truncated_len} bytes:\n\n{truncated}")
};
self.tool_results.insert(
tool_use_id.clone(),
LanguageModelToolResult {
tool_use_id: tool_use_id.clone(),
tool_name,
content: tool_result.into(),
content: Arc::from(final_tool_result),
is_error: false,
tool_output: Some(Arc::new(tool_result)),
},
);
self.pending_tool_uses_by_id.remove(&tool_use_id)
@@ -353,6 +384,7 @@ impl ToolUseState {
tool_name,
content: err.to_string().into(),
is_error: true,
tool_output: None,
},
);
@@ -427,6 +459,7 @@ impl ToolUseState {
} else {
tool_result.content.clone()
},
tool_output: tool_result.tool_output.clone(),
},
));
}

View File

@@ -8,13 +8,15 @@ use std::fmt::Formatter;
use std::sync::Arc;
use anyhow::Result;
use gpui::{App, Entity, SharedString, Task};
use gpui::{self, App, Entity, SharedString, Task};
use icons::IconName;
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
use language_model::ToolOutput;
use project::Project;
pub use crate::action_log::*;
// StringToolOutput is now directly imported from language_model
pub use crate::tool_registry::*;
pub use crate::tool_working_set::*;
@@ -66,7 +68,7 @@ pub trait Tool: 'static + Send + Sync {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>>;
) -> Task<Result<ToolOutput>>;
}
impl Debug for dyn Tool {

View File

@@ -23,7 +23,6 @@ http_client.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
lsp.workspace = true
project.workspace = true
regex.workspace = true
schemars.workspace = true

View File

@@ -1,6 +1,5 @@
mod bash_tool;
mod batch_tool;
mod code_symbol_iter;
mod code_symbols_tool;
mod copy_path_tool;
mod create_directory_tool;

View File

@@ -1,6 +1,9 @@
use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use futures::io::BufReader;
use futures::{AsyncBufReadExt, AsyncReadExt};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -14,7 +17,7 @@ use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct BashToolInput {
/// The bash command to execute as a one-liner.
/// The bash one-liner command to execute.
command: String,
/// Working directory for the command. This must be one of the root directories of the project.
cd: String,
@@ -74,7 +77,7 @@ impl Tool for BashTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input: BashToolInput = match serde_json::from_value(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -121,32 +124,89 @@ impl Tool for BashTool {
worktree.read(cx).abs_path()
};
cx.spawn(async move |_| {
cx.spawn(async move |_| -> Result<ToolOutput> {
// Add 2>&1 to merge stderr into stdout for proper interleaving.
let command = format!("({}) 2>&1", input.command);
let output = new_smol_command("bash")
let mut cmd = new_smol_command("bash")
.arg("-c")
.arg(&command)
.current_dir(working_dir)
.output()
.await
.stdout(std::process::Stdio::piped())
.spawn()
.context("Failed to execute bash command")?;
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
// Capture stdout with a limit
let stdout = cmd.stdout.take().unwrap();
let mut reader = BufReader::new(stdout);
if output.status.success() {
const MESSAGE_1: &str = "Command output too long. The first ";
const MESSAGE_2: &str = " bytes:\n\n";
const ERR_MESSAGE_1: &str = "Command failed with exit code ";
const ERR_MESSAGE_2: &str = "\n\n";
const STDOUT_LIMIT: usize = 8192;
const LIMIT: usize = STDOUT_LIMIT
- (MESSAGE_1.len()
+ (STDOUT_LIMIT.ilog10() as usize + 1) // byte count
+ MESSAGE_2.len()
+ ERR_MESSAGE_1.len()
+ 3 // status code
+ ERR_MESSAGE_2.len());
// Read one more byte to determine whether the output was truncated
let mut buffer = vec![0; LIMIT + 1];
let bytes_read = reader.read(&mut buffer).await?;
// Repeatedly fill the output reader's buffer without copying it.
loop {
let skipped_bytes = reader.fill_buf().await?;
if skipped_bytes.is_empty() {
break;
}
let skipped_bytes_len = skipped_bytes.len();
reader.consume_unpin(skipped_bytes_len);
}
let output_bytes = &buffer[..bytes_read];
// Let the process continue running
let status = cmd.status().await.context("Failed to get command status")?;
let output_string = if bytes_read > LIMIT {
// Valid to find `\n` in UTF-8 since 0-127 ASCII characters are not used in
// multi-byte characters.
let last_line_ix = output_bytes.iter().rposition(|b| *b == b'\n');
let output_string = String::from_utf8_lossy(
&output_bytes[..last_line_ix.unwrap_or(output_bytes.len())],
);
format!(
"{}{}{}{}",
MESSAGE_1,
output_string.len(),
MESSAGE_2,
output_string
)
} else {
String::from_utf8_lossy(&output_bytes).into()
};
if status.success() {
if output_string.is_empty() {
Ok("Command executed successfully.".to_string())
Ok(StringToolOutput::new("Command executed successfully."))
} else {
Ok(output_string)
Ok(StringToolOutput::new(output_string))
}
} else {
Ok(format!(
"Command failed with exit code {}\n{}",
output.status.code().unwrap_or(-1),
&output_string
))
Ok(StringToolOutput::new(format!(
"{}{}{}{}",
ERR_MESSAGE_1,
status.code().unwrap_or(-1),
ERR_MESSAGE_2,
output_string,
)))
}
})
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
use language_model::{ToolOutput, StringToolOutput};
use futures::future::join_all;
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -210,7 +211,7 @@ impl Tool for BatchTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<BatchToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -280,7 +281,7 @@ impl Tool for BatchTool {
match result {
Ok(output) => {
formatted_results
.push_str(&format!("Tool '{}' result:\n{}\n\n", tool_name, output));
.push_str(&format!("Tool '{}' result:\n{}\n\n", tool_name, output.response_for_model()));
}
Err(err) => {
error_occurred = true;
@@ -295,7 +296,7 @@ impl Tool for BatchTool {
.push_str("Note: Some tool invocations failed. See individual results above.");
}
Ok(formatted_results.trim().to_string())
Ok(StringToolOutput::new(formatted_results.trim().to_string()))
})
}
}

View File

@@ -1,88 +0,0 @@
use project::DocumentSymbol;
use regex::Regex;
#[derive(Debug, Clone)]
pub struct Entry {
pub name: String,
pub kind: lsp::SymbolKind,
pub depth: u32,
pub start_line: usize,
pub end_line: usize,
}
/// An iterator that filters document symbols based on a regex pattern.
/// This iterator recursively traverses the document symbol tree, incrementing depth for child symbols.
#[derive(Debug, Clone)]
pub struct CodeSymbolIterator<'a> {
symbols: &'a [DocumentSymbol],
regex: Option<Regex>,
// Stack of (symbol, depth) pairs to process
pending_symbols: Vec<(&'a DocumentSymbol, u32)>,
current_index: usize,
current_depth: u32,
}
impl<'a> CodeSymbolIterator<'a> {
pub fn new(symbols: &'a [DocumentSymbol], regex: Option<Regex>) -> Self {
Self {
symbols,
regex,
pending_symbols: Vec::new(),
current_index: 0,
current_depth: 0,
}
}
}
impl Iterator for CodeSymbolIterator<'_> {
type Item = Entry;
fn next(&mut self) -> Option<Self::Item> {
if let Some((symbol, depth)) = self.pending_symbols.pop() {
for child in symbol.children.iter().rev() {
self.pending_symbols.push((child, depth + 1));
}
return Some(Entry {
name: symbol.name.clone(),
kind: symbol.kind,
depth,
start_line: symbol.range.start.0.row as usize,
end_line: symbol.range.end.0.row as usize,
});
}
while self.current_index < self.symbols.len() {
let regex = self.regex.as_ref();
let symbol = &self.symbols[self.current_index];
self.current_index += 1;
if regex.is_none_or(|regex| regex.is_match(&symbol.name)) {
// Push in reverse order to maintain traversal order
for child in symbol.children.iter().rev() {
self.pending_symbols.push((child, self.current_depth + 1));
}
return Some(Entry {
name: symbol.name.clone(),
kind: symbol.kind,
depth: self.current_depth,
start_line: symbol.range.start.0.row as usize,
end_line: symbol.range.end.0.row as usize,
});
} else {
// Even if parent doesn't match, push children to check them later
for child in symbol.children.iter().rev() {
self.pending_symbols.push((child, self.current_depth + 1));
}
// Check if any pending children match our criteria
if let Some(result) = self.next() {
return Some(result);
}
}
}
None
}
}

View File

@@ -1,24 +1,22 @@
use std::fmt::{self, Write};
use std::fmt::Write;
use std::path::PathBuf;
use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use collections::IndexMap;
use gpui::{App, AsyncApp, Entity, Task};
use language::{CodeLabel, Language, LanguageRegistry};
use language::{OutlineItem, ParseStatus, Point};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use lsp::SymbolKind;
use project::{DocumentSymbol, Project, Symbol};
use project::{Project, Symbol};
use regex::{Regex, RegexBuilder};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ui::IconName;
use util::markdown::MarkdownString;
use crate::code_symbol_iter::{CodeSymbolIterator, Entry};
use crate::schema::json_schema_for;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CodeSymbolsInput {
/// The relative path of the source code file to read and get the symbols for.
@@ -132,7 +130,7 @@ impl Tool for CodeSymbolsTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<CodeSymbolsInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -150,8 +148,8 @@ impl Tool for CodeSymbolsTool {
};
cx.spawn(async move |cx| match input.path {
Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await,
None => project_symbols(project, regex, input.offset, cx).await,
Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await.map(StringToolOutput::new),
None => project_symbols(project, regex, input.offset, cx).await.map(StringToolOutput::new),
})
}
}
@@ -180,24 +178,28 @@ pub async fn file_outline(
action_log.buffer_read(buffer.clone(), cx);
})?;
let symbols = project
.update(cx, |project, cx| project.document_symbols(&buffer, cx))?
.await?;
// Wait until the buffer has been fully parsed, so that we can read its outline.
let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
while parse_status
.recv()
.await
.map_or(false, |status| status != ParseStatus::Idle)
{}
if symbols.is_empty() {
return Err(
if buffer.read_with(cx, |buffer, _| buffer.snapshot().is_empty())? {
anyhow!("This file is empty.")
} else {
anyhow!("No outline information available for this file.")
},
);
}
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
let Some(outline) = snapshot.outline(None) else {
return Err(anyhow!("No outline information available for this file."));
};
let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned())?;
let language_registry = project.read_with(cx, |project, _| project.languages().clone())?;
render_outline(&symbols, language, language_registry, regex, offset).await
render_outline(
outline
.items
.into_iter()
.map(|item| item.to_point(&snapshot)),
regex,
offset,
)
.await
}
async fn project_symbols(
@@ -292,61 +294,27 @@ async fn project_symbols(
}
async fn render_outline(
symbols: &[DocumentSymbol],
language: Option<Arc<Language>>,
registry: Arc<LanguageRegistry>,
items: impl IntoIterator<Item = OutlineItem<Point>>,
regex: Option<Regex>,
offset: u32,
) -> Result<String> {
const RESULTS_PER_PAGE_USIZE: usize = RESULTS_PER_PAGE as usize;
let entries = CodeSymbolIterator::new(symbols, regex.clone())
.skip(offset as usize)
// Take 1 more than RESULTS_PER_PAGE so we can tell if there are more results.
.take(RESULTS_PER_PAGE_USIZE.saturating_add(1))
.collect::<Vec<Entry>>();
let has_more = entries.len() > RESULTS_PER_PAGE_USIZE;
// Get language-specific labels, if available
let labels = match &language {
Some(lang) => {
let entries_for_labels: Vec<(String, SymbolKind)> = entries
.iter()
.take(RESULTS_PER_PAGE_USIZE)
.map(|entry| (entry.name.clone(), entry.kind))
.collect();
let mut items = items.into_iter().skip(offset as usize);
let lang_name = lang.name();
if let Some(lsp_adapter) = registry.lsp_adapters(&lang_name).first().cloned() {
lsp_adapter
.labels_for_symbols(&entries_for_labels, lang)
.await
.ok()
} else {
None
}
}
None => None,
};
let entries = items
.by_ref()
.filter(|item| {
regex
.as_ref()
.is_none_or(|regex| regex.is_match(&item.text))
})
.take(RESULTS_PER_PAGE_USIZE)
.collect::<Vec<_>>();
let has_more = items.next().is_some();
let mut output = String::new();
let entries_rendered = match &labels {
Some(label_list) => render_entries(
&mut output,
entries
.into_iter()
.take(RESULTS_PER_PAGE_USIZE)
.zip(label_list.iter())
.map(|(entry, label)| (entry, label.as_ref())),
),
None => render_entries(
&mut output,
entries
.into_iter()
.take(RESULTS_PER_PAGE_USIZE)
.map(|entry| (entry, None)),
),
};
let entries_rendered = render_entries(&mut output, entries);
// Calculate pagination information
let page_start = offset + 1;
@@ -372,31 +340,19 @@ async fn render_outline(
Ok(output)
}
fn render_entries<'a>(
output: &mut String,
entries: impl IntoIterator<Item = (Entry, Option<&'a CodeLabel>)>,
) -> u32 {
fn render_entries(output: &mut String, items: impl IntoIterator<Item = OutlineItem<Point>>) -> u32 {
let mut entries_rendered = 0;
for (entry, label) in entries {
for item in items {
// Indent based on depth ("" for level 0, " " for level 1, etc.)
for _ in 0..entry.depth {
output.push_str(" ");
}
match label {
Some(label) => {
output.push_str(label.text());
}
None => {
write_symbol_kind(output, entry.kind).ok();
output.push_str(&entry.name);
}
for _ in 0..item.depth {
output.push(' ');
}
output.push_str(&item.text);
// Add position information - convert to 1-based line numbers for display
let start_line = entry.start_line + 1;
let end_line = entry.end_line + 1;
let start_line = item.range.start.row + 1;
let end_line = item.range.end.row + 1;
if start_line == end_line {
writeln!(output, " [L{}]", start_line).ok();
@@ -408,38 +364,3 @@ fn render_entries<'a>(
entries_rendered
}
// We may not have a language server adapter to have language-specific
// ways to translate SymbolKnd into a string. In that situation,
// fall back on some reasonable default strings to render.
fn write_symbol_kind(buf: &mut String, kind: SymbolKind) -> Result<(), fmt::Error> {
match kind {
SymbolKind::FILE => write!(buf, "file "),
SymbolKind::MODULE => write!(buf, "module "),
SymbolKind::NAMESPACE => write!(buf, "namespace "),
SymbolKind::PACKAGE => write!(buf, "package "),
SymbolKind::CLASS => write!(buf, "class "),
SymbolKind::METHOD => write!(buf, "method "),
SymbolKind::PROPERTY => write!(buf, "property "),
SymbolKind::FIELD => write!(buf, "field "),
SymbolKind::CONSTRUCTOR => write!(buf, "constructor "),
SymbolKind::ENUM => write!(buf, "enum "),
SymbolKind::INTERFACE => write!(buf, "interface "),
SymbolKind::FUNCTION => write!(buf, "function "),
SymbolKind::VARIABLE => write!(buf, "variable "),
SymbolKind::CONSTANT => write!(buf, "constant "),
SymbolKind::STRING => write!(buf, "string "),
SymbolKind::NUMBER => write!(buf, "number "),
SymbolKind::BOOLEAN => write!(buf, "boolean "),
SymbolKind::ARRAY => write!(buf, "array "),
SymbolKind::OBJECT => write!(buf, "object "),
SymbolKind::KEY => write!(buf, "key "),
SymbolKind::NULL => write!(buf, "null "),
SymbolKind::ENUM_MEMBER => write!(buf, "enum member "),
SymbolKind::STRUCT => write!(buf, "struct "),
SymbolKind::EVENT => write!(buf, "event "),
SymbolKind::OPERATOR => write!(buf, "operator "),
SymbolKind::TYPE_PARAMETER => write!(buf, "type parameter "),
_ => Ok(()),
}
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, AppContext, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -77,7 +78,7 @@ impl Tool for CopyPathTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<CopyPathToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -105,10 +106,10 @@ impl Tool for CopyPathTool {
cx.background_spawn(async move {
match copy_task.await {
Ok(_) => Ok(format!(
Ok(_) => Ok(StringToolOutput::new(format!(
"Copied {} to {}",
input.source_path, input.destination_path
)),
))),
Err(err) => Err(anyhow!(
"Failed to copy {} to {}: {}",
input.source_path,

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -68,7 +69,7 @@ impl Tool for CreateDirectoryTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -87,7 +88,7 @@ impl Tool for CreateDirectoryTool {
.await
.map_err(|err| anyhow!("Unable to create directory {destination_path}: {err}"))?;
Ok(format!("Created directory {destination_path}"))
Ok(StringToolOutput::new(format!("Created directory {destination_path}")))
})
}
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -73,7 +74,7 @@ impl Tool for CreateFileTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<CreateFileToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -104,7 +105,7 @@ impl Tool for CreateFileTool {
.await
.map_err(|err| anyhow!("Unable to save buffer for {destination_path}: {err}"))?;
Ok(format!("Created file {destination_path}"))
Ok(StringToolOutput::new(format!("Created file {destination_path}")))
})
}
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use futures::{SinkExt, StreamExt, channel::mpsc};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -63,7 +64,7 @@ impl Tool for DeletePathTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let path_str = match serde_json::from_value::<DeletePathToolInput>(input) {
Ok(input) => input.path,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -124,7 +125,7 @@ impl Tool for DeletePathTool {
match delete {
Some(deletion_task) => match deletion_task.await {
Ok(()) => Ok(format!("Deleted {path_str}")),
Ok(()) => Ok(StringToolOutput::new(format!("Deleted {path_str}"))),
Err(err) => Err(anyhow!("Failed to delete {path_str}: {err}")),
},
None => Err(anyhow!(

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use language::{DiagnosticSeverity, OffsetRangeExt};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -83,7 +84,7 @@ impl Tool for DiagnosticsTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
match serde_json::from_value::<DiagnosticsToolInput>(input)
.ok()
.and_then(|input| input.path)
@@ -120,9 +121,9 @@ impl Tool for DiagnosticsTool {
}
if output.is_empty() {
Ok("File doesn't have errors or warnings!".to_string())
Ok(StringToolOutput::new("File doesn't have errors or warnings!"))
} else {
Ok(output)
Ok(StringToolOutput::new(output))
}
})
}
@@ -155,9 +156,9 @@ impl Tool for DiagnosticsTool {
});
if has_diagnostics {
Task::ready(Ok(output))
Task::ready(Ok(StringToolOutput::new(output)))
} else {
Task::ready(Ok("No errors or warnings found in the project.".to_string()))
Task::ready(Ok(StringToolOutput::new("No errors or warnings found in the project.")))
}
}
}

View File

@@ -5,6 +5,7 @@ use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow, bail};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use futures::AsyncReadExt as _;
use gpui::{App, AppContext as _, Entity, Task};
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
@@ -146,7 +147,7 @@ impl Tool for FetchTool {
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<FetchToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -164,7 +165,7 @@ impl Tool for FetchTool {
bail!("no textual content found");
}
Ok(text)
Ok(StringToolOutput::new(text))
})
}
}

View File

@@ -1,6 +1,7 @@
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, AppContext, AsyncApp, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -159,7 +160,7 @@ impl Tool for FindReplaceFileTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<FindReplaceFileToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -251,7 +252,7 @@ impl Tool for FindReplaceFileTool {
}).await;
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
Ok(StringToolOutput::new(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str)))
})
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -77,7 +78,7 @@ impl Tool for ListDirectoryTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<ListDirectoryToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -101,7 +102,7 @@ impl Tool for ListDirectoryTool {
.collect::<Vec<_>>()
.join("\n");
return Task::ready(Ok(output));
return Task::ready(Ok(StringToolOutput::new(output)));
}
let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else {
@@ -133,8 +134,8 @@ impl Tool for ListDirectoryTool {
.unwrap();
}
if output.is_empty() {
return Task::ready(Ok(format!("{} is empty.", input.path)));
return Task::ready(Ok(StringToolOutput::new(format!("{} is empty.", input.path))));
}
Task::ready(Ok(output))
Task::ready(Ok(StringToolOutput::new(output)))
}
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -90,7 +91,7 @@ impl Tool for MovePathTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<MovePathToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -116,10 +117,10 @@ impl Tool for MovePathTool {
cx.background_spawn(async move {
match rename_task.await {
Ok(_) => Ok(format!(
Ok(_) => Ok(StringToolOutput::new(format!(
"Moved {} to {}",
input.source_path, input.destination_path
)),
))),
Err(err) => Err(anyhow!(
"Failed to move {} to {}: {}",
input.source_path,

View File

@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use chrono::{Local, Utc};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -60,7 +61,7 @@ impl Tool for NowTool {
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input: NowToolInput = match serde_json::from_value(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -72,6 +73,6 @@ impl Tool for NowTool {
};
let text = format!("The current datetime is {now}.");
Task::ready(Ok(text))
Task::ready(Ok(StringToolOutput::new(text)))
}
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -53,7 +54,7 @@ impl Tool for OpenTool {
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input: OpenToolInput = match serde_json::from_value(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -62,7 +63,7 @@ impl Tool for OpenTool {
cx.background_spawn(async move {
open::that(&input.path_or_url).context("Failed to open URL or file path")?;
Ok(format!("Successfully opened {}", input.path_or_url))
Ok(StringToolOutput::new(format!("Successfully opened {}", input.path_or_url)))
})
}
}

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -71,7 +72,7 @@ impl Tool for PathSearchTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let (offset, glob) = match serde_json::from_value::<PathSearchToolInput>(input) {
Ok(input) => (input.offset, input.glob),
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -110,7 +111,7 @@ impl Tool for PathSearchTool {
}
if matches.is_empty() {
Ok(format!("No paths in the project matched the glob {glob:?}"))
Ok(StringToolOutput::new(format!("No paths in the project matched the glob {glob:?}")))
} else {
// Sort to group entries in the same directory together.
matches.sort();
@@ -134,7 +135,7 @@ impl Tool for PathSearchTool {
matches.join("\n")
};
Ok(response)
Ok(StringToolOutput::new(response))
}
})
}

View File

@@ -1,9 +1,9 @@
use std::sync::Arc;
use crate::code_symbols_tool::file_outline;
use crate::schema::json_schema_for;
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use itertools::Itertools;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -16,7 +16,7 @@ use util::markdown::MarkdownString;
/// If the model requests to read a file whose size exceeds this, then
/// the tool will return an error along with the model's symbol outline,
/// and suggest trying again using line ranges from the outline.
const MAX_FILE_SIZE_TO_READ: usize = 4096;
const MAX_FILE_SIZE_TO_READ: usize = 16384;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ReadFileToolInput {
@@ -89,7 +89,7 @@ impl Tool for ReadFileTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<ReadFileToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -115,9 +115,9 @@ impl Tool for ReadFileTool {
let lines = text.split('\n').skip(start - 1);
if let Some(end) = input.end_line {
let count = end.saturating_sub(start).max(1); // Ensure at least 1 line
Itertools::intersperse(lines.take(count), "\n").collect()
Itertools::intersperse(lines.take(count), "\n").collect::<String>()
} else {
Itertools::intersperse(lines, "\n").collect()
Itertools::intersperse(lines, "\n").collect::<String>()
}
})?;
@@ -125,7 +125,7 @@ impl Tool for ReadFileTool {
log.buffer_read(buffer, cx);
})?;
Ok(result)
Ok(StringToolOutput::new(result))
} else {
// No line ranges specified, so check file size to see if it's too big.
let file_size = buffer.read_with(cx, |buffer, _cx| buffer.text().len())?;
@@ -138,13 +138,13 @@ impl Tool for ReadFileTool {
log.buffer_read(buffer, cx);
})?;
Ok(result)
Ok(StringToolOutput::new(result))
} else {
// File is too big, so return an error with the outline
// and a suggestion to read again with line numbers.
let outline = file_outline(project, file_path, action_log, None, 0, cx).await?;
Ok(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline."))
Ok(StringToolOutput::new(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline.")))
}
}
})

View File

@@ -1,6 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use futures::StreamExt;
use gpui::{App, Entity, Task};
use language::OffsetRangeExt;
@@ -83,7 +84,7 @@ impl Tool for RegexSearchTool {
project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
const CONTEXT_LINES: u32 = 2;
let (offset, regex) = match serde_json::from_value::<RegexSearchToolInput>(input) {
@@ -179,16 +180,16 @@ impl Tool for RegexSearchTool {
}
if matches_found == 0 {
Ok("No matches found".to_string())
Ok(StringToolOutput::new("No matches found".to_string()))
} else if has_more_matches {
Ok(format!(
Ok(StringToolOutput::new(format!(
"Showing matches {}-{} (there were more matches found; use offset: {} to see next page):\n{output}",
offset + 1,
offset + matches_found,
offset + RESULTS_PER_PAGE,
))
)))
} else {
Ok(format!("Found {matches_found} matches:\n{output}"))
Ok(StringToolOutput::new(format!("Found {matches_found} matches:\n{output}")))
}
})
}

View File

@@ -1,5 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, AsyncApp, Entity, Task};
use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -122,7 +123,7 @@ impl Tool for SymbolInfoTool {
project: Entity<Project>,
action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
let input = match serde_json::from_value::<SymbolInfoToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
@@ -203,7 +204,7 @@ impl Tool for SymbolInfoTool {
if output.is_empty() {
Err(anyhow!("None found."))
} else {
Ok(output)
Ok(StringToolOutput::new(output))
}
})
}

View File

@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -51,10 +52,10 @@ impl Tool for ThinkingTool {
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
// This tool just "thinks out loud" and doesn't perform any actions.
Task::ready(match serde_json::from_value::<ThinkingToolInput>(input) {
Ok(_input) => Ok("Finished thinking.".to_string()),
Ok(_input) => Ok(StringToolOutput::new("Finished thinking.")),
Err(err) => Err(anyhow!(err)),
})
}

View File

@@ -944,8 +944,8 @@ impl Room {
)
})?;
if self.live_kit.as_ref().map_or(true, |kit| kit.deafened) {
if matches!(track, livekit_client::RemoteTrack::Audio(_)) {
track.set_enabled(false, cx);
if publication.is_audio() {
publication.set_enabled(false, cx);
}
}
match track {

View File

@@ -18,7 +18,7 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
[dependencies]
anyhow.workspace = true
async-recursion = "0.3"
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
async-tungstenite = { workspace = true, features = ["tokio", "tokio-rustls-manual-roots"] }
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
collections.workspace = true
@@ -26,6 +26,7 @@ credentials_provider.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_tokio.workspace = true
http_client.workspace = true
http_client_tls.workspace = true
log.workspace = true
@@ -51,6 +52,7 @@ url.workspace = true
util.workspace = true
worktree.workspace = true
telemetry.workspace = true
tokio.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
@@ -67,4 +69,3 @@ windows.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
cocoa.workspace = true
async-native-tls = { version = "0.5.0", features = ["vendored"] }

View File

@@ -20,7 +20,7 @@ use futures::{
AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt,
channel::oneshot, future::BoxFuture,
};
use gpui::{App, AppContext as _, AsyncApp, Entity, Global, Task, WeakEntity, actions};
use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use parking_lot::RwLock;
use postage::watch;
@@ -1086,7 +1086,7 @@ impl Client {
let rpc_url = self.rpc_url(http, release_channel);
let system_id = self.telemetry.system_id();
let metrics_id = self.telemetry.metrics_id();
cx.background_spawn(async move {
cx.spawn(async move |cx| {
use HttpOrHttps::*;
#[derive(Debug)]
@@ -1105,7 +1105,12 @@ impl Client {
.host_str()
.zip(rpc_url.port_or_known_default())
.ok_or_else(|| anyhow!("missing host in rpc url"))?;
let stream = connect_socks_proxy_stream(proxy.as_ref(), rpc_host).await?;
let stream = {
let handle = cx.update(|cx| gpui_tokio::Tokio::handle(cx)).ok().unwrap();
let _guard = handle.enter();
connect_socks_proxy_stream(proxy.as_ref(), rpc_host).await?
};
log::info!("connected to rpc endpoint {}", rpc_url);
@@ -1144,30 +1149,19 @@ impl Client {
request_headers.insert("x-zed-metrics-id", HeaderValue::from_str(&metrics_id)?);
}
match url_scheme {
Https => {
let (stream, _) =
async_tungstenite::async_tls::client_async_tls_with_connector(
request,
stream,
Some(http_client_tls::tls_config().into()),
)
.await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
.sink_map_err(|error| anyhow!(error)),
))
}
Http => {
let (stream, _) = async_tungstenite::client_async(request, stream).await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
.sink_map_err(|error| anyhow!(error)),
))
}
}
let (stream, _) = async_tungstenite::tokio::client_async_tls_with_connector_and_config(
request,
stream,
Some(Arc::new(http_client_tls::tls_config()).into()),
None,
)
.await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
.sink_map_err(|error| anyhow!(error)),
))
})
}
@@ -1639,7 +1633,7 @@ mod tests {
use crate::test::FakeServer;
use clock::FakeSystemClock;
use gpui::{BackgroundExecutor, TestAppContext};
use gpui::{AppContext as _, BackgroundExecutor, TestAppContext};
use http_client::FakeHttpClient;
use parking_lot::Mutex;
use proto::TypedEnvelope;

View File

@@ -1,11 +1,7 @@
//! socks proxy
use anyhow::{Result, anyhow};
use futures::io::{AsyncRead, AsyncWrite};
use http_client::Uri;
use tokio_socks::{
io::Compat,
tcp::{Socks4Stream, Socks5Stream},
};
use tokio_socks::tcp::{Socks4Stream, Socks5Stream};
pub(crate) async fn connect_socks_proxy_stream(
proxy: Option<&Uri>,
@@ -14,7 +10,7 @@ pub(crate) async fn connect_socks_proxy_stream(
let stream = match parse_socks_proxy(proxy) {
Some((socks_proxy, SocksVersion::V4)) => {
let stream = Socks4Stream::connect_with_socket(
Compat::new(smol::net::TcpStream::connect(socks_proxy).await?),
tokio::net::TcpStream::connect(socks_proxy).await?,
rpc_host,
)
.await
@@ -23,13 +19,15 @@ pub(crate) async fn connect_socks_proxy_stream(
}
Some((socks_proxy, SocksVersion::V5)) => Box::new(
Socks5Stream::connect_with_socket(
Compat::new(smol::net::TcpStream::connect(socks_proxy).await?),
tokio::net::TcpStream::connect(socks_proxy).await?,
rpc_host,
)
.await
.map_err(|err| anyhow!("error connecting to socks {}", err))?,
) as Box<dyn AsyncReadWrite>,
None => Box::new(smol::net::TcpStream::connect(rpc_host).await?) as Box<dyn AsyncReadWrite>,
None => {
Box::new(tokio::net::TcpStream::connect(rpc_host).await?) as Box<dyn AsyncReadWrite>
}
};
Ok(stream)
}
@@ -60,5 +58,11 @@ enum SocksVersion {
V5,
}
pub(crate) trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send + 'static {}
impl<T: AsyncRead + AsyncWrite + Unpin + Send + 'static> AsyncReadWrite for T {}
pub(crate) trait AsyncReadWrite:
tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
{
}
impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static> AsyncReadWrite
for T
{
}

View File

@@ -33,7 +33,6 @@ clock.workspace = true
collections.workspace = true
dashmap.workspace = true
derive_more.workspace = true
buffer_diff.workspace = true
envy = "0.4.2"
futures.workspace = true
google_ai.workspace = true
@@ -85,6 +84,7 @@ assistant_slash_command.workspace = true
assistant_tool.workspace = true
async-trait.workspace = true
audio.workspace = true
buffer_diff.workspace = true
call = { workspace = true, features = ["test-support"] }
channel.workspace = true
client = { workspace = true, features = ["test-support"] }

View File

@@ -4153,13 +4153,13 @@ async fn get_llm_api_token(
fn to_axum_message(message: TungsteniteMessage) -> anyhow::Result<AxumMessage> {
let message = match message {
TungsteniteMessage::Text(payload) => AxumMessage::Text(payload),
TungsteniteMessage::Binary(payload) => AxumMessage::Binary(payload),
TungsteniteMessage::Ping(payload) => AxumMessage::Ping(payload),
TungsteniteMessage::Pong(payload) => AxumMessage::Pong(payload),
TungsteniteMessage::Text(payload) => AxumMessage::Text(payload.as_str().to_string()),
TungsteniteMessage::Binary(payload) => AxumMessage::Binary(payload.into()),
TungsteniteMessage::Ping(payload) => AxumMessage::Ping(payload.into()),
TungsteniteMessage::Pong(payload) => AxumMessage::Pong(payload.into()),
TungsteniteMessage::Close(frame) => AxumMessage::Close(frame.map(|frame| AxumCloseFrame {
code: frame.code.into(),
reason: frame.reason,
reason: frame.reason.as_str().to_owned().into(),
})),
// We should never receive a frame while reading the message, according
// to the `tungstenite` maintainers:
@@ -4179,14 +4179,14 @@ fn to_axum_message(message: TungsteniteMessage) -> anyhow::Result<AxumMessage> {
fn to_tungstenite_message(message: AxumMessage) -> TungsteniteMessage {
match message {
AxumMessage::Text(payload) => TungsteniteMessage::Text(payload),
AxumMessage::Binary(payload) => TungsteniteMessage::Binary(payload),
AxumMessage::Ping(payload) => TungsteniteMessage::Ping(payload),
AxumMessage::Pong(payload) => TungsteniteMessage::Pong(payload),
AxumMessage::Text(payload) => TungsteniteMessage::Text(payload.into()),
AxumMessage::Binary(payload) => TungsteniteMessage::Binary(payload.into()),
AxumMessage::Ping(payload) => TungsteniteMessage::Ping(payload.into()),
AxumMessage::Pong(payload) => TungsteniteMessage::Pong(payload.into()),
AxumMessage::Close(frame) => {
TungsteniteMessage::Close(frame.map(|frame| TungsteniteCloseFrame {
code: frame.code.into(),
reason: frame.reason,
reason: frame.reason.as_ref().into(),
}))
}
}

View File

@@ -2,6 +2,7 @@ use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use assistant_tool::{ActionLog, Tool, ToolSource};
use language_model::{ToolOutput, StringToolOutput};
use gpui::{App, Entity, Task};
use icons::IconName;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -76,7 +77,7 @@ impl Tool for ContextServerTool {
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
) -> Task<Result<ToolOutput>> {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
let tool_name = self.tool.name.clone();
let server_clone = server.clone();
@@ -114,7 +115,7 @@ impl Tool for ContextServerTool {
}
}
}
Ok(result)
Ok(StringToolOutput::new(result))
})
} else {
Task::ready(Err(anyhow!("Context server not found")))

View File

@@ -1280,10 +1280,6 @@ mod tests {
unimplemented!()
}
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!()
}
fn to_proto(&self, _: &App) -> rpc::proto::File {
unimplemented!()
}

View File

@@ -12,8 +12,8 @@ use dap::{
};
use futures::{SinkExt as _, channel::mpsc};
use gpui::{
Action, App, AsyncWindowContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
Subscription, Task, WeakEntity, actions,
Action, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter, FocusHandle,
Focusable, Subscription, Task, WeakEntity, actions,
};
use project::{
Project,
@@ -336,6 +336,95 @@ impl DebugPanel {
})
}
fn close_session(&mut self, entity_id: EntityId, cx: &mut Context<Self>) {
let Some(session) = self
.sessions
.iter()
.find(|other| entity_id == other.entity_id())
else {
return;
};
session.update(cx, |session, cx| session.shutdown(cx));
self.sessions.retain(|other| entity_id != other.entity_id());
if let Some(active_session_id) = self
.active_session
.as_ref()
.map(|session| session.entity_id())
{
if active_session_id == entity_id {
self.active_session = self.sessions.first().cloned();
}
}
}
fn sessions_drop_down_menu(
&self,
active_session: &Entity<DebugSession>,
window: &mut Window,
cx: &mut Context<Self>,
) -> DropdownMenu {
let sessions = self.sessions.clone();
let weak = cx.weak_entity();
let label = active_session.read(cx).label_element(cx);
DropdownMenu::new_with_element(
"debugger-session-list",
label,
ContextMenu::build(window, cx, move |mut this, _, _| {
for session in sessions.into_iter() {
let weak_session = session.downgrade();
let weak_id = weak_session.entity_id();
this = this.custom_entry(
{
let weak = weak.clone();
move |_, cx| {
weak_session
.read_with(cx, |session, cx| {
h_flex()
.w_full()
.justify_between()
.child(session.label_element(cx))
.child(
IconButton::new(
"close-debug-session",
IconName::Close,
)
.icon_size(IconSize::Small)
.on_click({
let weak = weak.clone();
move |_, _, cx| {
weak.update(cx, |panel, cx| {
panel.close_session(weak_id, cx);
})
.ok();
}
}),
)
.into_any_element()
})
.unwrap_or_else(|_| div().into_any_element())
}
},
{
let weak = weak.clone();
move |window, cx| {
weak.update(cx, |panel, cx| {
panel.activate_session(session.clone(), window, cx);
})
.ok();
}
},
);
}
this
}),
)
}
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let active_session = self.active_session.clone();
@@ -529,34 +618,8 @@ impl DebugPanel {
},
)
.when_some(active_session.as_ref(), |this, session| {
let sessions = self.sessions.clone();
let weak = cx.weak_entity();
let label = session.read(cx).label(cx);
this.child(DropdownMenu::new(
"debugger-session-list",
label,
ContextMenu::build(window, cx, move |mut this, _, cx| {
for item in sessions {
let weak = weak.clone();
this = this.entry(
session.read(cx).label(cx),
None,
move |window, cx| {
weak.update(cx, |panel, cx| {
panel.activate_session(
item.clone(),
window,
cx,
);
})
.ok();
},
);
}
this
}),
))
.child(Divider::vertical())
let context_menu = self.sessions_drop_down_menu(session, window, cx);
this.child(context_menu).child(Divider::vertical())
})
.child(
IconButton::new("debug-new-session", IconName::Plus)

View File

@@ -31,6 +31,7 @@ pub(super) struct NewSessionModal {
debug_panel: WeakEntity<DebugPanel>,
mode: NewSessionMode,
stop_on_entry: ToggleState,
initialize_args: Option<serde_json::Value>,
debugger: Option<SharedString>,
last_selected_profile_name: Option<SharedString>,
}
@@ -82,17 +83,17 @@ impl NewSessionModal {
.map(Into::into)
.unwrap_or(ToggleState::Unselected),
last_selected_profile_name: None,
initialize_args: None,
}
}
fn debug_config(&self, cx: &App) -> Option<DebugTaskDefinition> {
let request = self.mode.debug_task(cx);
Some(DebugTaskDefinition {
adapter: self.debugger.clone()?.to_string(),
label: suggested_label(&request, self.debugger.as_deref()?),
request,
initialize_args: None,
initialize_args: self.initialize_args.clone(),
tcp_connection: None,
locator: None,
stop_on_entry: match self.stop_on_entry {
@@ -228,7 +229,7 @@ impl NewSessionModal {
weak.update(cx, |this, cx| {
this.last_selected_profile_name = Some(SharedString::from(&task.label));
this.debugger = Some(task.adapter.clone().into());
this.initialize_args = task.initialize_args.clone();
match &task.request {
DebugRequestType::Launch(launch_config) => {
this.mode = NewSessionMode::launch(

View File

@@ -7,7 +7,7 @@ use project::debugger::{dap_store::DapStore, session::Session};
use project::worktree_store::WorktreeStore;
use rpc::proto::{self, PeerId};
use running::RunningState;
use ui::prelude::*;
use ui::{Indicator, prelude::*};
use workspace::{
FollowableItem, ViewId, Workspace,
item::{self, Item},
@@ -81,7 +81,6 @@ impl DebugSession {
}
}
#[expect(unused)]
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
match &self.mode {
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
@@ -108,6 +107,33 @@ impl DebugSession {
.expect("Remote Debug Sessions are not implemented yet")
.label()
}
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
let label = self.label(cx);
let (icon, color) = match &self.mode {
DebugSessionState::Running(state) => {
if state.read(cx).session().read(cx).is_terminated() {
(Some(Indicator::dot().color(Color::Error)), Color::Error)
} else {
match state.read(cx).thread_status(cx).unwrap_or_default() {
project::debugger::session::ThreadStatus::Stopped => (
Some(Indicator::dot().color(Color::Conflict)),
Color::Conflict,
),
_ => (Some(Indicator::dot().color(Color::Success)), Color::Success),
}
}
}
};
h_flex()
.gap_2()
.when_some(icon, |this, indicator| this.child(indicator))
.justify_between()
.child(Label::new(label).color(color))
.into_any_element()
}
}
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}

View File

@@ -242,6 +242,7 @@ fn new_debugger_pane(
})));
pane.display_nav_history_buttons(None);
pane.set_custom_drop_handle(cx, custom_drop_handle);
pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
pane
});
@@ -343,12 +344,13 @@ impl RunningState {
SharedString::new_static("Modules"),
cx,
)),
true,
false,
false,
None,
window,
cx,
);
this.activate_item(0, false, false, window, cx);
});
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
rightmost_pane.update(cx, |this, cx| {
@@ -446,7 +448,7 @@ impl RunningState {
}
#[cfg(test)]
pub(crate) fn activate_variable_list(&self, window: &mut Window, cx: &mut App) {
pub(crate) fn activate_modules_list(&self, window: &mut Window, cx: &mut App) {
let (variable_list_position, pane) = self
.panes
.panes()
@@ -454,7 +456,7 @@ impl RunningState {
.find_map(|pane| {
pane.read(cx)
.items_of_type::<SubView>()
.position(|view| view.read(cx).tab_name == *"Variables")
.position(|view| view.read(cx).tab_name == *"Modules")
.map(|view| (view, pane))
})
.unwrap();

View File

@@ -8,7 +8,7 @@ use dap::OutputEvent;
use editor::{CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId};
use fuzzy::StringMatchCandidate;
use gpui::{Context, Entity, Render, Subscription, Task, TextStyle, WeakEntity};
use language::{Buffer, CodeLabel};
use language::{Buffer, CodeLabel, ToOffset};
use menu::Confirm;
use project::{
Completion,
@@ -392,25 +392,61 @@ impl ConsoleQueryBarCompletionProvider {
)
})
});
let snapshot = buffer.read(cx).text_snapshot();
cx.background_executor().spawn(async move {
let completions = completion_task.await?;
Ok(Some(
completion_task
.await?
.iter()
.map(|completion| project::Completion {
old_range: buffer_position..buffer_position, // TODO(debugger): change this
new_text: completion.text.clone().unwrap_or(completion.label.clone()),
label: CodeLabel {
filter_range: 0..completion.label.len(),
text: completion.label.clone(),
runs: Vec::new(),
},
icon_path: None,
documentation: None,
confirm: None,
source: project::CompletionSource::Custom,
insert_text_mode: None,
completions
.into_iter()
.map(|completion| {
let new_text = completion
.text
.as_ref()
.unwrap_or(&completion.label)
.to_owned();
let mut word_bytes_length = 0;
for chunk in snapshot
.reversed_chunks_in_range(language::Anchor::MIN..buffer_position)
{
let mut processed_bytes = 0;
if let Some(_) = chunk.chars().rfind(|c| {
let is_whitespace = c.is_whitespace();
if !is_whitespace {
processed_bytes += c.len_utf8();
}
is_whitespace
}) {
word_bytes_length += processed_bytes;
break;
} else {
word_bytes_length += chunk.len();
}
}
let buffer_offset = buffer_position.to_offset(&snapshot);
let start = buffer_offset - word_bytes_length;
let start = snapshot.anchor_before(start);
let old_range = start..buffer_position;
project::Completion {
old_range,
new_text,
label: CodeLabel {
filter_range: 0..completion.label.len(),
text: completion.label,
runs: Vec::new(),
},
icon_path: None,
documentation: None,
confirm: None,
source: project::CompletionSource::BufferWord {
word_range: buffer_position..language::Anchor::MAX,
resolved: false,
},
insert_text_mode: None,
}
})
.collect(),
))

View File

@@ -1,13 +1,14 @@
use anyhow::anyhow;
use gpui::{
AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, Subscription, WeakEntity, list,
AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, MouseButton, Stateful,
Subscription, WeakEntity, list,
};
use project::{
ProjectItem as _, ProjectPath,
debugger::session::{Session, SessionEvent},
};
use std::{path::Path, sync::Arc};
use ui::prelude::*;
use ui::{Scrollbar, ScrollbarState, prelude::*};
use util::maybe;
use workspace::Workspace;
@@ -17,6 +18,7 @@ pub struct ModuleList {
session: Entity<Session>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
scrollbar_state: ScrollbarState,
_subscription: Subscription,
}
@@ -50,6 +52,7 @@ impl ModuleList {
});
Self {
scrollbar_state: ScrollbarState::new(list.clone()),
list,
session,
workspace,
@@ -153,6 +156,38 @@ impl ModuleList {
self.session
.update(cx, |session, cx| session.modules(cx).to_vec())
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("module-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
impl Focusable for ModuleList {
@@ -177,5 +212,6 @@ impl Render for ModuleList {
.size_full()
.p_1()
.child(list(self.list.clone()).size_full())
.child(self.render_vertical_scrollbar(cx))
}
}

View File

@@ -4,14 +4,14 @@ use std::sync::Arc;
use anyhow::{Result, anyhow};
use dap::StackFrameId;
use gpui::{
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, Subscription, Task,
WeakEntity, list,
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, MouseButton, Stateful,
Subscription, Task, WeakEntity, list,
};
use language::PointUtf16;
use project::debugger::session::{Session, SessionEvent, StackFrame};
use project::{ProjectItem, ProjectPath};
use ui::{Tooltip, prelude::*};
use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
use util::ResultExt;
use workspace::Workspace;
@@ -32,6 +32,7 @@ pub struct StackFrameList {
entries: Vec<StackFrameEntry>,
workspace: WeakEntity<Workspace>,
current_stack_frame_id: Option<StackFrameId>,
scrollbar_state: ScrollbarState,
}
#[allow(clippy::large_enum_variant)]
@@ -75,6 +76,7 @@ impl StackFrameList {
});
Self {
scrollbar_state: ScrollbarState::new(list.clone()),
list,
session,
workspace,
@@ -493,6 +495,39 @@ impl StackFrameList {
}
}
}
fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
div()
.occlude()
.id("stack-frame-list-vertical-scrollbar")
.on_mouse_move(cx.listener(|_, _, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, _, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
impl Render for StackFrameList {
@@ -507,6 +542,7 @@ impl Render for StackFrameList {
.size_full()
.p_1()
.child(list(self.list.clone()).size_full())
.child(self.render_vertical_scrollbar(cx))
}
}

View File

@@ -138,7 +138,8 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
.clone()
});
running_state.update(cx, |_, cx| {
running_state.update_in(cx, |this, window, cx| {
this.activate_modules_list(window, cx);
cx.refresh_windows();
});

View File

@@ -207,9 +207,6 @@ async fn test_basic_fetch_initial_scope_and_variables(
.expect("Session should be running by this point")
.clone()
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
@@ -481,9 +478,6 @@ async fn test_fetch_variables_for_multiple_scopes(
.expect("Session should be running by this point")
.clone()
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
@@ -800,10 +794,6 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
variable_list.update(cx, |_, cx| cx.focus_self(window));
running
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
cx.dispatch_action(SelectFirst);
cx.dispatch_action(SelectFirst);
cx.run_until_parked();
@@ -1572,21 +1562,6 @@ async fn test_variable_list_only_sends_requests_when_rendering(
cx.run_until_parked();
// We shouldn't make any variable requests unless we're rendering the variable list
running_state.update_in(cx, |running_state, window, cx| {
let variable_list = running_state.variable_list().read(cx);
let empty: Vec<dap::Variable> = vec![];
assert_eq!(empty, variable_list.variables());
assert!(!made_scopes_request.load(Ordering::SeqCst));
cx.focus_self(window);
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =
running_state.stack_frame_list().update(cx, |list, _| {
@@ -1898,10 +1873,6 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
.expect("Session should be running by this point")
.clone()
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
let (stack_frame_list, stack_frame_id) =

View File

@@ -1384,7 +1384,9 @@ impl Editor {
window,
|editor, _, event, window, cx| match event {
BreakpointStoreEvent::ActiveDebugLineChanged => {
editor.go_to_active_debug_line(window, cx);
if editor.go_to_active_debug_line(window, cx) {
cx.stop_propagation();
}
}
_ => {}
},
@@ -1607,17 +1609,25 @@ impl Editor {
}
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
this._subscriptions.extend(project_subscriptions);
this._subscriptions
.push(cx.subscribe_self(|editor, e: &EditorEvent, cx| {
this._subscriptions.push(cx.subscribe_in(
&cx.entity(),
window,
|editor, _, e: &EditorEvent, window, cx| {
if let EditorEvent::SelectionsChanged { local } = e {
if *local {
let new_anchor = editor.scroll_manager.anchor();
let snapshot = editor.snapshot(window, cx);
editor.update_restoration_data(cx, move |data| {
data.scroll_anchor = new_anchor;
data.scroll_position = (
new_anchor.top_row(&snapshot.buffer_snapshot),
new_anchor.offset,
);
});
}
}
}));
},
));
this.end_selection(window, cx);
this.scroll_manager.show_scrollbars(window, cx);
@@ -2367,22 +2377,30 @@ impl Editor {
if selections.len() == 1 {
cx.emit(SearchEvent::ActiveMatchChanged)
}
if local && self.is_singleton(cx) {
let inmemory_selections = selections.iter().map(|s| s.range()).collect();
self.update_restoration_data(cx, |data| {
data.selections = inmemory_selections;
});
if local {
if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
let inmemory_selections = selections
.iter()
.map(|s| {
text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
})
.collect();
self.update_restoration_data(cx, |data| {
data.selections = inmemory_selections;
});
if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
{
if let Some(workspace_id) =
self.workspace.as_ref().and_then(|workspace| workspace.1)
if WorkspaceSettings::get(None, cx).restore_on_startup
!= RestoreOnStartupBehavior::None
{
let snapshot = self.buffer().read(cx).snapshot(cx);
let selections = selections.clone();
let background_executor = cx.background_executor().clone();
let editor_id = cx.entity().entity_id().as_u64() as ItemId;
self.serialize_selections = cx.background_spawn(async move {
if let Some(workspace_id) =
self.workspace.as_ref().and_then(|workspace| workspace.1)
{
let snapshot = self.buffer().read(cx).snapshot(cx);
let selections = selections.clone();
let background_executor = cx.background_executor().clone();
let editor_id = cx.entity().entity_id().as_u64() as ItemId;
self.serialize_selections = cx.background_spawn(async move {
background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
let db_selections = selections
.iter()
@@ -2399,6 +2417,7 @@ impl Editor {
.with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
.log_err();
});
}
}
}
}
@@ -2407,18 +2426,27 @@ impl Editor {
}
fn folds_did_change(&mut self, cx: &mut Context<Self>) {
if !self.is_singleton(cx)
|| WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
{
use text::ToOffset as _;
use text::ToPoint as _;
if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
return;
}
let snapshot = self.buffer().read(cx).snapshot(cx);
let Some(singleton) = self.buffer().read(cx).as_singleton() else {
return;
};
let snapshot = singleton.read(cx).snapshot();
let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
display_map
.snapshot(cx)
.folds_in_range(0..snapshot.len())
.map(|fold| fold.range.deref().clone())
let display_snapshot = display_map.snapshot(cx);
display_snapshot
.folds_in_range(0..display_snapshot.buffer_snapshot.len())
.map(|fold| {
fold.range.start.text_anchor.to_point(&snapshot)
..fold.range.end.text_anchor.to_point(&snapshot)
})
.collect()
});
self.update_restoration_data(cx, |data| {
@@ -2436,8 +2464,8 @@ impl Editor {
.folds_in_range(0..snapshot.len())
.map(|fold| {
(
fold.range.start.to_offset(&snapshot),
fold.range.end.to_offset(&snapshot),
fold.range.start.text_anchor.to_offset(&snapshot),
fold.range.end.text_anchor.to_offset(&snapshot),
)
})
.collect()
@@ -15884,8 +15912,9 @@ impl Editor {
}
}
pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let _ = maybe!({
// Returns true if the editor handled a go-to-line request
pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
maybe!({
let breakpoint_store = self.breakpoint_store.as_ref()?;
let Some((_, _, active_position)) =
@@ -15903,6 +15932,7 @@ impl Editor {
.read(cx)
.snapshot();
let mut handled = false;
for (id, ExcerptRange { context, .. }) in self
.buffer
.read(cx)
@@ -15916,6 +15946,7 @@ impl Editor {
let snapshot = self.buffer.read(cx).snapshot(cx);
let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
handled = true;
self.clear_row_highlights::<DebugCurrentRowHighlight>();
self.go_to_line::<DebugCurrentRowHighlight>(
multibuffer_anchor,
@@ -15926,9 +15957,9 @@ impl Editor {
cx.notify();
}
Some(())
});
handled.then_some(())
})
.is_some()
}
pub fn copy_file_name_without_extension(

View File

@@ -8404,7 +8404,7 @@ enum CursorPopoverType {
}
pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
(delta.pow(1.5) / 100.0).into()
(delta.pow(1.2) / 100.0).min(px(3.0)).into()
}
fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {

View File

@@ -6,7 +6,6 @@ use crate::{
scroll::ScrollAnchor,
};
use anyhow::{Context as _, Result, anyhow};
use clock::Global;
use collections::{HashMap, HashSet};
use file_icons::FileIcons;
use futures::future::try_join_all;
@@ -16,12 +15,12 @@ use gpui::{
ParentElement, Pixels, SharedString, Styled, Task, WeakEntity, Window, point,
};
use language::{
Bias, Buffer, CharKind, DiskState, Point, SelectionGoal,
Bias, Buffer, BufferRow, CharKind, DiskState, LocalFile, Point, SelectionGoal,
proto::serialize_anchor as serialize_text_anchor,
};
use lsp::DiagnosticSeverity;
use project::{
Project, ProjectEntryId, ProjectItem as _, ProjectPath, lsp_store::FormatTrigger,
Project, ProjectItem as _, ProjectPath, lsp_store::FormatTrigger,
project_settings::ProjectSettings, search::SearchQuery,
};
use rpc::proto::{self, PeerId, update_view};
@@ -30,13 +29,12 @@ use std::{
any::TypeId,
borrow::Cow,
cmp::{self, Ordering},
collections::hash_map,
iter,
ops::Range,
path::Path,
path::{Path, PathBuf},
sync::Arc,
};
use text::{BufferId, Selection};
use text::{BufferId, BufferSnapshot, Selection};
use theme::{Theme, ThemeSettings};
use ui::{IconDecorationKind, prelude::*};
use util::{ResultExt, TryFutureExt, paths::PathExt};
@@ -1243,26 +1241,14 @@ impl SerializableItem for Editor {
#[derive(Debug, Default)]
struct EditorRestorationData {
entries: HashMap<ProjectEntryId, RestorationData>,
entries: HashMap<PathBuf, RestorationData>,
}
#[derive(Debug)]
#[derive(Default, Debug)]
pub struct RestorationData {
pub scroll_anchor: ScrollAnchor,
pub folds: Vec<Range<Anchor>>,
pub selections: Vec<Range<Anchor>>,
pub buffer_version: Global,
}
impl Default for RestorationData {
fn default() -> Self {
Self {
scroll_anchor: ScrollAnchor::new(),
folds: Vec::new(),
selections: Vec::new(),
buffer_version: Global::default(),
}
}
pub scroll_position: (BufferRow, gpui::Point<f32>),
pub folds: Vec<Range<Point>>,
pub selections: Vec<Range<Point>>,
}
impl ProjectItem for Editor {
@@ -1280,21 +1266,37 @@ impl ProjectItem for Editor {
cx: &mut Context<Self>,
) -> Self {
let mut editor = Self::for_buffer(buffer.clone(), Some(project), window, cx);
if WorkspaceSettings::get(None, cx).restore_on_file_reopen {
if let Some(restoration_data) = Self::project_item_kind()
.and_then(|kind| pane.project_item_restoration_data.get(&kind))
.and_then(|data| data.downcast_ref::<EditorRestorationData>())
.and_then(|data| data.entries.get(&buffer.read(cx).entry_id(cx)?))
.filter(|data| !buffer.read(cx).version.changed_since(&data.buffer_version))
{
editor.fold_ranges(restoration_data.folds.clone(), false, window, cx);
if !restoration_data.selections.is_empty() {
editor.change_selections(None, window, cx, |s| {
s.select_ranges(restoration_data.selections.clone());
});
if let Some((excerpt_id, buffer_id, snapshot)) =
editor.buffer().read(cx).snapshot(cx).as_singleton()
{
if WorkspaceSettings::get(None, cx).restore_on_file_reopen {
if let Some(restoration_data) = Self::project_item_kind()
.and_then(|kind| pane.project_item_restoration_data.get(&kind))
.and_then(|data| data.downcast_ref::<EditorRestorationData>())
.and_then(|data| {
let file = project::File::from_dyn(buffer.read(cx).file())?;
data.entries.get(&file.abs_path(cx))
})
{
editor.fold_ranges(
clip_ranges(&restoration_data.folds, &snapshot),
false,
window,
cx,
);
if !restoration_data.selections.is_empty() {
editor.change_selections(None, window, cx, |s| {
s.select_ranges(clip_ranges(&restoration_data.selections, &snapshot));
});
}
let (top_row, offset) = restoration_data.scroll_position;
let anchor = Anchor::in_buffer(
*excerpt_id,
buffer_id,
snapshot.anchor_before(Point::new(top_row, 0)),
);
editor.set_scroll_anchor(ScrollAnchor { anchor, offset }, window, cx);
}
editor.set_scroll_anchor(restoration_data.scroll_anchor, window, cx);
}
}
@@ -1302,6 +1304,19 @@ impl ProjectItem for Editor {
}
}
fn clip_ranges<'a>(
original: impl IntoIterator<Item = &'a Range<Point>> + 'a,
snapshot: &'a BufferSnapshot,
) -> Vec<Range<Point>> {
original
.into_iter()
.map(|range| {
snapshot.clip_point(range.start, Bias::Left)
..snapshot.clip_point(range.end, Bias::Right)
})
.collect()
}
impl EventEmitter<SearchEvent> for Editor {}
impl Editor {
@@ -1320,8 +1335,7 @@ impl Editor {
let kind = Editor::project_item_kind()?;
let pane = editor.workspace()?.read(cx).pane_for(&cx.entity())?;
let buffer = editor.buffer().read(cx).as_singleton()?;
let entry_id = buffer.read(cx).entry_id(cx)?;
let buffer_version = buffer.read(cx).version();
let file_abs_path = project::File::from_dyn(buffer.read(cx).file())?.abs_path(cx);
pane.update(cx, |pane, _| {
let data = pane
.project_item_restoration_data
@@ -1336,17 +1350,8 @@ impl Editor {
}
};
let data = match data.entries.entry(entry_id) {
hash_map::Entry::Occupied(o) => {
if buffer_version.changed_since(&o.get().buffer_version) {
return None;
}
o.into_mut()
}
hash_map::Entry::Vacant(v) => v.insert(RestorationData::default()),
};
let data = data.entries.entry(file_abs_path).or_default();
write(data);
data.buffer_version = buffer_version;
Some(())
})
});

View File

@@ -606,7 +606,7 @@ impl GitRepository for RealGitRepository {
};
let content = repo.find_blob(oid)?.content().to_owned();
Ok(Some(String::from_utf8(content)?))
Ok(String::from_utf8(content).ok())
}
match logic(&repo.lock(), &path) {
@@ -629,8 +629,7 @@ impl GitRepository for RealGitRepository {
return None;
}
let content = repo.find_blob(entry.id()).log_err()?.content().to_owned();
let content = String::from_utf8(content).log_err()?;
Some(content)
String::from_utf8(content).ok()
})
.boxed()
}

View File

@@ -244,10 +244,6 @@ impl language::File for GitBlob {
self.worktree_id
}
fn as_any(&self) -> &dyn Any {
self
}
fn to_proto(&self, _cx: &App) -> language::proto::File {
unimplemented!()
}
@@ -282,10 +278,6 @@ impl language::File for CommitMetadataFile {
self.worktree_id
}
fn as_any(&self) -> &dyn Any {
self
}
fn to_proto(&self, _: &App) -> language::proto::File {
unimplemented!()
}

View File

@@ -393,6 +393,8 @@ pub enum Model {
Gemini20FlashLite,
#[serde(rename = "gemini-2.5-pro-exp-03-25")]
Gemini25ProExp0325,
#[serde(rename = "gemini-2.5-pro-preview-03-25")]
Gemini25ProPreview0325,
#[serde(rename = "custom")]
Custom {
name: String,
@@ -412,6 +414,7 @@ impl Model {
Model::Gemini20FlashThinking => "gemini-2.0-flash-thinking-exp",
Model::Gemini20FlashLite => "gemini-2.0-flash-lite-preview",
Model::Gemini25ProExp0325 => "gemini-2.5-pro-exp-03-25",
Model::Gemini25ProPreview0325 => "gemini-2.5-pro-preview-03-25",
Model::Custom { name, .. } => name,
}
}
@@ -425,6 +428,7 @@ impl Model {
Model::Gemini20FlashThinking => "Gemini 2.0 Flash Thinking",
Model::Gemini20FlashLite => "Gemini 2.0 Flash Lite",
Model::Gemini25ProExp0325 => "Gemini 2.5 Pro Exp",
Model::Gemini25ProPreview0325 => "Gemini 2.5 Pro Preview",
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name),
@@ -440,6 +444,7 @@ impl Model {
Model::Gemini20FlashThinking => 1_000_000,
Model::Gemini20FlashLite => 1_000_000,
Model::Gemini25ProExp0325 => 1_000_000,
Model::Gemini25ProPreview0325 => 1_000_000,
Model::Custom { max_tokens, .. } => *max_tokens,
}
}

View File

@@ -89,7 +89,7 @@ etagere = "0.2"
futures.workspace = true
gpui_macros.workspace = true
http_client = { optional = true, workspace = true }
image = "0.25.1"
image.workspace = true
inventory.workspace = true
itertools.workspace = true
log.workspace = true

View File

@@ -42,13 +42,10 @@ use std::{
/// }
/// register_action!(Paste);
/// ```
pub trait Action: 'static + Send {
pub trait Action: Any + Send {
/// Clone the action into a new box
fn boxed_clone(&self) -> Box<dyn Action>;
/// Cast the action to the any type
fn as_any(&self) -> &dyn Any;
/// Do a partial equality check on this action and the other
fn partial_eq(&self, action: &dyn Action) -> bool;
@@ -94,9 +91,9 @@ impl std::fmt::Debug for dyn Action {
}
impl dyn Action {
/// Get the type id of this action
pub fn type_id(&self) -> TypeId {
self.as_any().type_id()
/// Type-erase Action type.
pub fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
@@ -557,9 +554,6 @@ macro_rules! __impl_action {
::std::boxed::Box::new(self.clone())
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
$($items)*
}

View File

@@ -597,10 +597,6 @@ mod tests {
Box::new(TestAction)
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
where
Self: Sized,

View File

@@ -513,9 +513,8 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
let point = input.position.xy - quad.bounds.origin;
let center_to_point = point - half_size;
// Signed distance field threshold for inclusion of pixels. Use of 0.5
// instead of 1.0 causes the width of rounded borders to appear more
// consistent with straight borders.
// Signed distance field threshold for inclusion of pixels. 0.5 is the
// minimum distance between the center of the pixel and the edge.
let antialias_threshold = 0.5;
// Radius of the nearest corner
@@ -612,24 +611,29 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
// Dashed border logic when border_style == 1
if (quad.border_style == 1) {
// Position in "dash space", where each dash period has length 1
// Position along the perimeter in "dash space", where each dash
// period has length 1
var t = 0.0;
// Total number of dash periods, so that the dash spacing can be
// adjusted to evenly divide it
var max_t = 0.0;
// Since border width affects the dash size, the density of dashes
// varies, and this is indicated by dash_velocity. It has units
// (dash period / pixel). So a dash velocity of (1 / 10) is 1 dash
// every 10 pixels.
var dash_velocity = 0.0;
// Border width is proportional to dash size. This is the behavior
// used by browsers, but also avoids dashes from different segments
// overlapping when dash size is smaller than the border width.
//
// Dash pattern: (2 * border width) dash, (1 * border width) gap
let dash_length_per_width = 2.0;
let dash_gap_per_width = 1.0;
let dash_period_per_width = dash_length_per_width + dash_gap_per_width;
// Since the dash size is determined by border width, the density of
// dashes varies. Multiplying a pixel distance by this returns a
// position in dash space - it has units (dash period / pixels). So
// a dash velocity of (1 / 10) is 1 dash every 10 pixels.
var dash_velocity = 0.0;
// Dividing this by the border width gives the dash velocity
let dv_numerator = 1.0 / dash_period_per_width;
@@ -645,8 +649,8 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
t = select(point.y, point.x, is_horizontal) * dash_velocity;
max_t = select(size.y, size.x, is_horizontal) * dash_velocity;
} else {
// When corners are rounded, the dashes are laid out around the
// whole perimeter.
// When corners are rounded, the dashes are laid out clockwise
// around the whole perimeter.
let r_tr = quad.corner_radii.top_right;
let r_br = quad.corner_radii.bottom_right;
@@ -694,23 +698,34 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
if (is_near_rounded_corner) {
let radians = atan2(corner_center_to_point.y,
corner_center_to_point.x);
let corner_t = radians * corner_radius;
let corner_t = radians * corner_radius * dash_velocity;
if (center_to_point.x >= 0.0) {
if (center_to_point.y < 0.0) {
dash_velocity = corner_dash_velocity_tr;
t = upto_r - corner_t * dash_velocity;
// Subtracted because radians is pi/2 to 0 when
// going clockwise around the top right corner,
// since the y axis has been flipped
t = upto_r - corner_t;
} else {
dash_velocity = corner_dash_velocity_br;
t = upto_br + corner_t * dash_velocity;
// Added because radians is 0 to pi/2 when going
// clockwise around the bottom-right corner
t = upto_br + corner_t;
}
} else {
if (center_to_point.y >= 0.0) {
dash_velocity = corner_dash_velocity_bl;
t = upto_l - corner_t * dash_velocity;
// Subtracted because radians is pi/2 to 0 when
// going clockwise around the bottom-left corner,
// since the x axis has been flipped
t = upto_l - corner_t;
} else {
dash_velocity = corner_dash_velocity_tl;
t = upto_tl + corner_t * dash_velocity;
// Added because radians is 0 to pi/2 when going
// clockwise around the top-left corner, since both
// axis were flipped
t = upto_tl + corner_t;
}
}
} else {

View File

@@ -106,7 +106,17 @@ impl PlatformDispatcher for LinuxDispatcher {
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
self.main_sender.send(runnable).ok();
self.main_sender.send(runnable).unwrap_or_else(|runnable| {
// NOTE: Runnable may wrap a Future that is !Send.
//
// This is usually safe because we only poll it on the main thread.
// However if the send fails, we know that:
// 1. main_receiver has been dropped (which implies the app is shutting down)
// 2. we are on a background thread.
// It is not safe to drop something !Send on the wrong thread, and
// the app will exit soon anyway, so we must forget the runnable.
std::mem::forget(runnable);
});
}
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {

View File

@@ -121,7 +121,8 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
float2 point = input.position.xy - float2(quad.bounds.origin.x, quad.bounds.origin.y);
float2 center_to_point = point - half_size;
// Signed distance field threshold for inclusion of pixels
// Signed distance field threshold for inclusion of pixels. 0.5 is the
// minimum distance between the center of the pixel and the edge.
const float antialias_threshold = 0.5;
// Radius of the nearest corner
@@ -211,24 +212,29 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
// Dashed border logic when border_style == 1
if (quad.border_style == 1) {
// Position in "dash space", where each dash period has length 1
// Position along the perimeter in "dash space", where each dash
// period has length 1
float t = 0.0;
// Total number of dash periods, so that the dash spacing can be
// adjusted to evenly divide it
float max_t = 0.0;
// Since border width affects the dash size, the density of dashes
// varies, and this is indicated by dash_velocity. It has units
// (dash period / pixel). So a dash velocity of (1 / 10) is 1 dash
// every 10 pixels.
float dash_velocity = 0.0;
// Border width is proportional to dash size. This is the behavior
// used by browsers, but also avoids dashes from different segments
// overlapping when dash size is smaller than the border width.
//
// Dash pattern: (2 * border width) dash, (1 * border width) gap
const float dash_length_per_width = 2.0;
const float dash_gap_per_width = 1.0;
const float dash_period_per_width = dash_length_per_width + dash_gap_per_width;
// Since the dash size is determined by border width, the density of
// dashes varies. Multiplying a pixel distance by this returns a
// position in dash space - it has units (dash period / pixels). So
// a dash velocity of (1 / 10) is 1 dash every 10 pixels.
float dash_velocity = 0.0;
// Dividing this by the border width gives the dash velocity
const float dv_numerator = 1.0 / dash_period_per_width;
@@ -244,8 +250,8 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
max_t = is_horizontal ? size.x : size.y;
max_t *= dash_velocity;
} else {
// When corners are rounded, the dashes are laid out around the
// whole perimeter.
// When corners are rounded, the dashes are laid out clockwise
// around the whole perimeter.
float r_tr = quad.corner_radii.top_right;
float r_br = quad.corner_radii.bottom_right;
@@ -292,23 +298,34 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
if (is_near_rounded_corner) {
float radians = atan2(corner_center_to_point.y, corner_center_to_point.x);
float corner_t = radians * corner_radius;
float corner_t = radians * corner_radius * dash_velocity;
if (center_to_point.x >= 0.0) {
if (center_to_point.y < 0.0) {
dash_velocity = corner_dash_velocity_tr;
t = upto_r - corner_t * dash_velocity;
// Subtracted because radians is pi/2 to 0 when
// going clockwise around the top right corner,
// since the y axis has been flipped
t = upto_r - corner_t;
} else {
dash_velocity = corner_dash_velocity_br;
t = upto_br + corner_t * dash_velocity;
// Added because radians is 0 to pi/2 when going
// clockwise around the bottom-right corner
t = upto_br + corner_t;
}
} else {
if (center_to_point.y >= 0.0) {
dash_velocity = corner_dash_velocity_bl;
t = upto_l - corner_t * dash_velocity;
// Subtracted because radians is pi/1 to 0 when
// going clockwise around the bottom-left corner,
// since the x axis has been flipped
t = upto_l - corner_t;
} else {
dash_velocity = corner_dash_velocity_tl;
t = upto_tl + corner_t * dash_velocity;
// Added because radians is 0 to pi/2 when going
// clockwise around the top-left corner, since both
// axis were flipped
t = upto_tl + corner_t;
}
}
} else {

View File

@@ -3,7 +3,6 @@ use std::{
time::Duration,
};
use anyhow::Context as _;
use async_task::Runnable;
use flume::Sender;
use parking::Parker;
@@ -95,14 +94,8 @@ impl PlatformDispatcher for WindowsDispatcher {
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
if self
.main_sender
.send(runnable)
.context("Dispatch on main thread failed")
.log_err()
.is_some()
{
unsafe {
match self.main_sender.send(runnable) {
Ok(_) => unsafe {
PostThreadMessageW(
self.main_thread_id_win32,
WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
@@ -110,6 +103,17 @@ impl PlatformDispatcher for WindowsDispatcher {
LPARAM(0),
)
.log_err();
},
Err(runnable) => {
// NOTE: Runnable may wrap a Future that is !Send.
//
// This is usually safe because we only poll it on the main thread.
// However if the send fails, we know that:
// 1. main_receiver has been dropped (which implies the app is shutting down)
// 2. we are on a background thread.
// It is not safe to drop something !Send on the wrong thread, and
// the app will exit soon anyway, so we must forget the runnable.
std::mem::forget(runnable);
}
}
}

View File

@@ -22,10 +22,6 @@ fn test_action_macros() {
unimplemented!()
}
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!()
}
fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {
unimplemented!()
}

View File

@@ -306,7 +306,7 @@ pub enum BufferEvent {
}
/// The file associated with a buffer.
pub trait File: Send + Sync {
pub trait File: Send + Sync + Any {
/// Returns the [`LocalFile`] associated with this file, if the
/// file is local.
fn as_local(&self) -> Option<&dyn LocalFile>;
@@ -336,9 +336,6 @@ pub trait File: Send + Sync {
/// This is needed for looking up project-specific settings.
fn worktree_id(&self, cx: &App) -> WorktreeId;
/// Converts this file into an [`Any`] trait object.
fn as_any(&self) -> &dyn Any;
/// Converts this file into a protobuf message.
fn to_proto(&self, cx: &App) -> rpc::proto::File;
@@ -4610,10 +4607,6 @@ impl File for TestFile {
WorktreeId::from_usize(0)
}
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!()
}
fn to_proto(&self, _: &App) -> rpc::proto::File {
unimplemented!()
}

View File

@@ -4,6 +4,7 @@ mod registry;
mod request;
mod role;
mod telemetry;
mod tool_output;
#[cfg(any(test, feature = "test-support"))]
pub mod fake_provider;
@@ -26,6 +27,7 @@ use util::serde::is_default;
pub use crate::model::*;
pub use crate::rate_limiter::*;
pub use crate::tool_output::{ToolOutput, StringToolOutput};
pub use crate::registry::*;
pub use crate::request::*;
pub use crate::role::*;

View File

@@ -107,6 +107,7 @@ impl CloudModel {
| google_ai::Model::Gemini20FlashThinking
| google_ai::Model::Gemini20FlashLite
| google_ai::Model::Gemini25ProExp0325
| google_ai::Model::Gemini25ProPreview0325
| google_ai::Model::Custom { .. } => {
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
}

View File

@@ -2,6 +2,7 @@ use std::io::{Cursor, Write};
use std::sync::Arc;
use crate::role::Role;
use crate::tool_output::ToolOutput;
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
use base64::write::EncoderWriter;
use gpui::{
@@ -164,12 +165,13 @@ impl LanguageModelImage {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct LanguageModelToolResult {
pub tool_use_id: LanguageModelToolUseId,
pub tool_name: Arc<str>,
pub is_error: bool,
pub content: Arc<str>,
pub tool_output: Option<Arc<ToolOutput>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]

View File

@@ -0,0 +1,33 @@
use gpui::{AnyElement, App, SharedString, Window};
use serde::{Deserialize, Serialize};
/// An enum that represents different types of tool outputs that can be provided to the language model
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ToolOutput {
/// A simple string output
String {
string: SharedString,
rendered: Entity<Markdown>,
},
// Add other tool output types here as variants
}
impl ToolOutput {
/// Returns a string that will be given to the model
/// as the tool output.
pub fn response_for_model(&self) -> SharedString {
match self {
ToolOutput::String(output) => output.0.clone(),
// Handle other variants here
}
}
/// Returns a custom UI element to render the tool's output.
/// Returns None by default to indicate that rendering has not yet been
/// implemented for this tool, and the caller should do some default rendering.
pub fn render(&self, _window: &mut Window, _cx: &App) -> Option<AnyElement> {
match self {
ToolOutput::String { string, rendered } => todo!(),
}
}
}

View File

@@ -504,10 +504,14 @@ impl CloudLanguageModel {
let mut retry_delay = Duration::from_secs(1);
loop {
let request_builder = http_client::Request::builder();
let request_builder = http_client::Request::builder().method(Method::POST);
let request_builder = if let Ok(completions_url) = std::env::var("ZED_COMPLETIONS_URL")
{
request_builder.uri(completions_url)
} else {
request_builder.uri(http_client.build_zed_llm_url("/completion", &[])?.as_ref())
};
let request = request_builder
.method(Method::POST)
.uri(http_client.build_zed_llm_url("/completion", &[])?.as_ref())
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {token}"))
.body(serde_json::to_string(&body)?.into())?;

View File

@@ -991,6 +991,7 @@ impl LspAdapter for PyLspAdapter {
util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server")
.arg("-U")
.output()
.await?
.status
@@ -1001,6 +1002,7 @@ impl LspAdapter for PyLspAdapter {
util::command::new_smol_command(pip_path.as_path())
.arg("install")
.arg("python-lsp-server[all]")
.arg("-U")
.output()
.await?
.status
@@ -1011,6 +1013,7 @@ impl LspAdapter for PyLspAdapter {
util::command::new_smol_command(pip_path)
.arg("install")
.arg("pylsp-mypy")
.arg("-U")
.output()
.await?
.status

View File

@@ -255,23 +255,6 @@ impl RemoteTrackPublication {
}
}
impl RemoteTrack {
pub fn set_enabled(&self, enabled: bool, cx: &App) {
let this = self.clone();
Tokio::spawn(cx, async move {
match this {
RemoteTrack::Audio(remote_audio_track) => {
remote_audio_track.0.rtc_track().set_enabled(enabled)
}
RemoteTrack::Video(remote_video_track) => {
remote_video_track.0.rtc_track().set_enabled(enabled)
}
}
})
.detach();
}
}
impl Participant {
pub fn identity(&self) -> ParticipantIdentity {
match self {

View File

@@ -89,12 +89,3 @@ impl RemoteTrackPublication {
}
}
}
impl RemoteTrack {
pub fn set_enabled(&self, enabled: bool, _cx: &App) {
match self {
RemoteTrack::Audio(remote_audio_track) => remote_audio_track.set_enabled(enabled),
RemoteTrack::Video(remote_video_track) => remote_video_track.set_enabled(enabled),
}
}
}

View File

@@ -43,23 +43,6 @@ impl RemoteAudioTrack {
false
}
}
pub fn set_enabled(&self, enabled: bool) {
let Some(room) = self.room.upgrade() else {
return;
};
if enabled {
room.0
.lock()
.paused_audio_tracks
.remove(&self.server_track.sid);
} else {
room.0
.lock()
.paused_audio_tracks
.insert(self.server_track.sid.clone());
}
}
}
impl RemoteVideoTrack {
@@ -70,6 +53,4 @@ impl RemoteVideoTrack {
pub fn publisher_id(&self) -> ParticipantIdentity {
self.server_track.publisher_id.clone()
}
pub(crate) fn set_enabled(&self, _enabled: bool) {}
}

View File

@@ -861,15 +861,7 @@ impl Element for MarkdownElement {
self.markdown.clone(),
cx,
);
el.child(
div()
.group("markdown-code-block")
.absolute()
.top_1()
.right_1()
.w_5()
.child(codeblock),
)
el.child(div().absolute().top_1().right_1().w_5().child(codeblock))
});
}

View File

@@ -219,7 +219,7 @@ pub enum MarkdownEvent {
Start(MarkdownTag),
/// End of a tagged element.
End(MarkdownTagEnd),
/// Text that uses the associated range from the mardown source.
/// Text that uses the associated range from the markdown source.
Text,
/// Text that differs from the markdown source - typically due to substitution of HTML entities
/// and smart punctuation.

View File

@@ -1,7 +1,7 @@
use super::{
breakpoint_store::BreakpointStore,
locator_store::LocatorStore,
session::{self, Session},
session::{self, Session, SessionStateEvent},
};
use crate::{ProjectEnvironment, debugger, worktree_store::WorktreeStore};
use anyhow::{Result, anyhow};
@@ -869,6 +869,15 @@ fn create_new_session(
}
this.update(cx, |_, cx| {
cx.subscribe(
&session,
move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
SessionStateEvent::Shutdown => {
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
}
},
)
.detach();
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
})?;

View File

@@ -746,8 +746,7 @@ pub struct Session {
_background_tasks: Vec<Task<()>>,
}
trait CacheableCommand: 'static + Send + Sync {
fn as_any(&self) -> &dyn Any;
trait CacheableCommand: Any + Send + Sync {
fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool;
fn dyn_hash(&self, hasher: &mut dyn Hasher);
fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
@@ -757,12 +756,8 @@ impl<T> CacheableCommand for T
where
T: DapCommand + PartialEq + Eq + Hash,
{
fn as_any(&self) -> &dyn Any {
self
}
fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool {
rhs.as_any()
(rhs as &dyn Any)
.downcast_ref::<Self>()
.map_or(false, |rhs| self == rhs)
}
@@ -795,7 +790,7 @@ impl Eq for RequestSlot {}
impl Hash for RequestSlot {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.dyn_hash(state);
self.0.as_any().type_id().hash(state)
(&*self.0 as &dyn Any).type_id().hash(state)
}
}
@@ -832,7 +827,12 @@ pub enum SessionEvent {
Threads,
}
pub(crate) enum SessionStateEvent {
Shutdown,
}
impl EventEmitter<SessionEvent> for Session {}
impl EventEmitter<SessionStateEvent> for Session {}
// local session will send breakpoint updates to DAP for all new breakpoints
// remote side will only send breakpoint updates when it is a breakpoint created by that peer
@@ -1340,7 +1340,7 @@ impl Session {
fn invalidate_state(&mut self, key: &RequestSlot) {
self.requests
.entry(key.0.as_any().type_id())
.entry((&*key.0 as &dyn Any).type_id())
.and_modify(|request_map| {
request_map.remove(&key);
});
@@ -1553,6 +1553,8 @@ impl Session {
)
};
cx.emit(SessionStateEvent::Shutdown);
cx.background_spawn(async move {
let _ = task.await;
})

View File

@@ -2635,7 +2635,8 @@ impl LocalLspStore {
.into_iter()
.map(|edit| (range_from_lsp(edit.range), edit.new_text))
.collect::<Vec<_>>();
lsp_edits.sort_by_key(|(range, _)| range.start);
lsp_edits.sort_by_key(|(range, _)| (range.start, range.end));
let mut lsp_edits = lsp_edits.into_iter().peekable();
let mut edits = Vec::new();

View File

@@ -2663,6 +2663,62 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAp
});
}
#[gpui::test]
async fn test_edits_from_lsp_with_replacement_followed_by_adjacent_insertion(
cx: &mut gpui::TestAppContext,
) {
init_test(cx);
let text = "Path()";
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({
"a.rs": text
}),
)
.await;
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/dir/a.rs"), cx)
})
.await
.unwrap();
// Simulate the language server sending us a pair of edits at the same location,
// with an insertion following a replacement (which violates the LSP spec).
let edits = lsp_store
.update(cx, |lsp_store, cx| {
lsp_store.as_local_mut().unwrap().edits_from_lsp(
&buffer,
[
lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
new_text: "Path".into(),
},
lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
new_text: "from path import Path\n\n\n".into(),
},
],
LanguageServerId(0),
None,
cx,
)
})
.await
.unwrap();
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
assert_eq!(buffer.text(), "from path import Path\n\n\nPath()")
});
}
#[gpui::test]
async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
init_test(cx);

View File

@@ -1150,9 +1150,7 @@ impl ProjectPanel {
Some(state) => state,
None => return,
};
let filename = self.filename_editor.read(cx).text(cx);
if !filename.is_empty() {
if let Some(worktree) = self
.project
@@ -1160,6 +1158,7 @@ impl ProjectPanel {
.worktree_for_id(edit_state.worktree_id, cx)
{
if let Some(entry) = worktree.read(cx).entry_for_id(edit_state.entry_id) {
let mut already_exists = false;
if edit_state.is_new_entry() {
let new_path = entry.path.join(filename.trim_start_matches('/'));
if worktree
@@ -1167,12 +1166,7 @@ impl ProjectPanel {
.entry_for_path(new_path.as_path())
.is_some()
{
edit_state.validation_state = ValidationState::Error(format!(
"File or directory '{}' already exists at location. Please choose a different name.",
filename
));
cx.notify();
return;
already_exists = true;
}
} else {
let new_path = if let Some(parent) = entry.path.clone().parent() {
@@ -1183,18 +1177,28 @@ impl ProjectPanel {
if let Some(existing) = worktree.read(cx).entry_for_path(new_path.as_path())
{
if existing.id != entry.id {
edit_state.validation_state = ValidationState::Error(
"File or directory already exists".to_string(),
);
cx.notify();
return;
already_exists = true;
}
}
};
if already_exists {
edit_state.validation_state = ValidationState::Error(format!(
"File or directory '{}' already exists at location. Please choose a different name.",
filename
));
cx.notify();
return;
}
}
}
if filename.trim() != filename {
let trimmed_filename = filename.trim();
if trimmed_filename.is_empty() {
edit_state.validation_state =
ValidationState::Error("File or directory name cannot be empty.".to_string());
cx.notify();
return;
}
if trimmed_filename != filename {
edit_state.validation_state = ValidationState::Warning(
"File or directory name contains leading or trailing whitespace.".to_string(),
);
@@ -1202,7 +1206,6 @@ impl ProjectPanel {
return;
}
}
edit_state.validation_state = ValidationState::None;
cx.notify();
}
@@ -1216,6 +1219,9 @@ impl ProjectPanel {
let worktree_id = edit_state.worktree_id;
let is_new_entry = edit_state.is_new_entry();
let filename = self.filename_editor.read(cx).text(cx);
if filename.trim().is_empty() {
return None;
}
#[cfg(not(target_os = "windows"))]
let filename_indicates_dir = filename.ends_with("/");
// On Windows, path separator could be either `/` or `\`.

View File

@@ -766,6 +766,50 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) {
" .dockerignore",
]
);
// Test empty filename and filename with only whitespace
panel.update_in(cx, |panel, window, cx| panel.new_file(&NewFile, window, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v root1",
" > .git",
" > a",
" v b",
" > 3",
" > 4",
" > new-dir",
" [EDITOR: ''] <== selected",
" a-different-filename.tar.gz",
" > C",
]
);
panel.update_in(cx, |panel, window, cx| {
panel.filename_editor.update(cx, |editor, cx| {
editor.set_text("", window, cx);
});
assert!(panel.confirm_edit(window, cx).is_none());
panel.filename_editor.update(cx, |editor, cx| {
editor.set_text(" ", window, cx);
});
assert!(panel.confirm_edit(window, cx).is_none());
panel.cancel(&menu::Cancel, window, cx)
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v root1",
" > .git",
" > a",
" v b",
" > 3",
" > 4",
" > new-dir",
" a-different-filename.tar.gz <== selected",
" > C",
" .dockerignore",
]
);
}
#[gpui::test(iterations = 10)]

View File

@@ -31,10 +31,9 @@ pub trait RequestMessage: EnvelopedMessage {
type Response: EnvelopedMessage;
}
pub trait AnyTypedEnvelope: 'static + Send + Sync {
pub trait AnyTypedEnvelope: Any + Send + Sync {
fn payload_type_id(&self) -> TypeId;
fn payload_type_name(&self) -> &'static str;
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
fn is_background(&self) -> bool;
fn original_sender_id(&self) -> Option<PeerId>;
@@ -56,10 +55,6 @@ impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
T::NAME
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
self
}

View File

@@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::BTreeSet;
use std::path::PathBuf;
use std::sync::Arc;
@@ -1288,11 +1289,8 @@ impl RemoteServerProjects {
cx.notify();
}));
let Some(scroll_handle) = scroll_state
.scroll_handle()
.as_any()
.downcast_ref::<ScrollHandle>()
else {
let handle = &**scroll_state.scroll_handle() as &dyn Any;
let Some(scroll_handle) = handle.downcast_ref::<ScrollHandle>() else {
unreachable!()
};

View File

@@ -16,7 +16,7 @@ doctest = false
alacritty_terminal.workspace = true
anyhow.workspace = true
async-dispatcher.workspace = true
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
async-tungstenite = { workspace = true, features = ["tokio", "tokio-rustls-manual-roots"] }
base64.workspace = true
client.workspace = true
collections.workspace = true

View File

@@ -3,10 +3,8 @@ use gpui::{App, AppContext as _, Entity, Task, Window};
use http_client::{AsyncBody, HttpClient, Request};
use jupyter_protocol::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply};
use async_tungstenite::{
async_std::connect_async,
tungstenite::{client::IntoClientRequest, http::HeaderValue},
};
use async_tungstenite::tokio::connect_async;
use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue};
use futures::StreamExt;
use smol::io::AsyncReadExt as _;

View File

@@ -8,6 +8,7 @@ use futures::{SinkExt as _, StreamExt as _};
use proto::Message as _;
use std::time::Instant;
use std::{fmt::Debug, io};
use zstd::zstd_safe::WriteBuf;
const KIB: usize = 1024;
const MIB: usize = KIB * 1024;
@@ -59,7 +60,9 @@ where
self.encoding_buffer.clear();
self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
self.stream.send(WebSocketMessage::Binary(buffer)).await?;
self.stream
.send(WebSocketMessage::Binary(buffer.into()))
.await?;
}
Message::Ping => {
self.stream

View File

@@ -1030,7 +1030,7 @@ mod tests {
let _ = messages_ended_rx.await;
assert!(
server_conn
.send(WebSocketMessage::Binary(vec![]))
.send(WebSocketMessage::Binary(vec![].into()))
.await
.is_err()
);

View File

@@ -10,7 +10,7 @@ use proto::{
error::ErrorExt as _,
};
use std::{
any::TypeId,
any::{Any, TypeId},
sync::{Arc, Weak},
};
@@ -250,8 +250,7 @@ impl AnyProtoClient {
let message_type_id = TypeId::of::<M>();
let entity_type_id = TypeId::of::<E>();
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
envelope
.as_any()
(envelope as &dyn Any)
.downcast_ref::<TypedEnvelope<M>>()
.unwrap()
.payload
@@ -296,8 +295,7 @@ impl AnyProtoClient {
let message_type_id = TypeId::of::<M>();
let entity_type_id = TypeId::of::<E>();
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
envelope
.as_any()
(envelope as &dyn Any)
.downcast_ref::<TypedEnvelope<M>>()
.unwrap()
.payload

View File

@@ -1590,7 +1590,7 @@ impl Terminal {
return None;
};
Some(scroll_lines)
Some(scroll_lines.clamp(-3, 3))
}
pub fn mouse_down(&mut self, e: &MouseDownEvent, _cx: &mut Context<Self>) {

View File

@@ -1,5 +1,4 @@
use std::{
any::Any,
cell::{Cell, RefCell},
rc::Rc,
};
@@ -85,8 +84,4 @@ impl ScrollableHandle for TerminalScrollHandle {
),
)
}
fn as_any(&self) -> &dyn Any {
self
}
}

View File

@@ -2,10 +2,15 @@ use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
use crate::{ContextMenu, PopoverMenu, prelude::*};
enum LabelKind {
Text(SharedString),
Element(AnyElement),
}
#[derive(IntoElement)]
pub struct DropdownMenu {
id: ElementId,
label: SharedString,
label: LabelKind,
menu: Entity<ContextMenu>,
full_width: bool,
disabled: bool,
@@ -19,7 +24,21 @@ impl DropdownMenu {
) -> Self {
Self {
id: id.into(),
label: label.into(),
label: LabelKind::Text(label.into()),
menu,
full_width: false,
disabled: false,
}
}
pub fn new_with_element(
id: impl Into<ElementId>,
label: AnyElement,
menu: Entity<ContextMenu>,
) -> Self {
Self {
id: id.into(),
label: LabelKind::Element(label),
menu,
full_width: false,
disabled: false,
@@ -55,7 +74,7 @@ impl RenderOnce for DropdownMenu {
#[derive(IntoElement)]
struct DropdownMenuTrigger {
label: SharedString,
label: LabelKind,
full_width: bool,
selected: bool,
disabled: bool,
@@ -64,9 +83,9 @@ struct DropdownMenuTrigger {
}
impl DropdownMenuTrigger {
pub fn new(label: impl Into<SharedString>) -> Self {
pub fn new(label: LabelKind) -> Self {
Self {
label: label.into(),
label,
full_width: false,
selected: false,
disabled: false,
@@ -135,11 +154,16 @@ impl RenderOnce for DropdownMenuTrigger {
el.cursor_pointer()
}
})
.child(Label::new(self.label).color(if disabled {
Color::Disabled
} else {
Color::Default
}))
.child(match self.label {
LabelKind::Text(text) => Label::new(text)
.color(if disabled {
Color::Disabled
} else {
Color::Default
})
.into_any_element(),
LabelKind::Element(element) => element,
})
.child(
Icon::new(IconName::ChevronUpDown)
.size(IconSize::XSmall)

View File

@@ -51,6 +51,11 @@ impl Label {
label: label.into(),
}
}
/// Sets the text of the [`Label`].
pub fn set_text(&mut self, text: impl Into<SharedString>) {
self.label = text.into();
}
}
// nate: If we are going to do this, we might as well just

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