Compare commits

..

298 Commits

Author SHA1 Message Date
Max Brunsfeld
69bbcffcd9 🎨 Remove unnecessary title field from ProposedChangesEditor 2024-10-30 17:12:17 -07:00
Conrad Irwin
bd187883da Migration to remove dev servers (#19639)
Depends on #19638

Release Notes:

- None
2024-10-30 11:55:55 -06:00
Conrad Irwin
4f9217bca0 Support zed://ssh (#19970)
Closes: #15070

Release Notes:

- Added support for `zed://ssh/<connnection>/<path>`
2024-10-30 11:28:25 -06:00
Conrad Irwin
ce5222f1df Add KeyContextView (#19872)
Release Notes:

- Added `cmd-shift-p debug: Open Key Context View` to help debug custom
key bindings



https://github.com/user-attachments/assets/de273c97-5b27-45aa-9ff1-f943b0ed7dfe
2024-10-30 11:26:54 -06:00
Kirill Bulatov
cf7b0c8971 Add scrollbars to outline panel (#19969)
Part of https://github.com/zed-industries/zed/issues/15324


![image](https://github.com/user-attachments/assets/4f32d585-9bd2-46be-8234-3658a71906ee)

Repeats the approach used in the project panel.

Release Notes:

- Added scrollbars to outline panel

---------

Co-authored-by: Nate Butler <nate@zed.dev>
2024-10-30 19:09:14 +02:00
renovate[bot]
7bc4cb9868 Update Rust crate hyper to v0.14.31 (#19323)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [hyper](https://hyper.rs)
([source](https://redirect.github.com/hyperium/hyper)) |
workspace.dependencies | patch | `0.14.30` -> `0.14.31` |

---

### Release Notes

<details>
<summary>hyperium/hyper (hyper)</summary>

###
[`v0.14.31`](https://redirect.github.com/hyperium/hyper/releases/tag/v0.14.31)

[Compare
Source](https://redirect.github.com/hyperium/hyper/compare/v0.14.30...v0.14.31)

#### Bug Fixes

- **http1:** improve performance of parsing sequentially partial
messages
([97b595e](97b595e589))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-30 12:11:07 -04:00
Gherman
f84f3ffeb7 docs: Add linkedProjects section to Rust docs (#19954)
Related to #19897

Adds a section about multi-project workspaces and how to configure
rust-analyzer to diagnose them even if the cargo workspace does not list
them

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-30 11:43:44 -04:00
Richard Feldman
c564a4a26c Require /file or /tab when using Suggest Edits (#19960)
Now if you try to do Suggest Edits without a file context, you see this
(and it doesn't run the query).

<img width="635" alt="Screenshot 2024-10-30 at 10 51 24 AM"
src="https://github.com/user-attachments/assets/a3997ba6-98a9-4bfa-81b6-1d8579c26fd7">


Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-10-30 11:38:43 -04:00
Marshall Bowers
515fd7b75f git_hosting_providers: Fix support for GitLab remotes containing subgroups (#19962)
This PR fixes the support for GitLab remote URLs containing subgroups.

Reported in
https://github.com/zed-industries/zed/issues/18012#issuecomment-2446206256.

Release Notes:

- N/A
2024-10-30 11:16:44 -04:00
Peter Tripp
662a4440cc v0.161.x dev 2024-10-30 11:06:39 -04:00
Marshall Bowers
5dee43b05c dart: Extract to zed-extensions/dart repository (#19959)
This PR extracts the Dart extension to the
[zed-extensions/dart](https://github.com/zed-extensions/dart)
repository.

Release Notes:

- N/A
2024-10-30 11:00:06 -04:00
Antonio Scandurra
c8003c0697 Take a mutable context when resolving selections (#19948)
This is a behavior-preserving change, but lays the groundwork for
expanding selections when the cursor lands inside of a "replace" block.

Release Notes:

- N/A
2024-10-30 15:21:51 +01:00
Lukas Geiger
83e2889d63 Fix notebook cell-height when soft-wrapping lines (#19933) 2024-10-30 07:12:32 -07:00
Kirill Bulatov
d49cd0019f Log prettier errors on failures (#19951)
Closes https://github.com/zed-industries/zed/issues/11987

Release Notes:

- Fixed prettier not reporting failures in the status panel on
formatting and installation errors
2024-10-30 14:49:47 +02:00
Thorsten Ball
0ba40bdfb8 remote dev: Always upload binary in development mode (#19953)
Release Notes:

- N/A
2024-10-30 13:41:28 +01:00
Thorsten Ball
f6cd97f6fd remote dev: Allow canceling language server work in editor (#19946)
Release Notes:

- Added ability to cancel language server work in remote development.

Demo:



https://github.com/user-attachments/assets/c9ca91a5-617f-4886-a458-87c563c5a247
2024-10-30 13:27:11 +01:00
Thorsten Ball
774a8bf039 inline blame: Fix default setting for inline blame (#19943)
Follow-up to #19759. Fixes the default value. cc @pjtatlow 😄 

Release Notes:

- N/A
2024-10-30 11:40:04 +01:00
Antonio Scandurra
4431ef1870 Speed up point translation in the Rope (#19913)
This pull request introduces an index of Unicode codepoints, newlines
and UTF-16 codepoints.

Benchmarks worth a thousand words:

```
push/4096               time:   [467.06 µs 470.07 µs 473.24 µs]
                        thrpt:  [8.2543 MiB/s 8.3100 MiB/s 8.3635 MiB/s]
                 change:
                        time:   [-4.1462% -3.0990% -2.0527%] (p = 0.00 < 0.05)
                        thrpt:  [+2.0957% +3.1981% +4.3255%]
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild
push/65536              time:   [1.4650 ms 1.4796 ms 1.4922 ms]
                        thrpt:  [41.885 MiB/s 42.242 MiB/s 42.664 MiB/s]
                 change:
                        time:   [-3.2871% -2.3489% -1.4555%] (p = 0.00 < 0.05)
                        thrpt:  [+1.4770% +2.4054% +3.3988%]
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  3 (3.00%) low severe
  3 (3.00%) low mild

append/4096             time:   [729.00 ns 730.57 ns 732.14 ns]
                        thrpt:  [5.2103 GiB/s 5.2215 GiB/s 5.2327 GiB/s]
                 change:
                        time:   [-81.884% -81.836% -81.790%] (p = 0.00 < 0.05)
                        thrpt:  [+449.16% +450.53% +452.01%]
                        Performance has improved.
Found 11 outliers among 100 measurements (11.00%)
  3 (3.00%) low mild
  6 (6.00%) high mild
  2 (2.00%) high severe
append/65536            time:   [504.44 ns 505.58 ns 506.77 ns]
                        thrpt:  [120.44 GiB/s 120.72 GiB/s 121.00 GiB/s]
                 change:
                        time:   [-94.833% -94.807% -94.782%] (p = 0.00 < 0.05)
                        thrpt:  [+1816.3% +1825.8% +1835.5%]
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  3 (3.00%) high mild
  1 (1.00%) high severe

slice/4096              time:   [29.661 µs 29.733 µs 29.816 µs]
                        thrpt:  [131.01 MiB/s 131.38 MiB/s 131.70 MiB/s]
                 change:
                        time:   [-48.833% -48.533% -48.230%] (p = 0.00 < 0.05)
                        thrpt:  [+93.161% +94.298% +95.440%]
                        Performance has improved.
slice/65536             time:   [588.00 µs 590.22 µs 592.17 µs]
                        thrpt:  [105.54 MiB/s 105.89 MiB/s 106.29 MiB/s]
                 change:
                        time:   [-45.599% -45.347% -45.099%] (p = 0.00 < 0.05)
                        thrpt:  [+82.147% +82.971% +83.821%]
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) low severe
  1 (1.00%) high mild

bytes_in_range/4096     time:   [3.8630 µs 3.8811 µs 3.8994 µs]
                        thrpt:  [1001.8 MiB/s 1006.5 MiB/s 1011.2 MiB/s]
                 change:
                        time:   [+0.0600% +0.6000% +1.1833%] (p = 0.03 < 0.05)
                        thrpt:  [-1.1695% -0.5964% -0.0600%]
                        Change within noise threshold.
bytes_in_range/65536    time:   [98.178 µs 98.545 µs 98.931 µs]
                        thrpt:  [631.75 MiB/s 634.23 MiB/s 636.60 MiB/s]
                 change:
                        time:   [-0.6513% +0.7537% +2.2265%] (p = 0.30 > 0.05)
                        thrpt:  [-2.1780% -0.7481% +0.6555%]
                        No change in performance detected.
Found 11 outliers among 100 measurements (11.00%)
  8 (8.00%) high mild
  3 (3.00%) high severe

chars/4096              time:   [878.91 ns 879.45 ns 880.06 ns]
                        thrpt:  [4.3346 GiB/s 4.3376 GiB/s 4.3403 GiB/s]
                 change:
                        time:   [+9.1679% +9.4000% +9.6304%] (p = 0.00 < 0.05)
                        thrpt:  [-8.7844% -8.5923% -8.3979%]
                        Performance has regressed.
Found 8 outliers among 100 measurements (8.00%)
  1 (1.00%) low severe
  1 (1.00%) low mild
  3 (3.00%) high mild
  3 (3.00%) high severe
chars/65536             time:   [15.615 µs 15.691 µs 15.757 µs]
                        thrpt:  [3.8735 GiB/s 3.8899 GiB/s 3.9087 GiB/s]
                 change:
                        time:   [+5.4902% +5.9345% +6.4044%] (p = 0.00 < 0.05)
                        thrpt:  [-6.0190% -5.6021% -5.2045%]
                        Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) low mild

clip_point/4096         time:   [29.677 µs 29.835 µs 30.019 µs]
                        thrpt:  [130.13 MiB/s 130.93 MiB/s 131.63 MiB/s]
                 change:
                        time:   [-46.306% -45.866% -45.436%] (p = 0.00 < 0.05)
                        thrpt:  [+83.272% +84.728% +86.240%]
                        Performance has improved.
Found 11 outliers among 100 measurements (11.00%)
  3 (3.00%) high mild
  8 (8.00%) high severe
clip_point/65536        time:   [1.5933 ms 1.6116 ms 1.6311 ms]
                        thrpt:  [38.318 MiB/s 38.782 MiB/s 39.226 MiB/s]
                 change:
                        time:   [-30.388% -29.598% -28.717%] (p = 0.00 < 0.05)
                        thrpt:  [+40.286% +42.040% +43.653%]
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 7 filtered out; finished in 0.00s

point_to_offset/4096    time:   [14.493 µs 14.591 µs 14.707 µs]
                        thrpt:  [265.61 MiB/s 267.72 MiB/s 269.52 MiB/s]
                 change:
                        time:   [-71.990% -71.787% -71.588%] (p = 0.00 < 0.05)
                        thrpt:  [+251.96% +254.45% +257.01%]
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  5 (5.00%) high mild
  4 (4.00%) high severe
point_to_offset/65536   time:   [700.72 µs 713.75 µs 727.26 µs]
                        thrpt:  [85.939 MiB/s 87.566 MiB/s 89.194 MiB/s]
                 change:
                        time:   [-61.778% -61.015% -60.256%] (p = 0.00 < 0.05)
                        thrpt:  [+151.61% +156.51% +161.63%]
                        Performance has improved.
```

Calling `Rope::chars` got slightly slower but I don't think it's a big
issue (we don't really call `chars` for an entire `Rope`).

In a future pull request, I want to use the tab index (which we're not
yet using) and the char index to make `TabMap` a lot faster.

Release Notes:

- N/A
2024-10-30 10:59:03 +01:00
Mikayla Maki
b3f0ba1430 Implement panic reporting saving and uploads (#19932)
TODO: 
- [x] check that the app version is well formatted for zed.dev

Release Notes:

- N/A

---------

Co-authored-by: Trace <violet.white.batt@gmail.com>
2024-10-29 23:54:00 -07:00
Nate Butler
a5f52f0f04 Use theme families to refine user themes (#19936)
This PR changes the way we load user themes into the ThemeRegistry. 

Rather than directly pass a theme family's themes to
`insert_user_themes`, instead we use the new `refine_theme_family ` and
`ThemeFamily::refine_theme`.

This PR should have net zero change to themes today, but sets up
enabling theme variables. We need to do it this way so each theme has
access to it's family when it is refined.

Release Notes:

- N/A
2024-10-29 22:30:58 -04:00
Nate Butler
63524a2354 Add missing full-size styles for panes (#19935)
As we don't use scrolling flex layouts directly in panes that often, the
methods that would normally be applied to containers that should fill
the space weren't applied here.

Should help un-stuck #19872's layout issue, but I'm merging this change
separately in case it creates some other layout issue in panes.

Release Notes:

- N/A
2024-10-29 21:16:45 -04:00
Marshall Bowers
90edb7189f git_hosting_providers: Clean up tests (#19927)
This PR cleans up the tests for the various Git hosting providers.

These tests had rotted a bit over time, to the point that some of them
weren't even testing what they claimed anymore.

Release Notes:

- N/A
2024-10-29 17:24:32 -04:00
Max Brunsfeld
518f6b529b Fix missing diagnostic and text highlights after blocks (#19920)
Release Notes:

- Fixed an issue where diagnostic underlines and certain text highlights
were not rendered correctly below block decorations such as the inline
assistant prompt.

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
2024-10-29 13:47:43 -07:00
Conrad Irwin
fb97e462de Better handle interrupted connections for shared SSH (#19925)
Co-Authored-By: Mikayla <mikayla@zed.dev>
2024-10-29 16:43:34 -04:00
Marshall Bowers
5b7fa05a87 Make Git remote URL parsing more robust (#19924)
This PR improves the parsing of Git remote URLs in order to make
features that depend on them more robust.

Previously we were just treating these as plain strings and doing
one-off shotgun parsing to massage them into the right format. This
meant that we weren't accounting for edge cases in URL structure.

One of these cases was HTTPS Git URLs containing a username, which can
arise when using GitHub Enterprise (see
https://github.com/zed-industries/zed/issues/11160).

We now have a `RemoteUrl` typed to represent a parsed Git remote URL and
use the `Url` parser to parse it.

Release Notes:

- Improved the parsing of Git remote URLs to support additional
scenarios.
2024-10-29 16:19:05 -04:00
Conrad Irwin
d310a1269f SSH Remoting: Fix diagnostic summary syncing (#19923)
Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- SSH Remoting: Fix diagnostics summary over collab

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-29 13:02:32 -07:00
Mikayla Maki
9818835c9d Fix the log spam from the BlameBuffer request (#19921)
Release Notes:

- N/A
2024-10-29 13:02:21 -07:00
Mikayla Maki
f3b7f5944d Fix a rare crash on startup (#19922)
Release Notes:

- Fixed a rare crash that could happen when certain SQL statements are
prepared
2024-10-29 12:30:55 -07:00
Conrad Irwin
fc5cde9434 Fix quotes in Rust (#19914)
Release Notes:

- (preview only) Fixed quote-autoclose in Rust
2024-10-29 12:35:34 -06:00
Kyle Kelley
6ea4662326 Initial Notebook UI structure (#19756)
This is the start of a notebook UI for Zed. 

`🔔 Note: This won't be useable yet when it is merged! Read below. 🔔`

This is going to be behind a feature flag so that we can merge this
initial PR and then make follow up PRs. Release notes will be produced
in a future PR.

Minimum checklist for merging this:

* [x] All functionality behind the `notebooks` feature flag (with env
var opt out)
* [x] Open notebook files in the workspace
* [x] Remove the "Open Notebook" button from title bar
* [x] Incorporate text style refinements for cell editors
* [x] Rely on `nbformat` crate for parsing the notebook into our
in-memory format
* [x] Move notebook to a `gpui::List`
* [x] Hook up output rendering


Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-10-29 11:30:07 -07:00
Joseph T. Lyons
9d12308d06 Fix Julia icon extension lookup (#19916)
Release Notes:

- Fixed a bug where the Julia icon was not displayed for Julia files.
2024-10-29 14:07:54 -04:00
Richard Feldman
21137d2ba7 Delete /workflow (#19900)
This a separate PR from https://github.com/zed-industries/zed/pull/19705
so we can revert it more easily if we want it back later.

Release Notes:

- Added "Suggest Edit" button to the assistant panel if
`"enable_experimental_live_diffs": true` is set in the `"assistant"`
section of `settings.json`. This button takes the place of the previous
`/workflow` command, but it is experimental and may change!

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2024-10-29 13:57:33 -04:00
Conrad Irwin
273cb1921f Fix wrong UpdateWorktree chunk size being used in release mode (#19912)
Release Notes:

- Fixed slowness when collaborating

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-29 11:22:41 -06:00
Nathan Sobo
cfa20ff221 Sketch in assistant edit button (#19705)
Add an edit button to the assistant. This is totally hacked in for now,
just to see how this would feel rendered simply in the UI.

![CleanShot 2024-10-24 at 16 26
14@2x](https://github.com/user-attachments/assets/e630d078-78b7-42d7-93f1-cf61c00bd20e)

cc @as-cii @danilo-leal 

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2024-10-29 13:21:10 -04:00
Mikayla Maki
759d136fe6 Update a few doc comments (#19911)
Release Notes:

- N/A
2024-10-29 10:09:49 -07:00
Marshall Bowers
322aa41ad6 Add support for self-hosted GitLab instances for Git permalinks (#19909)
This PR adds support for self-hosted GitLab instances when generating
Git permalinks.

If the `origin` Git remote contains `gitlab` in the URL hostname we will
then attempt to register it as a self-hosted GitLab instance.

A note on this: I don't think relying on specific keywords is going to
be a suitable long-term solution to detection. In reality the
self-hosted instance could be hosted anywhere (e.g.,
`vcs.my-company.com`), so we will ultimately need a way to have the user
indicate which Git provider they are using (perhaps via a setting).

Closes https://github.com/zed-industries/zed/issues/18012.

Release Notes:

- Added support for self-hosted GitLab instances when generating Git
permalinks.
- The instance URL must have `gitlab` somewhere in the host in order to
be recognized.
2024-10-29 12:31:51 -04:00
Max Brunsfeld
3e2f1d733c Fix horizontal scroll caused by diagnostic block width error (#19856)
Previously, when scrolling the diagnostics view with the mouse, we'd get
a spurious horizontal scroll (even if the content was not overflowing
horizontally) due to an error in the widths of the diagnostic blocks.

Release Notes:

- Fixed an issue where the project diagnostics view spuriously allowed
horizontal scrolling by a small amount.
2024-10-29 09:16:38 -07:00
Michael Sloan
3fed738d2f Use same logic for skipping single instance check on Linux as on Mac/Win (#19446)
Release Notes:

- Linux: Now skips check which exits with "zed is already running" when
in development mode or when run with `zed-local`, matching the behavior
on Mac and Windows

Co-authored-by: Nathan Sobo <nathan@zed.dev>
2024-10-29 09:12:34 -07:00
Kirill Bulatov
5893e85708 Ensure shared ssh project propagates buffer changes to all participants (#19907)
Fixed the bug when shared ssh project did not account for client
changing things in their buffers.
Also ensures Prettier formatting workflow works for both ssh project
owner and ssh project clients.

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-10-29 17:24:10 +02:00
Joseph T. Lyons
1356665ed3 Update community links page url (#19899)
See:

- https://github.com/zed-industries/zed.dev/pull/786

Release Notes:

- N/A
2024-10-29 10:13:26 -04:00
Jen Stehlik
9739da8de3 Add Gleam icon (#19887)
I took a shot at creating an icon version of the Gleam logo in response
to https://github.com/zed-industries/zed/pull/19529

Release Notes:

- Added an icon for Gleam files.


![image](https://github.com/user-attachments/assets/97432ded-342f-4d87-8eb2-dc9145513d8c)

<img width="231" alt="Screenshot 2024-10-29 at 9 46 33 AM"
src="https://github.com/user-attachments/assets/c957c98f-3da0-4b92-bc21-2a5adca1daa3">

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-29 09:54:21 -04:00
Joseph T. Lyons
249c8a4d96 Remove community content from docs and point to zed.dev (#19895)
The community content now lives on zed.dev, discoverable via the navbar
`resources` menu.

See:

- https://github.com/zed-industries/zed.dev/pull/783

Release Notes:

- N/A
2024-10-29 09:44:58 -04:00
Thorsten Ball
f919fa92de remote servers: Fix title from alpha to beta (#19889)
Discussed this in Slack yesterday. We use `beta` because that's what we
use in the docs as well.

Release Notes:

- N/A
2024-10-29 13:38:30 +01:00
Bennet Bo Fenner
21b58643fa vsc menu: Fix issue when switching branch while non-visible worktree is open (#19888)
Fixes a regression introduced in #19755

<img width="935" alt="Screenshot 2024-10-29 at 12 13 04"
src="https://github.com/user-attachments/assets/7699b8da-631d-4932-89a8-bc5d7f2546f1">

Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- Fixed an issue where the branch switcher would show an error, when
opening a file outside of the project

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-29 12:27:30 +01:00
Bennet Bo Fenner
6a0bcca9ec ssh remoting: Hide share button while connecting to project (#19885)
Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- N/A

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-29 12:13:21 +01:00
PJ Tatlow
84328c303b Include commit summary in inline Git blame (#19759)
Closes #19758

Release Notes:

- Added feature to show commit summary as part of the inline Git blame

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-10-29 11:35:31 +01:00
Bennet Bo Fenner
f7b2b41df9 ssh remoting: Check nightly version correctly by comparing commit SHA (#19884)
This ensures that we detect if a new nightly version of the remote
server is available.
Previously we would always mark a version as matching if they had the
same semantic version.
However, for nightly versions we also need to check if they have the
same commit SHA.

Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-29 11:32:55 +01:00
Bennet Bo Fenner
7a6b6435c4 languages: Enable grammar loading when compiling with test feature (#19881)
This ensures that `cargo tests -p languages` will not fail with a
confusing error message.

Follow up to #19821

We opted to check the `test` feature flag instead of defining a runtime
flag, because we only want to include the `tree-sitter-*` dependencies
in some cases, which is not possible with a runtime flag.

Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- N/A

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-29 11:00:44 +01:00
Bennet Bo Fenner
bdb54decdc ssh remoting: Show the host's GitHub name in the titlebar when sharing an SSH project (#19844)
The name (GitHub name) of the host was not displayed when sharing an ssh
project.

Previously we assumed that the a collaborator is a host if the
`replica_id` of the collaborator was `0`,
but for ssh project the `replica_id` is actually `1`.

<img width="329" alt="Screenshot 2024-10-28 at 18 16 30"
src="https://github.com/user-attachments/assets/c0151e12-a96f-4f38-aec1-4ed5475a9eaf">


Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-29 09:52:54 +01:00
Bennet Bo Fenner
b5c41eeb98 Future-proof indent guides settings for panels (#19878)
This PR ensures that we do not have to break the indent guides settings
for the project/outline panel. In the future we might want to have a
more granular way to control when to show indent guides, or control
other indent guide properties, like its width.

Release Notes:

- N/A
2024-10-29 09:52:36 +01:00
Conrad Irwin
719a7f7890 Fix block cursor on graphemes (#19867)
Release Notes:

- Fixed block cursor rendering only first char of multii-char graphemes.
2024-10-28 21:05:24 -06:00
Joseph T. Lyons
1b84fee708 restore editor::UnfoldRecursive binding (#19865) 2024-10-28 22:32:59 -04:00
Conrad Irwin
58e5d4ff02 Reland invisibles (#19846)
Release Notes:

- Show invisibles in the editor

Relands #19298 

Trying to quantify a performance impact, it doesn't seem to impact much
visible in Instruments or in a micro-benchmark of Editor#layout_lines.
We're still taking a few hundred micro-seconds (+/- a lot) every time.
The ascii file has just ascii, where as the cc file has one control
character per line.

<img width="1055" alt="Screenshot 2024-10-28 at 12 14 53"
src="https://github.com/user-attachments/assets/1c382063-bb19-4e92-bbba-ed5e7c02309f">
<img width="1020" alt="Screenshot 2024-10-28 at 12 15 07"
src="https://github.com/user-attachments/assets/1789f65e-5f83-4c32-be47-7748c62c3703">
2024-10-28 20:27:09 -06:00
Mikayla Maki
85ff03cde0 Add more context to the save new file path picker (#19863)
Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2024-10-28 17:21:41 -07:00
Conrad Irwin
a3f0bb4547 SSH Remoting: Document manual binary management (#19862)
Release Notes:

- N/A
2024-10-28 17:10:08 -06:00
Marshall Bowers
93b20008e0 Add support for Doxygen doc comments in C++ (#19858)
This PR adds support for Doxygen-style doc comments in C++.

<img width="962" alt="Screenshot 2024-10-28 at 5 38 34 PM"
src="https://github.com/user-attachments/assets/57d0fa4b-07c1-4b71-899a-fba78e822e8f">

https://www.doxygen.nl/manual/docblocks.html

Closes https://github.com/zed-industries/zed/issues/18361.

Release Notes:

- C++: Added support for Doxygen-style doc comments starting with `/// `
or `//! `.
2024-10-28 17:44:23 -04:00
ClanEver
188a893fd0 python: Enhance syntax highlighting for type hints (#18185)
Release Notes:

- Python: Improved syntax highlighting for type hints.

# Before

![image](https://github.com/user-attachments/assets/876a69ab-a572-4d1b-af99-e6f85f249ea6)

# After

![image](https://github.com/user-attachments/assets/4fb98a9b-bc5d-4799-b535-057047884383)

---
Why manual recursion?
- Due to tree-sitter grammar not supporting recursion in query
(https://github.com/tree-sitter-grammars/tree-sitter-lua/issues/24),
currently only manual recursion is possible (refer to
https://github.com/projekt0n/github-nvim-theme/pull/250/files).

<br/>

Unable to highlight when simple structures appear before complex
structures, example:
```python
def t() -> str | dict[int, dict[int, dict[int, str]]]:
    pass
```
Because complex structures are parsed as `subscript` rather than
`generic_type` by tree-sitter in this case ☹

<br/>

Related:

- https://github.com/zed-industries/zed/issues/14715
- [Union Type (Python
Doc)](https://docs.python.org/3/library/stdtypes.html#union-type)
- [Type parameter lists (Python
Doc)](https://docs.python.org/3/reference/compound_stmts.html#type-parameter-lists)

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-28 16:56:59 -04:00
Piotr Osiewicz
052b746fbd language_selector: Fix debug_assert firing off on context menu creation for LSP view (#19854)
Closes #ISSUE

Release Notes:

- N/A
2024-10-28 21:55:38 +01:00
Mikayla Maki
80f89059aa Fix mouse clicks on remote-open-folder UI (#19851)
Also change Zed's standard style to use
`.track_focus(&self.focus_handle(cx))`, instead of
`.track_focus(&self.focus_handle)`, to catch these kinds of errors more
easily in the future.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
2024-10-28 12:55:55 -07:00
Mikayla Maki
826d83edfe Fix backtrace spam on remote server (#19850)
Release Notes:

- N/A

Co-authored-by: conrad <conrad@zed.dev>
2024-10-28 12:28:42 -07:00
Joseph T. Lyons
f5d5fab2c8 Improve fold_at_level performance (#19845)
Just spotted a tiny error that was causing us to continue looking for
nested folds 1 layer deeper than any fold already found at the target
level. We shouldn't continue to seek for a deeper fold after the fold at
the target level is found.

Tested on a debug build and used `editor.rs` as the source material:

```
Old

Level 1 fold:
[crates/editor/src/editor.rs:10777:9] counter = 2806
[crates/editor/src/editor.rs:10778:9] time_elapsed = 320.570792ms

Level 2 fold:
[crates/editor/src/editor.rs:10777:9] counter = 5615
[crates/editor/src/editor.rs:10778:9] time_elapsed = 497.4305ms

Level 3 fold:
[crates/editor/src/editor.rs:10777:9] counter = 7528
[crates/editor/src/editor.rs:10778:9] time_elapsed = 619.818334ms

New

Level 1 fold:
[crates/editor/src/editor.rs:10776:9] counter = 543
[crates/editor/src/editor.rs:10777:9] time_elapsed = 139.115625ms

Level 2 fold:
[crates/editor/src/editor.rs:10776:9] counter = 2806
[crates/editor/src/editor.rs:10777:9] time_elapsed = 312.560416ms

Level 3 fold:
[crates/editor/src/editor.rs:10776:9] counter = 5615
[crates/editor/src/editor.rs:10777:9] time_elapsed = 498.873292ms
```

Release Notes:

- N/A
2024-10-28 13:37:28 -04:00
Thorsten Ball
fab2f22a89 remote project: Fix project reference leak when waiting for prompt reply (#19838)
When the language server gave us a prompt and we'd close the window, we
wouldn't release the `project` until the next `flush_effects` call that
came in when opening a window.

With this change, we no longer hold a strong reference to the project in
the future. Fixes the leak and makes sure we clean up the SSH connection
when closing a window.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-28 17:07:30 +01:00
Marshall Bowers
a451bcc3c4 collab: Exempt staff from LLM usage limits (#19836)
This PR updates the usage limit check to exempt Zed staff members from
usage limits.

We previously had some affordances for the rate limits, but hadn't yet
updated it for the usage-based billing.

Release Notes:

- N/A
2024-10-28 11:45:18 -04:00
Marshall Bowers
5e9ff3e313 dart: Bump to v0.1.2 (#19835)
This PR bumps the Dart extension to v0.1.2.

Changes:

- https://github.com/zed-industries/zed/pull/19592

Release Notes:

- N/A
2024-10-28 11:36:44 -04:00
Thorsten Ball
cc81f19c68 remote server: Fix error log about inability to open buffer (#19824)
Turns out that we used client-side `fs` to check whether something is a
directory or not, which obviously doesn't work with SSH projects.

Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-28 16:35:37 +01:00
Ömer Sinan Ağacan
5e89fba681 dart: Add support for documentation comments (#19592)
Closes #19590

Release Notes:

- N/A
---

I'm unable to test this because rebuilding Zed with the changes does not
seem to use the changes. If maintainers could let me know how to test
these changes I'd like to verify that this really fixes #19590.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-28 11:20:04 -04:00
Thorsten Ball
67eb652bf1 remote servers: Always dismiss modal (#19831)
We display the errors in another window anyway and if the connection
takes a while it looks like a bug that the modal stays open.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-28 16:12:37 +01:00
Bennet Bo Fenner
e0ea9a9ab5 Remove leftover comments from previous PR (#19820)
Co-Authored-by: Thorsten <thorsten@zed.dev>

Removes some leftover comments from #19766 

Release Notes:

- N/A

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-28 16:00:38 +01:00
xdBronch
ff29a34298 zig: Account for doctests in outline (#19776)
zig has a feature called
[doctests](https://ziglang.org/documentation/master/#Doctests) where
instead of providing a string as the name of a test you use an
identifier so that the test is "tied" to it and can be used in
documentation. this wasnt accounted for so any tests using this were
unnamed in the outline

Release Notes:

- N/A
2024-10-28 10:49:40 -04:00
Thorsten Ball
6686f66949 ollama: Ensure only single task fetches models (#19830)
Before this change, we'd see a ton of requests from the Ollama provider
trying to fetch models:

```
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: https://api.zed.dev/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
[2024-10-28T15:00:52+01:00 DEBUG reqwest::connect] starting new connection: http://localhost:11434/
```

Turns out we'd send a request on *every* change to settings.

Now, with this change, we only send a single request.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-28 15:40:50 +01:00
David Soria Parra
8a96ea25c4 context_servers: Support tools (#19548)
This PR depends on #19547 

This PR adds support for tools from context servers. Context servers are
free to expose tools that Zed can pass to models. When called by the
model, Zed forwards the request to context servers. This allows for some
interesting techniques. Context servers can easily expose tools such as
querying local databases, reading or writing local files, reading
resources over authenticated APIs (e.g. kubernetes, asana, etc).

This is currently experimental. 

Things to discuss
* I want to still add a confirm dialog asking people if a server is
allows to use the tool. Should do this or just use the tool and assume
trustworthyness of context servers?
* Can we add tool use behind a local setting flag?

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-28 10:37:58 -04:00
Piotr Osiewicz
cdddb4d360 Add language toolchains (#19576)
This PR adds support for selecting toolchains for a given language (e.g.
Rust toolchains or Python virtual environments) with support for SSH
projects provided out of the box. For Python we piggy-back off of
[PET](https://github.com/microsoft/python-environment-tools), a library
maintained by Microsoft.
Closes #16421
Closes #7646

Release Notes:

- Added toolchain selector to the status bar (with initial support for
Python virtual environments)
2024-10-28 15:34:03 +01:00
Thorsten Ball
03bd95405b docs: Add diagram to remote development docs (#19827)
Release Notes:

- N/A
2024-10-28 14:14:51 +01:00
Kirill Bulatov
177dfdf900 Declare RUSTFLAGS env var for all CI jobs (#19826)
Follow-up of https://github.com/zed-industries/zed/pull/19149 

Makes RUSTFLAGS propagation uniform, to ensure all `cargo ...` jobs get
the same RUSTFLAGS env set.

Release Notes:

- N/A
2024-10-28 14:53:40 +02:00
Thorsten Ball
2ab0b3b819 remote server: Fix language servers not starting (#19821)
PR #19653 change the code in this diff, which lead to the remote_server
binary trying to load language grammars, which in turn failed, and
stopped languages from being loaded correctly.

That then lead to language servers not starting up.

This change reintroduces what #19653 removed, so that we don't load the
grammar on the remote_server, by ignoring the grammar name from the
config. The tests still all work.


Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-28 11:02:46 +01:00
Bennet Bo Fenner
888fec9299 outline panel: Add indent guides (#19719)
See #12673

| File | Search |
|--------|--------|
| <img width="302" alt="image"
src="https://github.com/user-attachments/assets/44b8d5f9-8446-41b5-8c0f-e438050f0ac9">
| <img width="301" alt="image"
src="https://github.com/user-attachments/assets/a2e6f77b-6d3b-4f1c-8fcb-16bd35274807">
|



Release Notes:

- Added indent guides to the outline panel
2024-10-28 09:54:18 +01:00
Bennet Bo Fenner
e86b096b92 docs: Add indent_guides setting to project panel docs (#19819)
Follow up to #18260 

Release Notes:

- N/A
2024-10-28 09:45:19 +01:00
Mikayla Maki
ffe36c9beb Remove hosted projects (#19754)
Release Notes:

- N/A
2024-10-27 19:44:21 -07:00
Kirill Bulatov
2d16d2d036 Fixed outline panel panicking on filtering (#19811)
Closes https://github.com/zed-industries/zed/issues/19732

Release Notes:

- Fixed outline panel panicking on filtering
([#19732](https://github.com/zed-industries/zed/issues/19732))
2024-10-28 02:31:02 +02:00
Mikayla Maki
c69da2df70 Add support for git branches on remote projects (#19755)
Release Notes:

- Fixed a bug where the branch switcher could not be used remotely.
2024-10-27 15:50:54 -07:00
CharlesChen0823
5506669b06 windows: Fix more windows platform test (#19802)
Release Notes:

- N/A

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-10-27 19:15:23 +02:00
Bennet Bo Fenner
b13940720a markdown preview: Ignore inline HTML tags in text (#19804)
Follow up to #19785

This PR ensures that we explicitly ignore inline HTML tags so that we
can still extract the text between the tags and show them to the user

Release Notes:

- N/A
2024-10-27 14:34:59 +01:00
Peter Tripp
db61711753 ci: Don't run GitHub Actions workflows on forks (#19789)
- Closes: https://github.com/zed-industries/zed/issues/19351

Release Notes:

- N/A
2024-10-27 09:04:52 -04:00
Joseph T. Lyons
c12a9f2673 Add fold_at_level test (#19800) 2024-10-26 21:57:55 -04:00
Kirill Bulatov
2e32f1c8a1 Restore horizontal scrollbar checks (#19767)
Closes https://github.com/zed-industries/zed/issues/19637

Follow-up of https://github.com/zed-industries/zed/pull/18927 , restores
the condition that removed the horizontal scrollbar when panel's items
are not long enough.

Release Notes:

- Fixed horizontal scrollbar not being hidden
([#19637](https://github.com/zed-industries/zed/issues/19637))
2024-10-26 21:57:22 +03:00
Bennet Bo Fenner
03a1c8d2b8 markdown preview: Fix infinite loop in parser when parsing list items (#19785)
Release Notes:

- Fixed an issue with the markdown parser when opening a markdown
preview file that contained HTML tags inside a list item
2024-10-26 14:59:46 +02:00
Kirill Bulatov
d7a277607b Fix a few Windows tests (#19773) 2024-10-26 03:32:22 +03:00
Thorsten Ball
fc8a72cdd8 WIP: ssh remoting: Add upload_binary field to SshConnections (#19748)
This removes the old `remote_server { "download_binary_on_host": bool }`
field and replaces it with a `upload_binary: bool` on every
`ssh_connection`.


@ConradIrwin it compiles, it connects, but I haven't tested it really
yet

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-10-25 17:32:54 -06:00
Kirill Bulatov
1acebb3c47 Remove another false-positive Danger message (#19769)
Follow-up of https://github.com/zed-industries/zed/pull/19151

Ignores any URLs aftrer `Release Notes:` (if present) and after
`Follow-up of` and `Part of` words.


Release Notes:

- N/A
2024-10-26 01:55:46 +03:00
Conrad Irwin
78ed0c9312 vim: Copy comment to new lines with o/O (#19766)
Co-Authored-By: Kurt Wolf <kurtwolfbuilds@gmail.com>

Closes: #4691

Closes #ISSUE

Release Notes:

- vim: o/O now respect `extend_comment_on_newline`
2024-10-25 16:47:44 -06:00
Conrad Irwin
98d2e5fe73 Quote fixes (#19765)
Closes #19372

Release Notes:

- Fixed autoclosing quotes when the string is already open.
- Added autoclosing of rust multiline strings

---------

Co-authored-by: Kurt Wolf <kurtwolfbuilds@gmail.com>
2024-10-25 16:28:08 -06:00
Max Brunsfeld
4325819075 Fix more failure cases of assistant edits (#19653)
* Make `description` optional (since we describe it as optional in the
prompt, and we're currently not showing it)
* Fix fuzzy location bug that neglected the cost of deleting prefixes of
the query.
* Make auto-indent work for single-line edits. Previously, auto-indent
would not occur when overwriting a single line (without inserting or
deleting a newline)

Release Notes:

- N/A
2024-10-25 14:30:34 -07:00
Marshall Bowers
c19c89e6df collab: Include checkout_complete query parameter after checking out (#19763)
This PR updates the checkout flow to include the `?checkout_complete=1`
query parameter after successfully checking out.

We'll use this on the account page to adapt the UI accordingly.

Release Notes:

- N/A
2024-10-25 16:29:20 -04:00
Joseph T. Lyons
507929cb79 Add editor: fold at level <level> commands (#19750)
Closes https://github.com/zed-industries/zed/issues/5142

Note that I only moved the cursor to the top of the file so it wouldn't
jump - the commands work no matter where you are in the file.


https://github.com/user-attachments/assets/78c74ca6-5c17-477c-b5d1-97c5665e44b0

Also, is VS Code doing this right thing here? or is it busted?


https://github.com/user-attachments/assets/8c503b50-9671-4221-b9f8-1e692fe8cd9a

Release Notes:

- Added `editor: fold at level <level>` commands. macOS: `cmd-k,
cmd-<number>`, Linux: `ctrl-k, ctrl-<number>`.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-10-25 16:08:37 -04:00
Max Brunsfeld
7d0a7aff44 Fix condition for re-using highlights when seeking buffer chunks iterator (#19760)
Fixes a syntax highlighting regression introduced in
https://github.com/zed-industries/zed/pull/19531, which caused syntax
highlighting to be missing after any block.

Release Notes:

- N/A
2024-10-25 12:37:03 -07:00
Kirill Bulatov
92ba18342c Properly deserialize active pane in the workspace (#19744)
Without setting the active pane metadata, no center pane events are
emitted on start before the pane is focused manually, which breaks
deserialization of other components like outline panel, which should
show the active pane's active item outlines on start.

Release Notes:

- N/A

Co-authored-by: Thorsten Ball <thorsten@zed.dev>
2024-10-25 22:04:09 +03:00
Kirill Bulatov
6de5ace116 Update outline panel representation when a theme is changed (#19747)
Release Notes:

- N/A
2024-10-25 22:04:02 +03:00
Richard Feldman
c9db1b9a7b Add keybindings for accepting hunks (#19749)
I went with Cmd-Shift-Y on macOS (Ctrl-Shift-Y on Linux) for "yes accept
this individual hunk" - both are currently unused.

I went with Cmd-Shift-A on macOS (Ctrl-Alt-A on Linux) for "accept all
hunks" - both are unused. (Ctrl-Shift-A on Linux was taken, as is
Ctrl-Alt-Y, so although the pairing of Ctrl-Shift-Y and Ctrl-Alt-A isn't
necessarily obvious, the letters seem intuitive - "yes" and "all" - and
those key combinations don't conflict with anything.)

Release Notes:

- Added keybindings for applying hunks in Proposed Changes
<img width="247" alt="Screenshot 2024-10-25 at 12 47 00 PM"
src="https://github.com/user-attachments/assets/d6355621-ba80-4ee2-8918-b7239a4d29be">
2024-10-25 14:10:04 -04:00
Nathan Sobo
24cb694494 Update placeholder text with key bindings to focus context panel and navigate history (#19447)
Hopefully, this will help people understand how easy it is to add
context to an inline transformation.

![CleanShot 2024-10-18 at 22 41
00@2x](https://github.com/user-attachments/assets/c09c1d89-3df2-4079-9849-9de7ac63c003)

@as-cii @maxdeviant @rtfeldman could somebody update this to display the
actual correct key bindings and ship it. I have them hard coded for now.

Release Notes:

- Updated placeholder text with key bindings to focus context panel and
navigate history.

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2024-10-25 14:09:21 -04:00
Conrad Irwin
85bdd9329b Revert "Show invisibles in editor (#19298)" (#19752)
Closes: #19714

This reverts commit 6dcec47235.

Release Notes:

- (preview only) Fixes a crash when rendering invisibles
2024-10-25 11:59:22 -06:00
Kirill Bulatov
d40ea8fc81 Make macOS bundle script compatible with GNU sed (#19745)
Closes https://github.com/zed-industries/zed/issues/19742

Release Notes:

- N/A
2024-10-25 19:04:38 +03:00
Marshall Bowers
5f9a1482f1 assistant: Make /file emit events as they occur (#19743)
This PR updates the `/file` command to emit its `SlashCommandEvent`s in
a way that can actually be streamed.

Previously it was buffering up all of the events and then returning them
all at once.

Note that we still don't yet support streaming in the context editor on
`main`, so there won't be any visible changes just yet.

Release Notes:

- N/A
2024-10-25 11:02:27 -04:00
Thorsten Ball
5c2238c7a5 ssh remoting: Use matching versions of remote server binary (#19740)
This changes the download logic to not fetch the latest version, but to
fetch the version matching the current version of Zed.


Release Notes:

- Changed the update logic of the SSH remote server to not fetch the
latest version for a current channel, but to fetch the version matching
the current Zed version. If Zed is updated, the server is updated too.
If the server is newer than the Zed version an error will be displayed.
2024-10-25 16:27:36 +02:00
Piotr Osiewicz
5769065f27 project panel: Persist full filename when renaming auto-folded entries (#19728)
This fixes a debug-only panic when processing filenames. The underflow
that happens in Preview/Stable shouldn't cause any issues (other than
maybe unmarking an entry in the project panel).

/cc @notpeter

Closes #ISSUE

Release Notes:

- N/A
2024-10-25 13:47:01 +02:00
Thorsten Ball
0173479d18 ssh remoting: Lock file becomes stale if connection drops & no update if binary is running (#19724)
Release Notes:

- Changed the update process of the remote server binary to not attempt
an update if we can detect that the current binary is used by another
process.
- Changed the update process of the remote server binary to mark the
lock file as stale in case the SSH connection of the process that
created the lock file isn't open anymore.
2024-10-25 12:42:31 +02:00
Max Brunsfeld
08a3c54bac Allow editor blocks to replace ranges of text (#19531)
This PR adds the ability for editor blocks to replace lines of text, but
does not yet use that feature anywhere. We'll update assistant patches
to use replace blocks on another branch:
https://github.com/zed-industries/zed/tree/assistant-patch-replace-blocks

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Richard Feldman <richard@zed.dev>
Co-authored-by: Marshall Bowers <marshall@zed.dev>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
2024-10-25 12:29:25 +02:00
Piotr Osiewicz
3617873431 project panel: Fix interactions with auto-folded directories (#19723)
Closes https://github.com/zed-industries/zed/issues/19566

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-10-25 12:13:21 +02:00
Bennet Bo Fenner
6eb6788201 image viewer: Reuse existing tabs (#19717)
Co-authored-by: Kirill <kirill@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>

Fixes #9896

Release Notes:

- Fixed an issue where clicking on an image inside the project panel
would not re-use an existing image tab

Co-authored-by: Kirill <kirill@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-25 09:34:50 +02:00
Conrad Irwin
ebc3031fd9 Inline initialization (#19711)
This restores all the init behaviour into main again. This means we
never need to call init_ui (and so we can't call it more than once).

Release Notes:

- (Nightly only) fixes a panic when using the cli to open another file
in a running zed.
2024-10-24 20:43:40 -06:00
Danilo Leal
42a7402cc5 assistant: Use a labeled button for the slash command menu (#19703)
This should help a bit more the discoverability of the slash commands.

Release Notes:

- N/A
2024-10-24 19:37:42 -03:00
Danilo Leal
6cd5c9e32f assistant: Tweak the model selector design (#19704)
Exploring using the UI font for it, as it is more common for dropdowns
and popovers throughout the app. Feeling like it makes it lighter and
also shorter in width!

| Before | After |
|--------|--------|
| <img width="1296" alt="Screenshot 2024-10-24 at 16 39 04"
src="https://github.com/user-attachments/assets/0412f922-77a9-4d83-adf9-5632534d6c5b">
| <img width="1296" alt="Screenshot 2024-10-24 at 16 38 26"
src="https://github.com/user-attachments/assets/8bf52ba7-fda7-4437-b53e-903c282f2931">
|

Release Notes:

- N/A
2024-10-24 19:37:09 -03:00
Conrad Irwin
d45b830412 SSH connection pooling (#19692)
Co-Authored-By: Max <max@zed.dev>

Closes #ISSUE

Release Notes:

- SSH Remoting: Reuse connections across hosts

---------

Co-authored-by: Max <max@zed.dev>
2024-10-24 14:37:54 -06:00
Conrad Irwin
3a9c071e6e Fix partial downloads of ssh remote server (#19700)
Release Notes:

- SSH Remoting: fix a bug where inerrrupting ssh connecting could leave
your local binary cached in an invalid state
2024-10-24 14:37:02 -06:00
Marshall Bowers
ca861bb1bb ui: Fix swapped element background colors (#19701)
This PR fixes an issue introduced in
https://github.com/zed-industries/zed/pull/18768 where the element
backgrounds colors for `ElevationIndex::ElevatedSurface` and
`ElevationIndex::Surface` were swapped.

Release Notes:

- N/A
2024-10-24 16:34:45 -04:00
Kirill Bulatov
454d3dd52b Fix ssh project history (#19683)
Use `Fs` instead of `std::fs` and do entry existence checks better:
* first, check the worktree entry existence without any FS checks
* then, only for local cases, use `Fs` to check for abs_path existence
of items, in case those came from single-filed worktrees that got closed
and removed.

Remote entries do not get file existence checks, so might try opening
previously removed buffers for now.

Release Notes:

- N/A
2024-10-24 21:49:07 +03:00
Peter Tripp
3ec015b325 docs: Example theme_overrides for docstrings as italic (#19694) 2024-10-24 14:37:57 -04:00
Mikayla Maki
02718284ef Remove dev servers (#19638)
TODO:

- [ ] Check that workspace migration worked
- [ ] Add server migrations and make sure SeaORM files are in sync
(maybe?)

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-10-24 12:14:03 -06:00
Marshall Bowers
b5f816dde5 assistant: Add implementation for /delta argument completion (#19693)
This PR fixes a panic that could occur when trying to complete arguments
for the `/delta` slash command.

We were using `unimplemented!()` instead of providing a default no-op
implementation like we do for other slash commands that do not support
completing arguments.

Closes https://github.com/zed-industries/zed/issues/19686.

Release Notes:

- Fixed a panic that could occur when trying to complete arguments with
the `/delta` command.
2024-10-24 13:39:06 -04:00
Antonio Scandurra
499e1459eb Fix crash in collab when sending worktree updates (#19678)
This pull request does a couple of things:

- In 29c2df73e1, we introduced a safety
guard that prevents this crash from happening again in the future by
returning an error instead of panicking when the payload is too large.
- In 3e7a2e5c30, we introduced chunking
for updates coming from SSH servers (previously, we were sending the
whole changeset and initial set of paths in their entirety).
- In 122b5b4, we introduced a panic hook that sends panics to Axiom.

For posterity, this is how we figured out what the panic was:

```
kubectl logs current-pod-name --previous --namespace=production
```

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Kirill <kirill@zed.dev>
2024-10-24 15:57:24 +02:00
Danilo Leal
b5aea548a8 ssh: Capitalize error and connection strings (#19675)
Another tiny PR for the sake of consistency :)

Release Notes:

- N/A
2024-10-24 09:43:35 -03:00
Danilo Leal
3c6a505166 docs: Add tweaks to the Remote Development page (#19674)
Just making just we also add the other keybinding to open the Remote
Projects dialog and capitalize every "SSH" mention for consistency. Tiny
stuff!

Release Notes:

- N/A
2024-10-24 09:35:59 -03:00
Thorsten Ball
efc4d3efdf ssh remoting: Fix wrong working directory for SSH terminals (#19672)
Before this change, we would save the working directory *on the client*
of each shell that was running in a terminal.

While it's technically right, it's wrong in all of these cases where
`working_directory` was used:

- in inline assistant
- when resolving file paths in the terminal output
- when serializing the current working dir and deserializing it on
restart

Release Notes:

- Fixed terminals opened on remote hosts failing to deserialize with an
error message after restarting Zed.
2024-10-24 13:52:26 +02:00
Bennet Bo Fenner
4214ed927f project panel: Add indent guides (#18260)
See #12673



https://github.com/user-attachments/assets/94079afc-a851-4206-9c9b-4fad3542334e



TODO:
- [x] Make active indent guides work for autofolded directories
- [x] Figure out which theme colors to use
- [x] Fix horizontal scrolling
- [x] Make indent guides easier to click
- [x] Fix selected background flashing when hovering over entry/indent
guide
- [x] Docs

Release Notes:

- Added indent guides to the project panel
2024-10-24 13:07:20 +02:00
Zhang
e040b200bc project_panel: Make up/down in file rename editor not select items (#19670)
Closes #19017 

Release Notes:

- Fixed project panel bug when renaming files where up/down keys could
select other files.
2024-10-24 12:15:42 +02:00
Thorsten Ball
1dba50f42f ssh remoting: Fix version check (#19668)
This snuck in when Bennet and I were debugging why our connection to the
SSH host would break. We suspected that somewhere something was logging
to STDOUT and, I guess, we changed all `println!` to `eprintln!`.

Now, two weeks later, I'm sitting here, wondering why the version check
doesn't work anymore. The server always reports a version of `""`.

Turns out we take the command's STDOUT and not STDERR, which is correct.

But it also turns out we started to print the version to STDERR, which
breaks the version check.

One-character bug & one-character fix.

Release Notes:

- N/A
2024-10-24 11:37:32 +02:00
renovate[bot]
0ffc92ab65 Update actions/checkout digest to 11bd719 (#19636)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) |
action | digest | `eef6144` -> `11bd719` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 21:57:06 -04:00
Marshall Bowers
d30361537e assistant: Update SlashCommand trait with streaming return type (#19652)
This PR updates the `SlashCommand` trait to use a streaming return type.

This change is just at the trait layer. The goal here is to decouple
changing the trait's API while preserving behavior on either side.

The `SlashCommandOutput` type now has two methods for converting two and
from a stream to use in cases where we're not yet doing streaming.

On the `SlashCommand` implementer side, the implements can call
`to_event_stream` to produce a stream of events based off the
`SlashCommandOutput`.

On the slash command consumer side we use
`SlashCommandOutput::from_event_stream` to convert a stream of events
back into a `SlashCommandOutput`.

The `/file` slash command has been updated to emit `SlashCommandEvent`s
directly in order for it to work properly.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-10-23 21:26:50 -04:00
renovate[bot]
510c71d41b Pin crate-ci/typos action to 8e6a428 (#19635)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) | action
| pinDigest | -> `8e6a428` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 19:00:28 -04:00
renovate[bot]
013d2d52fd Update Rust crate anyhow to v1.0.91 (#19640)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [anyhow](https://redirect.github.com/dtolnay/anyhow) |
workspace.dependencies | patch | `1.0.89` -> `1.0.91` |

---

### Release Notes

<details>
<summary>dtolnay/anyhow (anyhow)</summary>

###
[`v1.0.91`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.91)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.90...1.0.91)

- Ensure OUT_DIR is left with deterministic contents after build script
execution
([#&#8203;388](https://redirect.github.com/dtolnay/anyhow/issues/388))

###
[`v1.0.90`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.90)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.89...1.0.90)

-   Documentation improvements

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-24 00:45:58 +03:00
Conrad Irwin
eee91f3f1b docs: Update SSH docs (#19339)
Update of the SSH remoting docs

Release Notes:

- N/A
2024-10-23 14:25:27 -06:00
Kirill Bulatov
e87d5e145d Use zstd without dynamic linking due to musl usage (#19627)
Due to leaning towards `musl` builds, unit features for `zstd` and link
it statically too for Zed.


bfe1e34f59/zstd-safe/zstd-sys/build.rs (L260)
shows that `ZSTD_SYS_USE_PKG_CONFIG` env var can be used to return this
behavior.

Release Notes:

- N/A
2024-10-23 22:19:06 +03:00
Peter Tripp
291af664e1 Switch to Anthropic -latest tags (#19615)
- Closes: https://github.com/zed-industries/zed/issues/19609

Switches us to using `-latest` tags with Anthropic models instead of
pinning to a specific date version.
See: [Anthropic Model
Docs](https://docs.anthropic.com/en/docs/about-claude/models)

This is a no-op for:
- Claude 3 Opus (`claude-3-opus-20240229`)
- Claude 3 Sonnet (`claude-3-sonnet-20240229`)
- Claude 3 Haiku (`claude-3-haiku-20240307`)

For Claude 3.5 Sonnet this will update us from
`claude-3-5-sonnet-20240620` to `claude-3-5-sonnet-20241022`. We will
also pickup any subsequent model updates automatically when Anthropic
updates the `latest` tag.

This matches the behavior for OpenAI where use `gpt-4o` as the
model_name and not `gpt-4o-2024-08-06`.
2024-10-23 15:13:52 -04:00
Marshall Bowers
9c0dba4ce1 Add a SlashCommandResult type alias (#19633)
This PR adds a new `SlashCommandResult` type alias.

We're going to be changing what slash commands can return in order to
support streaming, so having this type alias in place will make that
switch a bit more neat.

Release Notes:

- N/A
2024-10-23 14:32:43 -04:00
Peter Tripp
8bfd27b00b docs: Improve Markdown trailing whitespace section (#19630) 2024-10-23 13:23:01 -04:00
Joseph T. Lyons
c6f08dea89 v0.160.x dev 2024-10-23 13:12:28 -04:00
Piotr Osiewicz
9dfe4a30bb languages: Do not expose unnecessary captures from tasks (#19625)
This tackles an issue with us exposing unnecessary env variables in
environment which are not actually needed for tasks themselves (and may
have little utility), yet come into the way of ssh remoting.

/cc @ConradIrwin 

Release Notes:

- N/A
2024-10-23 18:54:08 +02:00
Marshall Bowers
69b12f4e33 semantic_index: Disable embeddings index for non-staff (#19618)
This PR disables the embeddings index for non-staff users.

Release Notes:

- N/A
2024-10-23 12:34:51 -04:00
Danilo Leal
c3860804ff ssh: Ensure long server names (and nicknames) truncate (#19621)
Just polishing the UI a bit more. One drawback of this, though, is that
if you _do_ have a big nickname or server name, with this current
solution, you won't be able to see it. Ideally, we should be able to
hover over it and see it in a tooltip, but the `div` still doesn't
support that out of the box.

| Main modal | Modal header |
|--------|--------|
| <img width="1136" alt="Screenshot 2024-10-23 at 12 49 18"
src="https://github.com/user-attachments/assets/ed5f0222-faa1-49bd-b249-2f22497566d8">
| <img width="1136" alt="Screenshot 2024-10-23 at 12 49 23"
src="https://github.com/user-attachments/assets/5a464b12-99e8-4934-aa6a-c9c4c40ea4d4">
|

Release Notes:

- N/A
2024-10-23 13:31:03 -03:00
Thorsten Ball
6a0c19fcf9 ssh remoting: Log server version check result (#19622)
Release Notes:

- N/A
2024-10-23 18:30:48 +02:00
Tom Zaspel
622c266160 docs: Add documentation for auto_install_extensions setting (#19559)
Improved: Documenation for (un)installing extensions automatically.

Signed-off-by: Tom Zaspel <tom@zaspel.it>
Co-authored-by: Peter Tripp <peter@zed.dev>
2024-10-23 11:57:13 -04:00
Thorsten Ball
dc4396b79c ssh remoting: Fix overflowing host in titlebar (#19619)
Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2024-10-23 18:37:30 +03:00
Kirill Bulatov
1a59b6413b Fill the rest of the prompt data when sending to the client (#19616)
Follow-up of https://github.com/zed-industries/zed/pull/19587

Release Notes:

- N/A
2024-10-23 18:33:57 +03:00
Jamie Bowers
992155c60c Update example node settings in default.json to use the correct key for node path (#19607)
Hey team!

I was investigating the new node settings added in #18172, and when
trying to set the node path I noticed that the example settings in
`default.json` use the wrong key for the `node_path` - it should be
`path` instead.

See
[here](19eebcd349/crates/zed/src/main.rs (L488))
for where the setting is used.

Release Notes:

- N/A
2024-10-23 18:33:31 +03:00
Thorsten Ball
e633f62eaf ssh remoting: Fix opening settings file (#19614)
We have to do `workspace.with_local_workspace`, otherwise we'll try to
open the settings on the remote host.


Release Notes:

- N/A
2024-10-23 16:56:39 +02:00
Kirill Bulatov
b85af0e533 Fall back to handling the abs path for external worktree entries (#19612)
Certain files like Rust stdlib ones can be opened by cmd-clicking on
terminal, editor contents, etc.

Those files will not belong to the current worktree, so a fake worktree,
with a single file, invisible (i.e. its dir(s) will not be shown in the
UI such as project panel), will be created on the file opening.

When the file is closed, the worktree is closed and removed along the
way, so those worktrees are considered ephemeral and their ids are not
stored in the database.
This causes issues on reopening such files when they are closed. 

The PR makes Zed to fall back to opening the file by abs path when it's
not in the project metadata, but has the abs path stored in history or
in the opened items DB data.

Release Notes:

- Handle external worktree entries [re]open better
2024-10-23 17:34:23 +03:00
Thorsten Ball
bce1b7a10a ssh remoting: Add setting to download binary on host (#19606)
This adds the following optional setting:

```json
{
  "remote_server": {
    "download_on_host": false
  }
}
```
Right now, it's **off by default** because I haven't tested it enough.

Release Notes:

- N/A
2024-10-23 16:06:16 +02:00
Peter Tripp
c292bdd2ca docs: Improve language server configuration dotted/nested notation example (#19608) 2024-10-23 09:41:27 -04:00
Danilo Leal
1cb9f64917 ssh: Clean up bits of the main modal UI (#19604)
This PR main relevant change is removing the logic we had inserted for
keyboard nav scroll as that was unreliable; we need to figure out a
better solution still. I'm also removing the visible on hover behavior
for the scrollbar as that was making us lose the click and drag feature
the component has. Lastly, I added a bit of right-margin in the delete
icon button so that's not too crammed with the scrollbar.

Release Notes:

- N/A
2024-10-23 09:49:44 -03:00
Piotr Osiewicz
1bded42b2a ssh: Dismiss file picker when new window is opened (#19600)
Closes #ISSUE

Release Notes:

- N/A
2024-10-23 13:57:50 +02:00
Thorsten Ball
7f64f0454d ssh remoting: Ensure only single instance can upload binary (#19597)
This creates a `<binary_path>.lock` file and checks for it whenever
uploading a binary.

Parameters:
- Wait for other instance to finish: 10m
- Mark lockfile as stale and ignore it after: 10m
- When waiting on another process, check every 5seconds

We can tweak all of them.

Ideally we'd have a value in the lockfile, that lets us detect whether
the other process is still there, but I haven't found something stable
yet:

- We don't have a stable PID on the server side when we run multiple
commands
- The `ControlPath` is on the client side

Release Notes:

- N/A
2024-10-23 13:18:27 +02:00
Vitaly Slobodin
375bc88f95 lsp_log: Add server capabilities view (#19448)
Hello, this PR adds a new view to the LSP servers menu for
displaying an LSP server capabilities.

When I work on LSP stuff, quite often I need to check what capabilities
an LSP server has. Currently there is no built-in way for checking that
in Zed, and I have to use [`LSP
DevTools`](https://lsp-devtools.readthedocs.io) project. LSP DevTools
works OK but it works as a proxy between the client and the server, so
setting it up is not that easy in Zed. Zed already has many goodies for
LSP like tracing and RPC messages, so I thought that a simple view with
server capabilities could be useful too. Thanks!

## Some screenshots:

### Ruby LSP

![CleanShot 2024-10-19 at 07 44
38@2x](https://github.com/user-attachments/assets/22c97b49-c539-4e39-a5f1-1c926347abca)


### New menu entry:

![CleanShot 2024-10-19 at 07 45
08@2x](https://github.com/user-attachments/assets/d3903d6e-c09a-40e2-b042-1abde490987d)


Release Notes:

- N/A
2024-10-23 12:53:49 +02:00
wannacu
d53a86b01d project_panel: Fix the confusing display when files or directories have the same name in a case-insensitive comparison (#19211)
- Closes #18851

Release Notes:

- Fixed directory name comparison on Linux not considering the case
([#18851](https://github.com/zed-industries/zed/issues/18851))
2024-10-23 10:37:57 +03:00
Kevin Wang
6c7e79eff6 Cap the size of the Supermaven states buffer (#19246)
Caps the size of the Supermaven states buffer to 1000 elements.
Previously, the buffer would grow unbounded so for long sessions the
number of states that the Supermaven autocomplete provider maintains can
be quite large. In practice, states that are sufficiently old are so
unlikely to be visited again that we can regenerate the completion.
Thus, we can cap the buffer to 1000 elements.

Release Notes:

- N/A
2024-10-23 10:36:14 +03:00
Mikayla Maki
d0bc84eb33 Fix remoting things (#19587)
- Fixes modal closing when using the remote modal folder 
- Fixes a bug with local terminals where they could open in / instead of
~
- Fixes a bug where SSH connections would continue running after their
window is closed
- Hides SSH Terminal process details from Zed UI
- Implement `cmd-o` for remote projects
- Implement LanguageServerPromptRequest for remote LSPs

Release Notes:

- N/A
2024-10-23 00:14:43 -07:00
Joseph T. Lyons
fabc14355c Update telemetry docs to not point to specific lines of code (#19583)
The old links pointed to specific lines that were no longer the correct
lines. Let's skip pointing the reader of the docs to a specific line.

Release Notes:

- N/A
2024-10-23 00:36:43 -04:00
Conrad Irwin
07e086b41e Don't upload local settings to ssh remotes (#19577)
Closes: #18618

Release Notes:

- (breaking) SSH Remoting: stop uploading local settings to the remote.
2024-10-22 20:11:05 -06:00
Mikayla Maki
48674ec54c Add paths to connection headers (#19579)
Closes #ISSUE

<img width="562" alt="Screenshot 2024-10-22 at 3 21 22 PM"
src="https://github.com/user-attachments/assets/51afd8e8-b635-4d69-9463-2ccdaabeb955">

<img width="844" alt="Screenshot 2024-10-22 at 3 22 35 PM"
src="https://github.com/user-attachments/assets/ae57c0f8-396c-485d-b5e2-14558da98a18">

Release Notes:

- N/A

---------

Co-authored-by: Trace <violet.white.batt@gmail.com>
2024-10-22 16:50:33 -07:00
Finn Evers
fc7874e64e Fix GitHub link for upstream markdown grammer (#19580)
This PR removes an extra slash from the Github link to the upstream
Tree-sitter markdown grammer introduced in #19570 in the cargo-files.

Release Notes:

- N/A
2024-10-22 19:06:34 -04:00
Marshall Bowers
dcb0da0a7d collab: Return free tier usage from GET /billing/monthly_spend (#19578)
This PR updates the `GET /billing/monthly_spend` endpoint to also return
information about the free tier usage.

Release Notes:

- N/A
2024-10-22 18:06:54 -04:00
Conrad Irwin
a9f48bd9d1 Try to build with musl on CI (#19571)
- Closes #19092 
- Closes #15411

Release Notes:

- SSH Remoting: Build with musl (adds support for machines with old, or
no glibc)
2024-10-22 16:00:00 -06:00
Mikayla Maki
33197608ed Make closing the SSH modal equivalent to cancelling the SSH connection task (#19568)
TODO: 
- [x] Fix focus not being on the modal initially

Release Notes:

- N/A

---------

Co-authored-by: Trace <violet.white.batt@gmail.com>
2024-10-22 14:50:55 -07:00
Kirill Bulatov
f951410ef0 Use upstream tree-sitter-markdown (#19570)
After
https://github.com/tree-sitter-grammars/tree-sitter-markdown/pull/163 ,
no need to use zed-industries fork for tree-sitter-markdown

Release Notes:

- N/A
2024-10-23 00:41:01 +03:00
Peter Tripp
47ade2f9f9 Check for rsync before downloading updates (#19392) 2024-10-22 17:12:26 -04:00
Peter Tripp
263e143d1b macos: Add services menu (#16959) 2024-10-22 17:08:19 -04:00
Marshall Bowers
21a44d74bd collab: Prevent users from ending up with multiple active billing subscriptions (#19574)
This PR adds some safeguards to ensure that users do not end up with
multiple active billing subscriptions.

We now do the following:

1. When initiating a checkout, we first make sure the user does not
already have an active subscription.
2. When creating subscriptions in response to Stripe events, we ensure
that we don't already have an active subscription.

Release Notes:

- N/A
2024-10-22 17:01:59 -04:00
Derek Briggs
efd485cbb8 Add file icons for Zig, Julia, SCSS, HCL, Nix, and Roc (#19529)
Release Notes:

- Added file icons for Zig, Julia, SCSS, HCL, Nix, and Roc files.

<img width="733" alt="image"
src="https://github.com/user-attachments/assets/37602be7-173f-40d1-8177-3d35b11d4208">

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-22 16:03:58 -04:00
Kirill Bulatov
7a6550c1d1 Do not allow drag and drop of FS entries into the remote projects (#19565)
Release Notes:

- N/A
2024-10-22 22:34:54 +03:00
Danilo Leal
23ad470daf ssh: Add UI refinements to the remote modals (#19558)
This PR polishes spacing, borders, header design, font size, etc.

Release Notes:

- N/A
2024-10-22 16:33:59 -03:00
Conrad Irwin
6dcec47235 Show invisibles in editor (#19298)
Release Notes:

- Added highlighting for "invisible" unicode characters

Closes #16310

---------

Co-authored-by: dovakin0007 <dovakin0007@gmail.com>
Co-authored-by: dovakin0007 <73059450+dovakin0007@users.noreply.github.com>
2024-10-22 13:23:13 -06:00
Conrad Irwin
e93d62680d Improve disconnected modal for SSH (#19567)
Closes #ISSUE

Release Notes:

- SSH Remoting: made reconnect modal more robust
2024-10-22 13:22:27 -06:00
Marshall Bowers
5dbf68ddc4 editor: Save base buffers when applying changes from their branches (#19562)
This PR makes it so that when we apply changes within a branch
buffer—currently just the edits buffer—we save the underlying buffer.

This also fixes an issue where new files created via edits were not
properly flushed to disk.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-10-22 14:10:11 -04:00
Joseph T. Lyons
d8d84bf5d4 Tell users they can close a stale issue if they can't repro (#19563)
A user commented on this issue to let us know they couldn't reproduce,
which would keep it open for another 6 months. Best we can do is tell
them what to do if they can't repro, which is to close the issue
themselves.

- https://github.com/zed-industries/zed/issues/10671

Release Notes:

- N/A
2024-10-22 13:46:21 -04:00
Mikayla Maki
6f6893a93a Fix reversed prompt in #19533 (#19538)
Release Notes:

- N/A
2024-10-22 10:33:42 -07:00
Joseph T. Lyons
7ae25d10c8 Fix comment when marking issues as stale (#19561)
Oops

<img width="928" alt="image"
src="https://github.com/user-attachments/assets/a16ea5cd-c02d-4add-9b58-853de9aa5ba7">

Release Notes:

- N/A
2024-10-22 13:29:48 -04:00
Thorsten Ball
d80683f2bf ssh remoting: Fix heartbeat timer and exit conditions (#19557)
This contains a bunch of smallish but nasty fixes:

- Heartbeat timer was never reset after first heartbeat
- Use same return value when stderr is closed as when stdout is closed
- Always check proxy process status since it should also be done when we
get to this point (either it died and our task stopped, or our task
stopped and we dropped the process handle and it was killed on drop)
- make error messages less wrongly-specific


Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-22 18:45:56 +02:00
Adam Wolff
ab98d4889b assistant: Add tracking fields to Codegen for alternative assists (#19467)
Added some tracking fields to Codegen and CodegenAlternatives in order
to collect data for experimentation in a fork.

Release Notes:

- N/A
2024-10-22 12:45:01 -04:00
Richard Feldman
ce11ca9d49 Don't suggest assistant code actions if it's disabled (#19553)
Closes #19155

Release Notes:

- Fixed code action for "Fix with assistant" appearing when assistant
was disabled.
2024-10-22 12:36:36 -04:00
Kirill Bulatov
edda149d75 Do not remove worktrees after the headless server removal (#19556)
Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-10-22 19:27:50 +03:00
renovate[bot]
291ca2c32c Update Rust crate wasmtime-wasi to v24.0.1 (#19338)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [wasmtime-wasi](https://redirect.github.com/bytecodealliance/wasmtime)
| workspace.dependencies | patch | `24.0.0` -> `24.0.1` |

---

### Release Notes

<details>
<summary>bytecodealliance/wasmtime (wasmtime-wasi)</summary>

###
[`v24.0.1`](https://redirect.github.com/bytecodealliance/wasmtime/releases/tag/v24.0.1)

[Compare
Source](https://redirect.github.com/bytecodealliance/wasmtime/compare/v24.0.0...v24.0.1)

#### 24.0.1

Released 2024-10-09.

##### Fixed

- Fix a runtime crash when combining tail-calls with host imports that
capture a
    stack trace or trap.

[GHSA-q8hx-mm92-4wvg](https://redirect.github.com/bytecodealliance/wasmtime/security/advisories/GHSA-q8hx-mm92-4wvg)

- Fix a race condition could lead to WebAssembly control-flow integrity
and type
    safety violations.

[GHSA-7qmx-3fpx-r45m](https://redirect.github.com/bytecodealliance/wasmtime/security/advisories/GHSA-7qmx-3fpx-r45m)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 11:39:35 -04:00
Danilo Leal
3ba2af289b ssh: Expose server address in the title bar (#19549)
This PR exposes the server address (or the nickname, if there is one) on
the title bar and in all modals that have the SSH header. The title bar
tooltip meta description still shows the original server address
(regardless of a nickname existing in this case), though.

<img width="600" alt="Screenshot 2024-10-22 at 10 58 36"
src="https://github.com/user-attachments/assets/64a94d9f-798b-44a4-9dee-6056886535bb">

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-10-22 12:39:00 -03:00
David Soria Parra
d8d8c908ed context_servers: Update protocol (#19547)
We sadly have to change the underlying protocol once again. This will
likely be the last change to the core protocol without correctly
handling older versions. From here on out, we want to get better with
version handling. To do so, we introduce the notion of a string protocol
version to be explicit of when the underlying protocol last changed.

The change also changes the return values of prompts. For now we only
allow User messages from servers to match the current behaviour. We will
change this once #19222 lands which will allow slash commands to insert
user and assistant messages.

Release Notes:

- N/A
2024-10-22 11:19:32 -04:00
Adam Wolff
680b3dd80b Refine AI context summary prompt (#19530)
Release Notes:

- Improved prompt for generating context editor summaries.
2024-10-22 11:02:04 -04:00
Peter Tripp
270e13bb9a docs: Document upstream JSONC Prettier issues (#19552) 2024-10-22 10:43:21 -04:00
Thorsten Ball
b3aa7055e4 ssh remoting: Daemonize server process properly by forking and closing stdio (#19544)
This fixes the `ssh` proxy process not being notified when the proxy
process dies. Turns out that the server would have stdout/stderr/stdin
connected to the grand-parent ssh process connected to it and as long as
the server kept running (even once it was daemonized into the
background) the grand-parent ssh process wouldn't exit.

That in turn meant that the Zed client wasn't notified when the proxy
process died.

Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-22 16:36:40 +02:00
Joseph T. Lyons
f16461d7d0 Fix Discord link (#19551)
We can't seem to generate invite links that dumps a user directly into a
specific channel, so just adding a general invite link and then pointing
them to the Windows channel name.

Release Notes:

- N/A
2024-10-22 10:36:05 -04:00
Danilo Leal
970f8db5c4 ssh: Don't erase the connection input on escape key (#19550)
This PR changes the behavior of pressing the `Esc` key on the connection
input. Now, if you hit it, the address inserted into the input won't be
erased. Effectively, escape now only cancels the connection process
instead of doing both (clearing the input _and_ cancelling the
connection).

Release Notes:

- N/A

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-10-22 11:28:39 -03:00
Piotr Osiewicz
bc9086c9af ssh: Fix file picker not getting focus when it's opened for the first time (#19546)
Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2024-10-22 16:11:59 +02:00
Thorsten Ball
a367c6de6e ssh remote: Only send a single FlushBufferedMessages (#19541)
Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-22 12:04:15 +02:00
Thorsten Ball
27d1a566d0 ssh remoting: Emit Disconnected when reconnected exhausted (#19540)
Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-22 11:35:27 +02:00
Conrad Irwin
4f52077d97 Better error handling for SSH (#19533)
Before this change we sometimes showed errors inline, sometimes in
alerts.
Sometimes we closed the window, someimtes we didn't.

Now they always show as prompts and we never close windows.

Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- SSH Remoting: Improve error handling
2024-10-21 22:17:42 -07:00
Conrad Irwin
6e485453d0 Rename dev servers to remote projects in UI (#19527)
Release Notes:

- N/A

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-21 16:50:25 -06:00
Conrad Irwin
9bae93cd39 SSH Remoting: Fix yes/no/fingerprint prompt (#19526)
Release Notes:

- SSH Remoting: fix SSH fingerprint prompt

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-21 15:28:22 -06:00
Marshall Bowers
1a4b253ee5 collab: Add support for a custom monthly allowance for LLM usage (#19525)
This PR adds support for setting a monthly LLM usage allowance for
certain users.

Release Notes:

- N/A
2024-10-21 17:12:33 -04:00
Vladimir Varankin
89f6b65ee6 docs: Add missing link to jsonnet.md in the summary (#19522)
This is a fixup for #19410.

Apparently, `mdbook` requires a reference to the document from the
`SUMMARY.md`. This fixes the 404
(https://zed.dev/docs/languages/jsonnet.html) and also adds the Jsonnet
to the docs' navigation.

Release Notes:

- N/A
2024-10-21 15:56:56 -04:00
Bruno Calza
a2c6b4ad2f Fix empty keystroke with simulated IME (#19414)
Closes #19181

When the keystroke was empty ("") the `ime_key` was converted from
`None` to `Some("")` when `with_simulated_ime` was called. That was
leading to not intentional behavior when an empty keystroke was combined
with `shift-up` in a keybinding `["workspace::SendKeystrokes", "shift-up
"]`.

By adding a `key.is_empty()` we make sure the `ime_key` keeps as `None`.

This was manually tested. 

Release Notes:

- Fixed empty keystroke with simulated ime

Signed-off-by: Bruno Calza <brunoangelicalza@gmail.com>
2024-10-21 13:44:05 -06:00
Conrad Irwin
6b7d85b769 SSH Remoting: Fix Save As (#19517)
Co-Authored-By: Mikayla <mikayla@zed.dev>

Closes #ISSUE

Release Notes:

- SSH Remoting: Fix SaveAs to pick the file on the remote

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-21 13:41:48 -06:00
Max Brunsfeld
cb3eb75712 Fix block-wise autoindent when editing adjacent ranges (#19521)
This fixes problems where auto-indent wasn't working correctly for
assistant edits.

Release Notes:

- Fixed a bug where auto-indent didn't work correctly when pasting with
multiple cursors on adjacent lines

Co-authored-by: Marshall <marshall@zed.dev>
2024-10-21 12:41:41 -07:00
Mikayla Maki
bae85d858e SSH Remoting: Fix reload/save race (#19519)
Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-10-21 11:23:19 -07:00
Marshall Bowers
755fd695f5 editor: Move hunk controls to the right (#19515)
This PR moves the hunk controls over to the right so we can see how they
feel over there.

Git:

<img width="1068" alt="Screenshot 2024-10-21 at 10 27 34 AM"
src="https://github.com/user-attachments/assets/71556e22-024a-4bdf-8a99-fe28430b9155">

Live diffs:

<img width="1060" alt="Screenshot 2024-10-21 at 10 27 28 AM"
src="https://github.com/user-attachments/assets/681ff409-dc55-4b63-87d7-7e39016417d2">


Release Notes:

- Moved hunk controls to the right of the header.
2024-10-21 10:45:54 -04:00
Daniel Eichman
74e25c11f1 Fix incorrect checkbox placement in Markdown preview (#19383)
- Closes #12515

Before fix:
<img width="1506" alt="Screenshot 2024-10-17 at 09 50 19"
src="https://github.com/user-attachments/assets/250f50cb-0119-4b96-bc9b-7258aa83247c">

After fix:
<img width="1027" alt="Screenshot 2024-10-17 at 09 52 36"
src="https://github.com/user-attachments/assets/c2eb7e4a-3c03-466c-b215-7fcc22eed024">

Testing:
- Manual testing 
- Added unit test

Test results, these tests fail on the main branch for my setup as well,
I have docker running but still had some failures:
```
failures:
    tests::integration_tests::test_context_collaboration_with_reconnect
    tests::integration_tests::test_formatting_buffer
    tests::integration_tests::test_fs_operations
    tests::integration_tests::test_git_branch_name
    tests::integration_tests::test_git_diff_base_change
    tests::integration_tests::test_git_status_sync
    tests::integration_tests::test_join_after_restart
    tests::integration_tests::test_join_call_after_screen_was_shared
    tests::integration_tests::test_joining_channels_and_calling_multiple_users_simultaneously
    tests::integration_tests::test_leaving_project
    tests::integration_tests::test_leaving_worktree_while_opening_buffer
    tests::integration_tests::test_local_settings
    tests::integration_tests::test_lsp_hover
    tests::integration_tests::test_mute_deafen
    tests::integration_tests::test_open_buffer_while_getting_definition_pointing_to_it
    tests::integration_tests::test_pane_split_left
    tests::integration_tests::test_prettier_formatting_buffer
    tests::integration_tests::test_preview_tabs
    tests::integration_tests::test_project_reconnect
    tests::integration_tests::test_project_search
    tests::integration_tests::test_project_symbols
    tests::integration_tests::test_propagate_saves_and_fs_changes
    tests::integration_tests::test_references
    tests::integration_tests::test_reloading_buffer_manually
    tests::integration_tests::test_right_click_menu_behind_collab_panel
    tests::integration_tests::test_room_location
    tests::integration_tests::test_room_uniqueness
    tests::integration_tests::test_server_restarts
    tests::integration_tests::test_unshare_project
    tests::notification_tests::test_notifications
    tests::random_project_collaboration_tests::test_random_project_collaboration
    tests::remote_editing_collaboration_tests::test_sharing_an_ssh_remote_project

test result: FAILED. 156 passed; 32 failed; 0 ignored; 0 measured; 0 filtered out; finished in 100.98s
```
Comments:
I do not have a ton of rust knowledge, so very open to feedback. TYSM

Release Notes:

- Fix Incorrect checkbox placement in Markdown preview

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-10-21 15:57:49 +02:00
Michael Sloan
b355a6f449 Fix markdown preview handling of empty list items (#19449)
Before this change, `parse_block` was consuming events that it doesn't
handle. This was fine in its use in `parse_document`, but in its use in
`parse_list` this broke when there is an empty list item, causing it to
consume list end tags / list item starts / etc.

Release Notes:

- Fixed markdown preview rendering of empty list items.
2024-10-21 15:13:26 +02:00
reslear
6341ad2f7a docs: Correct link to Vue extension (#19508)
Closes -

Release Notes:

- N/A
2024-10-21 08:12:10 -04:00
Kirill Bulatov
d3cb08bf35 Support .editorconfig (#19455)
Closes https://github.com/zed-industries/zed/issues/8534
Supersedes https://github.com/zed-industries/zed/pull/16349

Potential concerns:
* we do not follow up to the `/` when looking for `.editorconfig`, only
up to the worktree root.
Seems fine for most of the cases, and the rest should be solved
generically later, as the same issue exists for settings.json
* `fn language` in `AllLanguageSettings` is very hot, called very
frequently during rendering. We accumulate and parse all `.editorconfig`
file contents beforehand, but have to go over globs and match these
against the path given + merge the properties still.
This does not seem to be very bad, but needs more testing and
potentially some extra caching.


Release Notes:

- Added .editorconfig support

---------

Co-authored-by: Ulysse Buonomo <buonomo.ulysse@gmail.com>
2024-10-21 13:05:30 +03:00
renovate[bot]
d95a4f8671 Update swatinem/rust-cache digest to 82a92a6 (#19318)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [swatinem/rust-cache](https://redirect.github.com/swatinem/rust-cache)
| action | digest | `23bce25` -> `82a92a6` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 11:10:45 +03:00
Alvaro Gaona
44dc693d30 docs: Update C# and C++ configuration pages (#19500)
- Update C# configuration page.
- Fix typo in C++ configuration page.

Release Notes:

- N/A
2024-10-21 11:07:12 +03:00
Conrad Irwin
92c29be74c SSH Remoting: Fix reconnects (#19485)
Before this change messages could be lost on reconnect, now they will
not be.

Release Notes:

- SSH Remoting: make reconnects smoother

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-10-19 23:14:19 -06:00
Kirill Bulatov
1ae30f5813 Show project panel symlink icons for remote clients (#19464) 2024-10-19 19:44:47 +03:00
Antonio Scandurra
e8207288e5 Refold updated patch only if the patch was already folded (#19462)
Release Notes:

- N/A
2024-10-19 17:10:50 +02:00
Nathan Sobo
781fff220c Fix merging of an update of a symbol with an insert_before operation before the same symbol (#19450)
When we insert before some text and then update that same text, we need
to preserve and concatenate the new text associated with both
operations.

Release Notes:

- N/A
2024-10-19 08:36:21 -06:00
Max Brunsfeld
d209eab058 Combine excerpt footer and header into a single block (#19441)
This simplifies rendering of excerpt headers and footers, and removes
the need to store a `BlockDisposition` on these boundary blocks. It's a
step toward implementing "replace blocks", which we want to use in the
assistant panel.

We've also cleaned up the way heights are specified for headers and
footers and fixed some visual asymmetries between the "expand upward"
and "expand downward" buttons.

Release Notes:

- N/A

---------

Co-authored-by: Richard <richard@zed.dev>
2024-10-18 17:58:07 -07:00
Vitaly Slobodin
3e0c5c10b7 lsp: Handle unregistration "textDocument/rename" from a server (#19427)
Hi. While working on https://github.com/zed-industries/zed/pull/19230 I
noticed that some servers send a request to unregistered the
`textDocument/rename` capability. I thought it would be good to handle
that message in Zed:

```plaintext
[2024-10-18T21:25:07+02:00 WARN  project::lsp_store] unhandled capability unregistration: Unregistration { id: "biome_rename", method: "textDocument/rename" }
```

So this pull request implements that. Thanks.

Release Notes:

- N/A
2024-10-19 00:52:17 +02:00
Mikayla Maki
8a912726d7 Fix flakey SSH connection (#19439)
Fixes a bug due to the `select!` macro tossing futures that had
partially read messages, causing us to desync our message reading with
the input stream.

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: conrad <conrad@zed.dev>
2024-10-18 15:41:43 -07:00
Marshall Bowers
30e081b3f7 elixir: Bump to v0.1.1 (#19437)
This PR bumps the Elixir extension to v0.1.1.

Changes:

- https://github.com/zed-industries/zed/pull/19135

Release Notes:

- N/A
2024-10-18 18:31:08 -04:00
Conrad Irwin
a5492b3ea6 Revert "SSH reconnect reliability (#19398)" (#19440)
This reverts commit 98ecb43b2d.

Tests fail on main?!

Closes #ISSUE

Release Notes:

- N/A
2024-10-18 15:08:56 -07:00
Marshall Bowers
47380001cc remote: Fix formatting (#19438)
This PR fixes some formatting issues from #19398 that slipped past CI,
somehow.

Release Notes:

- N/A
2024-10-18 17:31:41 -04:00
Conrad Irwin
98ecb43b2d SSH reconnect reliability (#19398)
Release Notes:

- SSH Remoting: Fix message reliability across restarts

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-10-18 15:28:08 -06:00
Valentine Briese
be474a6d6f docs: Update info on JDTLS install for Java (#19436) 2024-10-18 17:12:34 -04:00
Marshall Bowers
b44bed0115 collab: Unconditionally execute billing checks (#19432)
This PR removes the conditional checks around the billing-related
enforcement for LLM completions.

These were just in place to prevent executing any billing code before we
had rolled it out. Now that it is rolled out, we don't need this
conditional execution anymore.

Release Notes:

- N/A
2024-10-18 15:55:28 -04:00
Joseph T. Lyons
11a82e3347 Do not run CI on changes to community action config files (#19430)
Release Notes:

- N/A
2024-10-18 15:36:20 -04:00
Joseph T. Lyons
be81e29b0f Prefer users to open new issues if closed stale issue is valid (#19428)
I no longer want to have to keep my ears and eyes open for GitHub
notifications that relate to someone requesting a closed stale issue be
reopened. As a community maintainer, I can get hundreds or even
thousands of notifications a week, and a lot of those are about activity
on closed issues. If everyone following an issue did not react fast
enough (7 days) to keep an issue flagged as `stale` open, let's instruct
them to open new issues, so we are forced to see it during next triage.

Release Notes:

- N/A
2024-10-18 15:04:26 -04:00
Valentine Briese
6a463be1ae docs: Direct Java extension users to JDTLS initialization options (#19401)
Continuation of #19390
2024-10-18 15:02:42 -04:00
Vladimir Varankin
64a6e9cafb docs: Outline Jsonnet language (#19410)
This PR adds a basic documentation about the Jsonnet language support.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-18 14:20:15 -04:00
Marshall Bowers
fa738ee5e1 vue: Extract to zed-extensions/vue repository (#19426)
This PR extracts the Vue extension to the
[zed-extensions/vue](https://github.com/zed-extensions/vue) repository.

Release Notes:

- N/A
2024-10-18 14:08:32 -04:00
Marshall Bowers
15449cdf30 svelte: Extract to zed-extensions/svelte repository (#19425)
This PR extracts the Svelte extension to the
[zed-extensions/svelte](https://github.com/zed-extensions/svelte)
repository.

Release Notes:

- N/A
2024-10-18 13:36:07 -04:00
Shish
2db9090a2f remote: Polish for connection progress & error dialogs (#19379)
Before/after:

![err1-before](https://github.com/user-attachments/assets/43d959b3-c9d9-45dd-938e-42d34ec1cfc5)

![err1-after](https://github.com/user-attachments/assets/311d53e0-752c-4eb8-9816-64b1970c228d)

Before/after (I feel like text-wrapping would be more useful than
text-ellipsis here, but I don't see any wrap function):

![err2-before](https://github.com/user-attachments/assets/1626cda9-bf06-43fe-9b7d-3ec64f4db08a)

![err2-after](https://github.com/user-attachments/assets/749a6950-1409-4e75-808e-a1a96dbfc87e)

Before/after:

![prog-before](https://github.com/user-attachments/assets/f5f5a171-db42-4797-bab0-ad71c750bb20)

![prog-after](https://github.com/user-attachments/assets/b52a7694-36f6-4f7a-8a90-ceb223f12ec1)

Release Notes:

- N/A
2024-10-18 11:09:52 -06:00
Thomas
34b8655bf6 Improve increment/decrement with leading zeros in vim mode (#18362)
- Closes: 18360

Release Notes:

- Added support for incrementing and decrementing numbers with leading
zeros
2024-10-18 11:03:34 -06:00
张小白
5b745a82e1 reqwest_client: Fix socks proxy settings (#19123)
Closes #19362

This pull request includes several updates to the `reqwest_client` crate
and its dependencies. The most important changes involve adding support
for SOCKS proxies, improving error handling for proxy URIs, and adding
tests for proxy functionality.

### Dependency Updates:
*
[`Cargo.toml`](diffhunk://#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542L394-R401):
Added support for SOCKS proxies in the `reqwest` dependency by including
the `socks` feature.

### Code Improvements:
*
[`crates/reqwest_client/src/reqwest_client.rs`](diffhunk://#diff-8e036b034e987390be2f57373864b75d6983f0cf84e85c43793eb431d13538f3L47-R52):
Improved error handling when parsing proxy URIs by logging errors
instead of directly panicking.

### Testing Enhancements:
*
[`crates/reqwest_client/src/reqwest_client.rs`](diffhunk://#diff-8e036b034e987390be2f57373864b75d6983f0cf84e85c43793eb431d13538f3R274-R317):
Added tests to verify the handling of various proxy URIs, including
valid and invalid cases.

Release Notes:

- N/A
2024-10-18 09:57:00 -07:00
renovate[bot]
c59a75db1d Update actions/upload-artifact digest to b4b15b8 (#19310)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | digest | `604373d` -> `b4b15b8` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 12:41:36 -04:00
David Baldwin
b3c93130ec elixir: Support describe, test, setup, setup_all in outlines (#19135)
Closes #9894 

Release Notes:

- N/A

### Before


![2024-10-12T204848@2x](https://github.com/user-attachments/assets/84b7f123-8845-4e6d-b1b1-444e54ea6599)

### After


![2024-10-12T204749@2x](https://github.com/user-attachments/assets/67fdcead-bad3-4967-9ac4-0b85f1da7bca)
2024-10-18 12:39:24 -04:00
Marshall Bowers
73a6c542f3 vue: Bump to v0.1.1 (#19421)
This PR bumps the Vue extension to v0.1.1.

Changes:

- https://github.com/zed-industries/zed/pull/19419

Release Notes:

- N/A
2024-10-18 11:56:16 -04:00
Marshall Bowers
2cd6c19873 svelte: Bump to v0.2.1 (#19420)
This PR bumps the Svelte extension to v0.2.1.

Changes:

- https://github.com/zed-industries/zed/pull/19418

Release Notes:

- N/A
2024-10-18 11:37:51 -04:00
Marshall Bowers
6f24c1da79 vue: Support lang attribute for style tag injections (#19419)
This PR adds support for injecting languages into `<style>` tags using
`<style lang="...">` in Vue.

Extracted from https://github.com/zed-industries/zed/pull/18052.

Release Notes:

- N/A

Co-authored-by: Albert Marashi <albert@lumina.earth>
2024-10-18 11:36:30 -04:00
Marshall Bowers
5508832ba6 svelte: Adjust block keyword highlighting (#19418)
This PR adjusts the highlights for `{#each ...}` and `{#if ...}`
expression blocks to use keyword highlighting.

Extracted from https://github.com/zed-industries/zed/pull/18052.

Release Notes:

- N/A

Co-authored-by: Albert Marashi <albert@lumina.earth>
2024-10-18 11:25:03 -04:00
Marshall Bowers
35f2f2aac4 Treat .postcss files as CSS (#19416)
This PR makes it so `.postcss` files are recognized as CSS.

The `tree-sitter-css` grammar has basic support for PostCSS:
https://github.com/tree-sitter/tree-sitter-css/issues/17#issuecomment-1830349808.

Closes #18051.

Release Notes:

- `.postcss` files are now recognized as CSS.
2024-10-18 11:11:29 -04:00
Peter Tripp
9e27b6694a keymap: Add cmd-o 'Go to symbol' for JetBrains MacOS key bindings (#19415) 2024-10-18 10:12:39 -04:00
CharlesChen0823
f5124c21d1 scrollbar: Fix horizontal scrollbar display overflow last item (#19403)
currently, the laste item display is overflow by scrollbar, cause it
cannot clickable.
So remain one item height for display horizontal scrollbar,.
![Screenshot 2024-10-18
192352](https://github.com/user-attachments/assets/d686f0e8-93f8-426e-8816-6f00ed17a599)



Release Notes:

- N/A
2024-10-18 14:07:50 +02:00
Thorsten Ball
ea460014ab ssh remoting: Undo the spawning of message handlers (#19409)
I suspect that this might have something to do with saving files not
being possible after a while.

I want to merge this and bump nightly.

Release Notes:

- N/A
2024-10-18 10:48:11 +02:00
Peter Tripp
5168fc27a1 docs: More Java extension documentation (#19390)
Follow up of: https://github.com/zed-industries/zed/pull/19113
2024-10-17 20:21:45 -04:00
Marshall Bowers
2bcf9fc490 Add client::zed_urls module for constructing zed.dev URLs (#19391)
This PR adds a new `zed_urls` module to the `client` crate.

This module contains functions for constructing URLs to Zed properties,
such as zed.dev.

The URLs produced by this module will respect the server URL set via
settings or the `ZED_SERVER_URL` environment variable. This allows them
to correctly reflect the current environment (such as when testing Zed
against a local collab/zed.dev).

Release Notes:

- N/A
2024-10-17 16:18:35 -04:00
Marshall Bowers
3c32f01a8f paths: Update doc comment (#19388)
This PR updates a doc comment to use proper capitalization.

Also removes an unneeded `return`.

Release Notes:

- N/A
2024-10-17 15:36:29 -04:00
Marshall Bowers
9d61cd5120 ci: Check spelling separately from formatting (#19385)
This PR moves the spelling check out of the `check_style` action, which
we can leave for just checking formatting.

We can't use the `crates-ci-typos` action as-is on the macOS runners due
to the absence of `wget`.

Release Notes:

- N/A
2024-10-17 13:47:23 -04:00
Max Brunsfeld
411f64b374 Restructure assistant edits to show all changes in a proposed-change editor (#18240)
This changes the `/workflow` command so that instead of emitting edits
in separate steps, the user is presented with a single tab, with an
editable diff that they can apply to the buffer.

Todo

* Assistant panel
* [x] Show a patch title and a list of changed files in a block
decoration
* [x] Don't store resolved patches as state on Context. Resolve on
demand.
    * [ ] Better presentation of patches in the panel
    * [ ] Show a spinner while patch is streaming in
* Patches
* [x] Preserve leading whitespace in new text, auto-indent insertions
    * [x] Ensure patch title is very short, to fit better in tab
* [x] Improve patch location resolution, prefer skipping whitespace over
skipping `}`
    * [x] Ensure patch edits are auto-indented properly
* [ ] Apply `Update` edits via a diff between the old and new text, to
get fine-grained edits.
* Proposed changes editor
    * [x] Show patch title in the tab
    * [x] Add a toolbar with an "Apply all" button
* [x] Make `open excerpts` open the corresponding location in the base
buffer (https://github.com/zed-industries/zed/pull/18591)
* [x] Add an apply button above every hunk
(https://github.com/zed-industries/zed/pull/18592)
* [x] Expand all diff hunks by default
(https://github.com/zed-industries/zed/pull/18598)
    * [x] Fix https://github.com/zed-industries/zed/issues/18589
* [x] Syntax highlighting doesn't work until the buffer is edited
(https://github.com/zed-industries/zed/pull/18648)
* [x] Disable LSP interaction in Proposed Changes editor
(https://github.com/zed-industries/zed/pull/18945)
* [x] No auto-indent? (https://github.com/zed-industries/zed/pull/18984)
* Prompt
    * [ ] make sure old_text is unique

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2024-10-17 13:18:13 -04:00
Peter Tripp
4ae2f93086 ci: Improve GitHub Action modularity (#18861)
- Closes: https://github.com/zed-industries/zed/issues/19351
- Switch to using the official [typos GitHub Action](https://github.com/crate-ci/typos/blob/master/docs/github-action.md)
- Move the typos check into `actions/check_style`
- Move Squawk Postgres migration check out of `actions/check_style` file into ci.yml
- `actions/check_style` can now be run on stateless/linux runners (previous required self-hosted MacOS runner)
- ci.yml: Split old `style` into checks into those that can run statelessly (linux) and everything else into a new `migration` group which benefit from the full git checkout available on the MacOS runners.
- ci.yml: Move `Check unused dependencies` from style to `linux_tests`
- Add `if: github.repository_owner == 'zed-industries'` to all jobs so they won't try and run on GitHub forks.
2024-10-17 12:50:19 -04:00
Conrad Irwin
65fb2782eb Always have cmd-o open a local project (#19376)
Release Notes:

- Fixed `cmd-o` in an SSH project to always open a local project
2024-10-17 10:36:53 -06:00
Thorsten Ball
e6b9a8ef9b ssh remoting: Handle OpenNewBuffer request (#19373)
Release Notes:

- N/A
2024-10-17 17:49:17 +02:00
Elliot Thomas
398d0396b6 workspace: Fix inconsistent paths order serialization (#19232)
Release Notes:

- Fixed inconsistent serialization of workspace paths order
2024-10-17 17:38:28 +02:00
Finn Evers
e9e4c770ca Update all occurrences of option_as_meta to new default value (#19369)
This PR is a quick follow-up to #19364 which updates some left-out
occurrences of `option_as_meta` to the new default value (`false`).
2024-10-17 11:21:07 -04:00
Thorsten Ball
4be9da2641 remote ssh: Make "get permalink to line" work (#19366)
This makes the `editor: copy permalink to line` and `editor: copy
permalink to line` actions work in SSH remote projects.

Previously it would only work in local projects.

Demo:


https://github.com/user-attachments/assets/a8012152-b631-4b34-9ff2-e4d033c97dee




Release Notes:

- N/A
2024-10-17 17:07:42 +02:00
Thorsten Ball
c186e99a3d ssh remote: Reset missed heartbeats on connection activity (#19368)
Ran into this this morning. At least I suspect I ran into it. In any
case: we need to reset the missed hearbeats to 0 in case we got any
connection activity.

Release Notes:

- N/A
2024-10-17 17:07:34 +02:00
Peter Tripp
4df882c295 Make terminal.option_as_meta=false in default settings (#19364)
- This reverts the change I made in https://github.com/zed-industries/zed/pull/15535 which set `option_as_meta` to `true` in the default settings.
- `true` is a reasonable default for US Keyboards, but is terrible for many others which rely on `alt+<key>` for totally normal keystroke combinations.
2024-10-17 10:31:35 -04:00
Marshall Bowers
17f2929b4c collab: Anchor new subscription's billing cycle to the first of the month (#19367)
This PR makes it so new subscriptions will have their billing cycle
anchored to the first of the month.

When someone signs up today, they will be billed starting on the first
of next month.

Release Notes:

- N/A

Co-authored-by: Antonio <antontio@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
2024-10-17 10:18:12 -04:00
Peter Tripp
5ad392035e Support uppercase extensions in image preview (#19304) 2024-10-17 08:48:18 -04:00
Antonio Scandurra
8c910540ed Subtract FREE_TIER_MONTHLY_SPENDING_LIMIT from reported monthly spend (#19358)
Release Notes:

- N/A
2024-10-17 13:09:50 +02:00
Antonio Scandurra
455f241c6a Introduce a new /billing/monthly_spend API (#19354)
Fixes https://github.com/zed-industries/zed/issues/19353

Release Notes:

- N/A
2024-10-17 12:34:25 +02:00
Antonio Scandurra
498ecd6404 Fetch more than one page when polling stripe events (#19343)
This fixes a bug that was causing most users to be unable to use the
LLMs via Zed. It was caused by not using pagination and, instead, always
querying the very first page of stripe events.

Note that we're also allowing processing events generated in the last 24
hours (before, this was only 1 hour). I did this so that we can process
the backlog of events that the aforementioned bug was skipping.

Release Notes:

- N/A
2024-10-17 09:47:25 +02:00
Thorsten Ball
3216de7eb5 ssh remoting: Do not print error backtrace on non-zero exit (#19290)
Closes #ISSUE


Release Notes:

- N/A
2024-10-17 09:41:16 +02:00
renovate[bot]
57369b5a54 Update Rust crate tree-sitter-elixir to v0.3.1 (#19335) 2024-10-17 08:44:51 +03:00
Heavysnowjakarta
f9d4272e13 docs: Java extension settings (#19113)
Co-authored-by: Peter Tripp <peter@zed.dev>
2024-10-17 00:04:59 -04:00
Conrad Irwin
378a2cf9d8 Allow passing args to ssh (#19336)
This is useful for passing a custom identity file, jump hosts, etc.

Unlike with the v1 feature, we won't support `gh`/`gcloud` ssh wrappers
(yet?). I think the right way of supporting those would be to let
extensions provide remote projects.

Closes #19118

Release Notes:

- SSH remoting: restored ability to set arguments for SSH
2024-10-16 21:09:31 -06:00
Conrad Irwin
f1d01d59ac Simplify PR template (#19337)
Release Notes:

- N/A
2024-10-16 20:22:08 -06:00
Danilo Leal
78093b8e76 ssh: Clean up title bar indicator icon (#19328)
This PR cleans up the custom icon with indicator implementation in favor
of `IconWithIndicator`, which we already had. It seems like it isn't
super used still, but it's good to try to enforce some consistency
either way. I checked my changes against the REPL stuff (one instance
where its used) and everything's looking good so far. As far as SSH,
nothing has visually changed; we just have less code for this thing now.

<img width="800" alt="Screenshot 2024-10-17 at 2 15 47 AM"
src="https://github.com/user-attachments/assets/5c146757-501e-4242-b145-a576a8f289b5">

---

Release Notes:

- N/A
2024-10-16 22:25:27 -03:00
Danilo Leal
a41e973782 ssh: Remove server count from modal header (#19329)
The server count was something that existed since the remote development
implementation and we just kept it there without a lot of critical
thinking. However, it doesn't feel like it's particularly useful yet,
which means that, at least for now, we could clean it up more and wait
for further feedback to add it back, if ever requested.

Release Notes:

- N/A
2024-10-16 22:25:15 -03:00
Danilo Leal
9a3d8733ce ssh: Use system prompt for the server removal action (#19332)
This PR replaces a toast for the system prompt to confirm the action of
removing a server from the remote list. The alert dialog component is
the right choice here as we want to have a modal action that forces
choice. This should make it easier to convert to a nativa alert dialog
in the future, as well as for other platforms.

<img width="800" alt="Screenshot 2024-10-17 at 3 01 41 AM"
src="https://github.com/user-attachments/assets/7bb1210a-54bf-40da-a85a-f269484825a1">

Release Notes:

- N/A
2024-10-16 22:25:03 -03:00
Conrad Irwin
c888101e4b SSH remoting: Don't panic when opening root, open ~ instead (#19322)
Release Notes:

- Fixed a panic when doing `zed ssh://server/`
2024-10-16 17:17:20 -06:00
Conrad Irwin
0c04fb9862 SSH remoting: better error message for projects (#19320)
Before this, if no project paths were opened you were in a wierd UI
state where
most things didn't work because the project was ssh, but no
files/folders were open.

Release Notes:

- Fixed error handling when no project paths could be opened
2024-10-16 17:16:56 -06:00
Marshall Bowers
f6fad3b09e collab: Remove lifetime spending limit in favor of LLM usage billing (#19321)
This PR removes the lifetime spending limit that was added in #16780.

We had previously added this as a way to prevent runaway usage, but now
that we have a cap on free usage per month with paid access after that,
we don't need this check anymore.

Release Notes:

- N/A
2024-10-16 18:14:07 -04:00
renovate[bot]
6614feff97 Pin astral-sh/setup-uv action to f3bcaeb (#19309)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | pinDigest | -> `f3bcaeb` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 17:20:39 -04:00
Conrad Irwin
08b1545c85 Show a user-visible error message if saving fails (#19311)
Release Notes:

- Added a user-visible error message when a manual save fails.
2024-10-16 15:17:38 -06:00
Marshall Bowers
fedd177b08 collab: Add context to errors syncing billing events to Stripe (#19315)
This PR adds context to errors that occur when trying to sync billing
events to Stripe.

Release Notes:

- N/A
2024-10-16 17:09:26 -04:00
renovate[bot]
4288096ca1 Update Rust crate tree-sitter-cpp to v0.23.1 (#18974)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[tree-sitter-cpp](https://redirect.github.com/tree-sitter/tree-sitter-cpp)
| workspace.dependencies | patch | `0.23.0` -> `0.23.1` |

---

### Release Notes

<details>
<summary>tree-sitter/tree-sitter-cpp (tree-sitter-cpp)</summary>

###
[`v0.23.1`](https://redirect.github.com/tree-sitter/tree-sitter-cpp/compare/v0.23.0...v0.23.1)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-cpp/compare/v0.23.0...v0.23.1)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMTQuMCIsInVwZGF0ZWRJblZlciI6IjM4LjExNC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 23:21:23 +03:00
renovate[bot]
256c31a5d9 Update Rust crate tree-sitter-c to v0.23.1 (#18958)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tree-sitter-c](https://redirect.github.com/tree-sitter/tree-sitter-c)
| workspace.dependencies | patch | `0.23.0` -> `0.23.1` |

---

### Release Notes

<details>
<summary>tree-sitter/tree-sitter-c (tree-sitter-c)</summary>

###
[`v0.23.1`](https://redirect.github.com/tree-sitter/tree-sitter-c/compare/v0.23.0...v0.23.1)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-c/compare/v0.23.0...v0.23.1)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMTQuMCIsInVwZGF0ZWRJblZlciI6IjM4LjExNC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 23:19:10 +03:00
David Soria Parra
c8b6ad9666 Context Servers: Protocol fixes and UI improvements (#19087)
This PR does two things. It fixes some minor inconsistencies in the
protocol. This is mostly about handling JSON RPC notifications correctly
and skipping fields when set to None.

Second part is about improving the rendering of context server commands,
by passing on the description
of the command to the slash command UI and showing the name of the
argument as a CodeLabel.

Release Notes:

- N/A
2024-10-16 13:07:15 -07:00
Peter Tripp
0e22c9f275 docs: Add C++ clangd example arguments (#19308) 2024-10-16 16:07:05 -04:00
Kirill Bulatov
56f69be2e7 Do not allow [re]running ssh tasks when not connected to the server (#19306)
Release Notes:

- N/A
2024-10-16 22:57:39 +03:00
Kirill Bulatov
02f63e49ed Resolve proto hints with empty resolve data (#19274)
Fixed ssh remoting not showing a lot of hints


Release Notes:

- N/A
2024-10-16 21:50:51 +03:00
Kirill Bulatov
3dcc638537 Better handle shell for remote ssh projects (#19297)
Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-10-16 21:49:54 +03:00
Marshall Bowers
d35b646dbb assistant: Direct user to account page to subscribe for more LLM usage (#19300)
This PR updates the location where we send the user to subscribe for
more LLM usage to the account page.

Release Notes:

- Updated the URL to the account page when subscribing to LLM usage.
2024-10-16 14:03:42 -04:00
张小白
338bf3fd28 windows: Fix window not displaying correctly on launch (#19124)
Closes #18705 (comment)

This PR fixes the issue where the Zed window was not displaying
correctly on launch. Now, when Zed is closed in a maximized state, it
will reopen in a maximized state.

On macOS, when a window is created but not yet visible, calling `zoom`
or `toggle_fullscreen` will still affect the hidden window. However,
this behavior is different on Windows, so special handling is required.

Also, since #18705 hasn't been reviewed yet, I'm not sure if this PR
should be merged now or if it should wait until #18705 is reviewed
first.


Release Notes:

- N/A
2024-10-16 10:29:42 -07:00
Matin Aniss
879a2ea06f gpui: Replace redundant code in animation (#19273)
Just a small change to replace some redundant code in the animation
element.

Release Notes:

- N/A
2024-10-16 10:26:26 -07:00
Piotr Osiewicz
7a5003bea2 ssh: Do not look up dev servers when rendering the default mode (#19295)
This should help with the bug where there's a mismatch between
connection count and the list showing empty state.

Closes #ISSUE

Release Notes:

- N/A
2024-10-16 18:53:05 +02:00
Joseph T. Lyons
f8f3f369f6 v0.159.x dev 2024-10-16 12:47:57 -04:00
Antonio Scandurra
474e670bbd Increase monthly free tier spend from 5 dollars to 10 dollars (#19291)
Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
2024-10-16 12:22:24 -04:00
Marshall Bowers
fe0bcc063c collab: Add Stripe API key to Kubernetes template (#19292)
This PR adds the Stripe API key to the Kubernetes template.

It's optional right now, so we can set the API key when we're ready.

Release Notes:

- N/A
2024-10-16 12:10:39 -04:00
Thorsten Ball
69abe71bf7 ssh remoting: Treat closed stderr as error (#19289)
Before this change we had a race condition bug: if stderr was closed
before the other two sockets, we wouldn't properly detect when the
server died, and not report or retry anything.

That's because we treated a closed stderr as a non-error.

Technically, it isn't an error (closing a connection is okay!), but
until we have a proper shutdown ceremony between all three processes, we
can treat it as an error, because that lets us to detect when the server
is gone.

On the client-side, we also always react to these errors by
reconnecting. Except when we shutdown: there we do a proper shutdown and
won't error on the proxy exit code.

So, this works, even if I wish there was a better way for the server to
communicate to the proxy that it shutdown properly. But I don't want a
fourth socket.

Release Notes:

- N/A
2024-10-16 18:05:52 +02:00
Marshall Bowers
9c3d80d6e8 collab: Fetch more meters and prices when initializing StripeBilling (#19288)
This PR makes it so we fetch more meters and prices when initializing
`StripeBilling`, as we have more than 10 meters defined.

Release Notes:

- N/A
2024-10-16 11:40:56 -04:00
Kirill Bulatov
834d50f0db Properly open worktrees when cmd-clicking in terminal or on inlay hints (#19280)
* uses the state that's synced, to fetch the language server name
* uses proper, canonicalized path when creating a remote ssh worktree,
otherwise `~/foo/something` stays unexpanded

Release Notes:

- N/A
2024-10-16 18:12:36 +03:00
Kirill Bulatov
bcdb10b3cb Do not attempt to install prettier if the language change is unrelated (#19283)
Release Notes:

- Fix prettier install being attempted too much
2024-10-16 18:10:05 +03:00
Marshall Bowers
598939d186 collab: Refresh the user's LLM token when their subscription changes (#19281)
This PR makes it so collab will trigger a refresh for a user's LLM token
whenever their subscription changes.

This allows us to proactively push down changes to their subscription.

In order to facilitate this, the Stripe event processing has been moved
from the `api` service to the `collab` service in order to access the
RPC server.

Release Notes:

- N/A
2024-10-16 10:58:28 -04:00
Thorsten Ball
9d944d0662 ssh remote: Restore ControlPersist=no (#19277)
This restores the change from #19193 that I erroneously reverted in
#19234.

I think the bug in #19275 got in my way when testing.

With that bug fixed, the changes in here also work fine.


Release Notes:

- N/A
2024-10-16 16:13:31 +02:00
Tilman Roeder
7d2628e805 Make the divider rule color more muted (#19255)
I've been a bit annoyed by the hover divider rule being extremely bright
compared to other divider rules in the UI. This PR updates their color
to use the regular border color from the current theme instead of the
muted (but still pretty bright) text color.

Apologies for the unsolicited PR (and please feel free to close if it
goes against some other plans / designs you already have in place :).

#### Example screenshot before:
<img width="302" alt="Screenshot 2024-10-15 at 23 29 18"
src="https://github.com/user-attachments/assets/7ea22808-8135-4a46-9457-e670225aebaa">

#### Example screenshot after:
<img width="312" alt="Screenshot 2024-10-15 at 23 28 16"
src="https://github.com/user-attachments/assets/63ac0d02-ae6d-4962-84a2-1fdb95519b15">

***

Release Notes:

- Make the divider rule in LSP hovers more muted
2024-10-16 11:00:22 -03:00
Ihnat Aŭtuška
84df3a0cad Allow formatting selections via LSP (#18752)
Release Notes:

- Added a new `editor: format selections` action that allows formatting
only the currently selected text via the primary language server.

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-10-16 15:58:37 +02:00
Thorsten Ball
eb76065ad3 ssh remoting: Fix hang when activity channel gets dropped (#19275)
When the SSH command dies or the server, the channel gets dropped and
the heartbeat method went into an infinite loop causing a hang.

Oversight from yesterday. Fixed now.

Release Notes:

- N/A
2024-10-16 15:57:58 +02:00
Peter Tripp
84018d7a2d zig: Bump to v0.3.1 (#19252)
Includes:
- https://github.com/zed-industries/zed/pull/18323
- https://github.com/zed-industries/zed/pull/17488
2024-10-16 08:42:45 -04:00
Peter Tripp
57c55b32e1 html: Bump to v0.1.3 (#19251)
Includes:
- https://github.com/zed-industries/zed/pull/18024
2024-10-16 08:42:27 -04:00
Peter Tripp
a4357c429a elixir: Bump to v0.1.0 (#19250)
Includes:
- https://github.com/zed-industries/zed/pull/18024
- https://github.com/zed-industries/zed/pull/17488
- https://github.com/zed-industries/zed/pull/16985
2024-10-16 08:42:07 -04:00
Peter Tripp
103665ee28 astro: Bump to v0.1.1 (#19249)
Includes:
- https://github.com/zed-industries/zed/pull/18024
2024-10-16 08:41:45 -04:00
Thorsten Ball
2f960c4aba project environment: Log when which env is used (#19270)
This adds more logging for debugging purposes.

Release Notes:

- N/A
2024-10-16 14:12:45 +02:00
Piotr Osiewicz
109ebc5f27 ui: Add Scrollbar component (#18927)
Closes #ISSUE

Release Notes:

- N/A
2024-10-16 13:57:28 +02:00
476 changed files with 24582 additions and 17785 deletions

View File

@@ -7,9 +7,3 @@ runs:
- name: cargo fmt
shell: bash -euxo pipefail {0}
run: cargo fmt --all -- --check
- name: Find modified migrations
shell: bash -euxo pipefail {0}
run: |
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
. ./script/squawk

View File

@@ -2,14 +2,4 @@ Closes #ISSUE
Release Notes:
- Added/Fixed/Improved ...
Optionally, include screenshots / media showcasing your addition that can be included in the release notes.
### Or...
Closes #ISSUE
Release Notes:
- N/A
- N/A *or* Added/Fixed/Improved ...

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0

View File

@@ -18,7 +18,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Checkout code
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
ref: ${{ github.event.inputs.branch }}
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}

View File

@@ -14,6 +14,7 @@ on:
- "**"
paths-ignore:
- "docs/**"
- ".github/workflows/community_*"
concurrency:
# Allow only one workflow per any non-`main` branch.
@@ -24,38 +25,31 @@ env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
RUSTFLAGS: "-D warnings"
jobs:
style:
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
if: github.repository_owner == 'zed-industries'
timeout-minutes: 60
name: Check formatting and spelling
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0
fetch-depth: 0 # fetch full history
- name: Remove untracked files
run: git clean -df
- name: Check spelling
run: script/check-spelling
- name: Run style checks
uses: ./.github/actions/check_style
- name: Check unused dependencies
uses: bnjbvr/cargo-machete@main
- name: Check licenses are present
run: script/check-licenses
- name: Check license generation
run: script/generate-licenses /tmp/zed_licenses_output
- name: Find modified migrations
shell: bash -euxo pipefail {0}
run: |
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
. ./script/squawk
- name: Ensure fresh merge
shell: bash -euxo pipefail {0}
@@ -77,36 +71,64 @@ jobs:
input: "crates/proto/proto/"
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
style:
timeout-minutes: 60
name: Check formatting and spelling
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-8vcpu-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Run style checks
uses: ./.github/actions/check_style
- name: Check for typos
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
with:
config: ./typos.toml
macos_tests:
timeout-minutes: 60
name: (macOS) Run Clippy and tests
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: cargo clippy
run: ./script/clippy
- name: Check unused dependencies
uses: bnjbvr/cargo-machete@main
- name: Check licenses
run: |
script/check-licenses
script/generate-licenses /tmp/zed_licenses_output
- name: Run tests
uses: ./.github/actions/run_tests
- name: Build collab
run: RUSTFLAGS="-D warnings" cargo build -p collab
run: cargo build -p collab
- name: Build other binaries and features
run: |
RUSTFLAGS="-D warnings" cargo build --workspace --bins --all-features
cargo build --workspace --bins --all-features
cargo check -p gpui --features "macos-blade"
RUSTFLAGS="-D warnings" cargo build -p remote_server
cargo build -p remote_server
linux_tests:
timeout-minutes: 60
name: (Linux) Run Clippy and tests
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
steps:
@@ -114,12 +136,12 @@ jobs:
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -134,11 +156,12 @@ jobs:
uses: ./.github/actions/run_tests
- name: Build Zed
run: RUSTFLAGS="-D warnings" cargo build -p zed
run: cargo build -p zed
build_remote_server:
timeout-minutes: 60
name: (Linux) Build Remote Server
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
steps:
@@ -146,12 +169,12 @@ jobs:
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -160,21 +183,25 @@ jobs:
run: ./script/remote-server && ./script/install-mold 2.34.0
- name: Build Remote Server
run: RUSTFLAGS="-D warnings" cargo build -p remote_server
run: cargo build -p remote_server
# todo(windows): Actually run the tests
windows_tests:
timeout-minutes: 60
name: (Windows) Run Clippy and tests
if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-1
steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
run: git config --system core.longpaths true
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
@@ -184,7 +211,7 @@ jobs:
run: cargo xtask clippy
- name: Build Zed
run: $env:RUSTFLAGS="-D warnings"; cargo build
run: cargo build
bundle-mac:
timeout-minutes: 60
@@ -210,7 +237,7 @@ jobs:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# We need to fetch more than one commit so that `script/draft-release-notes`
# is able to diff between the current and previous tag.
@@ -248,20 +275,20 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (universal) to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -295,7 +322,7 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
@@ -312,7 +339,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -342,7 +369,7 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
@@ -359,7 +386,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
@@ -367,7 +394,6 @@ jobs:
- name: Upload app bundle to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

View File

@@ -14,10 +14,10 @@ jobs:
stale-issue-message: >
Hi there! 👋
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. If you are able to reproduce this issue in the latest version of Zed, please let us know by commenting on this issue, and we will keep it open. If you can't reproduce it, feel free to close the issue yourself. Otherwise, we'll close it in 7 days.
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, feel free to ping a Zed team member to reopen this issue or open a new one."
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
# We will increase `days-before-stale` to 365 on or after Jan 24th,
# 2024. This date marks one year since migrating issues from
# 'community' to 'zed' repository. The migration added activity to all

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries'
steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
with:
version: "latest"
enable-cache: true

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries'
steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
with:
version: "latest"
enable-cache: true

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:

View File

@@ -8,11 +8,12 @@ on:
jobs:
deploy-docs:
name: Deploy Docs
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false

View File

@@ -17,7 +17,7 @@ jobs:
- test
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0
@@ -36,7 +36,7 @@ jobs:
needs: style
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0
@@ -71,7 +71,7 @@ jobs:
run: doctl registry login
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
@@ -97,7 +97,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false

View File

@@ -11,10 +11,11 @@ on:
jobs:
check_formatting:
name: "Check formatting"
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
@@ -29,5 +30,8 @@ jobs:
false
}
- name: Check spelling
run: script/check-spelling docs/
- name: Check for Typos with Typos-CLI
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
with:
config: ./typos.toml
files: ./docs/

View File

@@ -1,50 +0,0 @@
name: Mark for Nightly
on:
issue_comment:
types: [created]
jobs:
mark-for-nightly:
if: github.event.issue.pull_request && contains(github.event.comment.body, '/nightly')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Get PR details and check if open
id: pr_details
uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});
if (pr.data.state !== 'open') {
console.log('PR is not open. Skipping.');
return null;
}
return {
sha: pr.data.head.sha,
ref: pr.data.head.ref
}
- name: Edit comment with SHA
if: steps.pr_details.outputs.result != 'null'
uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const sha = '${{ fromJson(steps.pr_details.outputs.result).sha }}';
const originalBody = context.payload.comment.body;
const updatedBody = originalBody.replace('/nightly', `/nightly:${sha}`);
const finalBody = `${updatedBody}\n\nThis PR's current HEAD (${sha}) will be included in the next nightly build. To remove it from the nightly build, comment with \`/nightly:remove\`.`;
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
body: finalBody
});

View File

@@ -16,12 +16,12 @@ jobs:
- ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"

View File

@@ -27,7 +27,7 @@ jobs:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false

View File

@@ -14,63 +14,6 @@ env:
RUST_BACKTRACE: 1
jobs:
prepare-nightly-train:
name: Prepare nightly-train branch
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
fetch-depth: 0
- name: Create or update nightly-train branch
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git checkout -B nightly-train main
# Find all comments with /nightly:SHA
nightly_comments=$(gh api graphql -f query='
query($owner:String!, $repo:String!) {
repository(owner:$owner, name:$repo) {
pullRequests(last:128, states:OPEN) {
nodes {
number
comments(last:128) {
nodes {
body
}
}
}
}
}
}' -f owner=${{ github.repository_owner }} -f repo=${{ github.event.repository.name }} | jq -r '.data.repository.pullRequests.nodes[] | select(.comments.nodes[].body | contains("/nightly:")) | {number: .number, comments: [.comments.nodes[].body | select(contains("/nightly:"))]} | @json')
# Process each PR
echo "$nightly_comments" | jq -c '.' | while read -r pr; do
pr_number=$(echo "$pr" | jq -r '.number')
last_command=$(echo "$pr" | jq -r '.comments[-1]')
sha=$(echo "$last_command" | grep -oP '/nightly:\K[a-f0-9]+')
if [ -n "$sha" ]; then
if git cherry-pick $sha; then
if cargo check --workspace; then
echo "Successfully cherry-picked and checked $sha from PR #$pr_number"
else
git reset --hard HEAD~1
echo "::warning::Commit $sha from PR #$pr_number failed cargo check, excluding from nightly-train"
fi
else
echo "::warning::Failed to cherry-pick $sha from PR #$pr_number, skipping"
git cherry-pick --abort
fi
elif [[ $last_command == *"/nightly:remove"* ]]; then
echo "Skipping PR #$pr_number due to /nightly:remove command"
fi
done
git push origin nightly-train --force
style:
timeout-minutes: 60
name: Check formatting and Clippy lints
@@ -78,14 +21,12 @@ jobs:
runs-on:
- self-hosted
- test
needs: prepare-nightly-train
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
fetch-depth: 0
ref: nightly-train
- name: Run style checks
uses: ./.github/actions/check_style
@@ -103,10 +44,9 @@ jobs:
needs: style
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
ref: nightly-train
- name: Run tests
uses: ./.github/actions/run_tests
@@ -135,10 +75,9 @@ jobs:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
ref: nightly-train
- name: Set release channel to nightly
run: |
@@ -170,10 +109,9 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
ref: nightly-train
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -211,10 +149,9 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
ref: nightly-train
- name: Install Linux dependencies
run: ./script/linux
@@ -245,10 +182,9 @@ jobs:
- bundle-linux-arm
steps:
- name: Checkout repo
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
ref: nightly-train
- name: Update nightly tag
run: |

View File

@@ -1,3 +1,3 @@
# Code of Conduct
The Code of Conduct for this repository can be found online at [zed.dev/docs/code-of-conduct](https://zed.dev/docs/code-of-conduct).
The Code of Conduct for this repository can be found online at [zed.dev/code-of-conduct](https://zed.dev/code-of-conduct).

View File

@@ -2,7 +2,7 @@
Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor!
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/docs/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
## Contribution ideas

713
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,6 @@ members = [
"crates/context_servers",
"crates/copilot",
"crates/db",
"crates/dev_server_projects",
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
@@ -45,7 +44,6 @@ members = [
"crates/google_ai",
"crates/gpui",
"crates/gpui_macros",
"crates/headless",
"crates/html_to_markdown",
"crates/http_client",
"crates/image_viewer",
@@ -119,6 +117,7 @@ members = [
"crates/theme_selector",
"crates/time_format",
"crates/title_bar",
"crates/toolchain_selector",
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
@@ -139,7 +138,6 @@ members = [
"extensions/astro",
"extensions/clojure",
"extensions/csharp",
"extensions/dart",
"extensions/deno",
"extensions/elixir",
"extensions/elm",
@@ -158,12 +156,10 @@ members = [
"extensions/ruff",
"extensions/slash-commands-example",
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
"extensions/test-extension",
"extensions/toml",
"extensions/uiua",
"extensions/vue",
"extensions/zig",
#
@@ -203,7 +199,6 @@ command_palette_hooks = { path = "crates/command_palette_hooks" }
context_servers = { path = "crates/context_servers" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
dev_server_projects = { path = "crates/dev_server_projects" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -221,7 +216,6 @@ go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
gpui_macros = { path = "crates/gpui_macros" }
headless = { path = "crates/headless" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
image_viewer = { path = "crates/image_viewer" }
@@ -296,6 +290,7 @@ theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
toolchain_selector = { path = "crates/toolchain_selector" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
@@ -349,6 +344,7 @@ ctor = "0.2.6"
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
exec = "0.3.1"
@@ -374,6 +370,7 @@ linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = "0.3.1"
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
@@ -381,6 +378,11 @@ 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 = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
profiling = "1"
@@ -389,11 +391,19 @@ prost-build = "0.9"
prost-types = "0.9"
pulldown-cmark = { version = "0.12.0", default-features = false }
rand = "0.8.5"
rayon = "1.8"
regex = "1.5"
repair_json = "0.1.0"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = ["charset", "http2", "macos-system-configuration", "rustls-tls-native-roots", "stream"]}
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
"rustls-tls-native-roots",
"socks",
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.15", default-features = false, features = [
runtimelib = { version = "0.16.0", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
@@ -453,7 +463,7 @@ tree-sitter-diff = "0.1.0"
tree-sitter-html = "0.20"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.23"
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
tree-sitter-ruby = "0.23"

1
assets/icons/diff.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-diff"><path d="M12 3v14"/><path d="M5 10h14"/><path d="M5 21h14"/></svg>

After

Width:  |  Height:  |  Size: 275 B

View File

@@ -58,6 +58,7 @@
"gitignore": "vcs",
"gitkeep": "vcs",
"gitmodules": "vcs",
"gleam": "gleam",
"go": "go",
"gql": "graphql",
"graphql": "graphql",
@@ -65,6 +66,7 @@
"h": "c",
"handlebars": "code",
"hbs": "template",
"hcl": "hcl",
"heex": "elixir",
"heic": "image",
"heif": "image",
@@ -82,6 +84,7 @@
"j2k": "image",
"java": "java",
"jfif": "image",
"jl": "julia",
"jp2": "image",
"jpeg": "image",
"jpg": "image",
@@ -116,6 +119,7 @@
"myd": "storage",
"myi": "storage",
"nim": "nim",
"nix": "nix",
"nu": "terminal",
"odp": "document",
"ods": "document",
@@ -128,6 +132,7 @@
"php": "php",
"plist": "template",
"png": "image",
"postcss": "css",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
@@ -142,12 +147,15 @@
"rb": "ruby",
"rebar.config": "erlang",
"rkt": "code",
"roc": "roc",
"rs": "rust",
"rtf": "document",
"sass": "sass",
"sav": "storage",
"sc": "scala",
"scala": "scala",
"scm": "code",
"scss": "sass",
"sdf": "storage",
"sh": "terminal",
"sql": "storage",
@@ -181,6 +189,7 @@
"yaml": "settings",
"yml": "settings",
"yrl": "erlang",
"zig": "zig",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
@@ -256,6 +265,9 @@
"fsharp": {
"icon": "icons/file_icons/fsharp.svg"
},
"gleam": {
"icon": "icons/file_icons/gleam.svg"
},
"go": {
"icon": "icons/file_icons/go.svg"
},
@@ -265,6 +277,9 @@
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
"hcl": {
"icon": "icons/file_icons/hcl.svg"
},
"heroku": {
"icon": "icons/file_icons/heroku.svg"
},
@@ -277,6 +292,9 @@
"javascript": {
"icon": "icons/file_icons/javascript.svg"
},
"julia": {
"icon": "icons/file_icons/julia.svg"
},
"kotlin": {
"icon": "icons/file_icons/kotlin.svg"
},
@@ -292,6 +310,9 @@
"nim": {
"icon": "icons/file_icons/nim.svg"
},
"nix": {
"icon": "icons/file_icons/nix.svg"
},
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
@@ -316,12 +337,18 @@
"react": {
"icon": "icons/file_icons/react.svg"
},
"roc": {
"icon": "icons/file_icons/roc.svg"
},
"ruby": {
"icon": "icons/file_icons/ruby.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"sass": {
"icon": "icons/file_icons/sass.svg"
},
"scala": {
"icon": "icons/file_icons/scala.svg"
},
@@ -360,6 +387,9 @@
},
"vue": {
"icon": "icons/file_icons/vue.svg"
},
"zig": {
"icon": "icons/file_icons/zig.svg"
}
}
}

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" fill="black" d="M 3.828125 14.601562 C 3.894531 15.726562 5.183594 16.375 6.132812 15.785156 L 6.136719 15.785156 L 8.988281 13.824219 C 8.996094 13.816406 9.007812 13.8125 9.015625 13.804688 C 9.203125 13.675781 9.4375 13.636719 9.65625 13.691406 L 12.988281 14.550781 C 14.105469 14.839844 15.140625 13.769531 14.8125 12.667969 L 13.832031 9.386719 C 13.769531 9.167969 13.800781 8.9375 13.921875 8.75 C 13.921875 8.746094 13.925781 8.746094 13.925781 8.746094 L 15.777344 5.863281 L 15.777344 5.859375 C 15.78125 5.851562 15.785156 5.84375 15.789062 5.835938 L 15.792969 5.835938 C 16.382812 4.871094 15.6875 3.582031 14.542969 3.554688 L 11.109375 3.472656 C 10.878906 3.464844 10.664062 3.359375 10.519531 3.183594 L 8.339844 0.542969 C 8.019531 0.152344 7.550781 -0.015625 7.105469 0.0078125 L 7.101562 0.0078125 C 7.039062 0.0117188 6.976562 0.0195312 6.914062 0.0273438 C 6.414062 0.117188 5.945312 0.453125 5.75 1 L 4.609375 4.222656 C 4.535156 4.4375 4.367188 4.613281 4.152344 4.695312 L 0.957031 5.945312 C -0.121094 6.363281 -0.328125 7.835938 0.589844 8.535156 L 3.316406 10.609375 C 3.5 10.75 3.609375 10.960938 3.625 11.191406 Z M 7.515625 1.847656 C 7.421875 1.730469 7.296875 1.695312 7.183594 1.714844 C 7.066406 1.734375 6.960938 1.8125 6.914062 1.953125 L 5.867188 4.902344 C 5.699219 5.382812 5.328125 5.765625 4.851562 5.949219 L 1.925781 7.09375 C 1.785156 7.148438 1.710938 7.253906 1.695312 7.371094 C 1.679688 7.484375 1.71875 7.605469 1.839844 7.695312 L 4.335938 9.597656 C 4.742188 9.90625 4.992188 10.375 5.023438 10.882812 L 5.207031 14.003906 C 5.214844 14.152344 5.296875 14.253906 5.398438 14.304688 C 5.503906 14.355469 5.632812 14.355469 5.757812 14.269531 L 8.347656 12.492188 C 8.765625 12.207031 9.292969 12.113281 9.785156 12.242188 L 12.824219 13.027344 C 12.972656 13.066406 13.09375 13.023438 13.175781 12.9375 C 13.257812 12.855469 13.296875 12.734375 13.253906 12.589844 L 12.355469 9.589844 C 12.210938 9.105469 12.285156 8.578125 12.558594 8.148438 L 14.253906 5.511719 C 14.335938 5.386719 14.332031 5.257812 14.277344 5.15625 C 14.222656 5.054688 14.117188 4.980469 13.964844 4.976562 L 10.824219 4.902344 C 10.316406 4.886719 9.835938 4.65625 9.511719 4.261719 Z M 7.515625 1.847656 "/>
<path fill="black" d="M 5.71875 7.257812 C 5.671875 7.25 5.628906 7.246094 5.582031 7.246094 C 5.09375 7.246094 4.695312 7.644531 4.695312 8.128906 C 4.695312 8.613281 5.09375 9.011719 5.582031 9.011719 C 6.070312 9.011719 6.46875 8.613281 6.46875 8.128906 C 6.46875 7.6875 6.140625 7.320312 5.71875 7.257812 Z M 5.71875 7.257812 "/>
<path fill="black" d="M 11.019531 7.953125 C 10.976562 7.957031 10.929688 7.960938 10.886719 7.960938 C 10.398438 7.960938 10 7.5625 10 7.078125 C 10 6.59375 10.398438 6.195312 10.886719 6.195312 C 11.371094 6.195312 11.773438 6.59375 11.773438 7.078125 C 11.773438 7.519531 11.445312 7.886719 11.019531 7.953125 Z M 11.019531 7.953125 "/>
<path fill="black" d="M 7.269531 9.089844 C 7.53125 8.988281 7.828125 9.113281 7.933594 9.375 C 8.125 9.859375 8.503906 9.996094 8.796875 9.949219 C 9.082031 9.898438 9.378906 9.664062 9.378906 9.136719 C 9.378906 8.855469 9.605469 8.628906 9.886719 8.628906 C 10.167969 8.628906 10.398438 8.855469 10.398438 9.136719 C 10.398438 10.140625 9.757812 10.816406 8.96875 10.949219 C 8.1875 11.078125 7.351562 10.664062 6.988281 9.75 C 6.882812 9.488281 7.011719 9.195312 7.269531 9.089844 Z M 7.269531 9.089844 "/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.11466 3.11809C7.21859 3.37393 7.09545 3.66558 6.83961 3.76952L4.31181 4.79643C4.1233 4.87302 4 5.05619 4 5.25967V11.5C4 11.7761 3.77614 12 3.5 12H2.5C2.22386 12 2 11.7761 2 11.5V4.41827C2 3.90959 2.30825 3.45164 2.77953 3.26018L6.08686 1.91658C6.34269 1.81265 6.63434 1.93579 6.73828 2.19163L7.11466 3.11809ZM10.5 1.99999C10.7761 1.99999 11 2.22384 11 2.49999V10.5C11 10.7761 10.7761 11 10.5 11H9.5C9.22386 11 9 10.7761 9 10.5V9.49999C9 9.22384 8.77614 8.99999 8.5 8.99999H7.5C7.22386 8.99999 7 9.22384 7 9.49999V13.5C7 13.7761 6.77614 14 6.5 14H5.5C5.22386 14 5 13.7761 5 13.5V5.53124C5 5.25509 5.22386 5.03124 5.5 5.03124H6.5C6.77614 5.03124 7 5.25509 7 5.53124V6.49999C7 6.77613 7.22386 6.99999 7.5 6.99999H8.5C8.77614 6.99999 9 6.77613 9 6.49999V2.49999C9 2.22384 9.22386 1.99999 9.5 1.99999H10.5ZM13.5 4.03124C13.7761 4.03124 14 4.2551 14 4.53124L14 11.5847C14 12.0859 13.7006 12.5386 13.2394 12.7349L9.99399 14.1159C9.7399 14.224 9.44626 14.1057 9.33813 13.8516L8.94658 12.9315C8.83845 12.6774 8.95678 12.3837 9.21087 12.2756L11.6958 11.2182C11.8802 11.1397 12 10.9586 12 10.7581L12 4.53124C12 4.2551 12.2238 4.03124 12.5 4.03124L13.5 4.03124Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="5" r="2.75" fill="black"/>
<circle cx="4.75" cy="11" r="2.75" fill="black" fill-opacity="0.5"/>
<circle cx="11.25" cy="11" r="2.75" fill="black" fill-opacity="0.75"/>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View File

@@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00005 4.76556L4.76569 2.74996M6.00005 4.76556L3.75 4.76563M6.00005 4.76556L7.25006 4.7656" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M10.0232 11.2311L11.2675 13.2406M10.0232 11.2311L12.2732 11.2199M10.0232 11.2311L8.7732 11.2373" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
<path d="M9.99025 4.91551L10.9985 2.77781M9.99025 4.91551L8.75599 3.03419M9.99025 4.91551L10.6759 5.9607" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
<path d="M6.0323 11.1009L5.03465 13.2436M6.0323 11.1009L7.27585 12.9761M6.0323 11.1009L5.34151 10.0592" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M11.883 8.19023L14.2466 8.19287M11.883 8.19023L13.0602 6.27268M11.883 8.19023L11.229 9.25547" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M4.12354 7.8356L1.76002 7.84465M4.12354 7.8356L2.95585 9.75894M4.12354 7.8356L4.7723 6.76713" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,7 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.51497 2.02702L1.92042 1.95067C1.69543 1.94589 1.57917 2.21756 1.73796 2.37702L6.24865 6.9068C6.42388 7.08277 6.72071 6.92326 6.67067 6.68002L5.75454 2.22659C5.73103 2.11231 5.63161 2.02949 5.51497 2.02702Z" fill="black" fill-opacity="0.5"/>
<path d="M8.05816 7.38492L12.1366 8.02844C12.3704 8.06532 12.5198 7.78697 12.3599 7.61255L7.30439 2.09814C7.13336 1.91159 6.82522 2.06811 6.87499 2.31624L7.852 7.18714C7.87257 7.28971 7.95483 7.36862 8.05816 7.38492Z" fill="black"/>
<path d="M9.0952 10.9797L11.3824 9.35081C11.564 9.22151 11.4983 8.93722 11.2785 8.90058L8.496 8.43683C8.31974 8.40746 8.17047 8.56712 8.21162 8.74101L8.70689 10.8337C8.74777 11.0064 8.95062 11.0827 9.0952 10.9797Z" fill="black" fill-opacity="0.5"/>
<path d="M5.10282 13.9632L7.59108 12.4532C7.68331 12.3972 7.72923 12.2884 7.70498 12.1832L6.75736 8.07484C6.699 7.8218 6.34133 7.81448 6.27266 8.06491L4.73201 13.6834C4.67223 13.9014 4.90954 14.0805 5.10282 13.9632Z" fill="black"/>
<path d="M11.3183 4.89351L13.1588 7.03149L15.535 6.14302C15.7099 6.07761 15.754 5.85043 15.6161 5.72438L13.7222 3.99219L11.4546 4.48614C11.2695 4.52645 11.1947 4.74995 11.3183 4.89351Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.92096 7.00668C7.87408 7.83549 10.0987 7.48203 10.9376 7.06254C12.8751 6.09381 13.9407 4.39379 12.6407 2.90629C11.0157 1.04692 6.24221 2.49998 4.89844 3.40625C3.55467 4.31252 2.67972 5.53126 2.89071 7.1719C3.1017 8.81254 4.68758 9.7422 6.03128 10.3203C5.38786 10.5616 3.8517 11.0388 3.3125 11.7188C2.71341 12.4742 3.04343 14 4.51577 14C7.15639 14 7.59539 11.1486 7.14847 10.4375C7.88773 10.1295 8.49597 9.96169 9.40138 9.77081C9.63831 9.72087 9.65457 9.46395 9.41295 9.44827C8.80252 9.40864 7.30567 9.8489 6.92096 9.97657C5.78909 9.35157 4.51016 7.93818 4.59378 6.87501C4.68676 5.6928 5.27676 5.07603 6.84508 4.21876C8.01705 3.57813 10.258 3.10695 11.25 3.62501C12.6563 4.35936 10.7875 5.75599 9.92969 6.32031C9.28179 6.74656 8.21971 6.77513 7.22979 6.61435C6.99371 6.576 6.74048 6.84974 6.92096 7.00668ZM5.6719 12.4643C6.35508 11.9894 6.45471 11.1076 6.29955 10.8844C5.76663 11.0874 4.36593 11.9102 4.75111 12.4643C4.90628 12.6875 5.31358 12.7134 5.6719 12.4643Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.25 12H11C10.794 12 10.6764 11.7648 10.8 11.6L11.925 10.1C11.9722 10.037 12.0463 10 12.125 10H12.75C12.8881 10 13 9.88807 13 9.75V6.25C13 6.11193 12.8881 6 12.75 6H12.4045C12.2187 6 12.0978 5.80442 12.1809 5.6382L12.9309 4.1382C12.9732 4.0535 13.0598 4 13.1545 4H14.25C14.3881 4 14.5 4.11193 14.5 4.25V11.75C14.5 11.8881 14.3881 12 14.25 12Z" fill="black"/>
<path d="M1.75 4H5C5.20601 4 5.32361 4.23519 5.2 4.4L4.075 5.9C4.02779 5.96295 3.95369 6 3.875 6H3.25C3.11193 6 3 6.11193 3 6.25V9.75C3 9.88807 3.11193 10 3.25 10H3.59549C3.78134 10 3.90221 10.1956 3.8191 10.3618L3.0691 11.8618C3.02675 11.9465 2.94018 12 2.84549 12H1.75C1.61193 12 1.5 11.8881 1.5 11.75V4.25C1.5 4.11193 1.61193 4 1.75 4Z" fill="black"/>
<path d="M7.55748 6H5.95006C5.74177 6 5.62482 5.76022 5.75306 5.59609L6.92493 4.09609C6.97231 4.03544 7.04498 4 7.12194 4H9.93075C9.97607 4 10.0205 3.98769 10.0594 3.96437L11.6408 3.0155C11.8641 2.88154 12.1179 3.13555 11.9837 3.3587L8.22612 9.6083C8.12629 9.77433 8.24508 9.98591 8.43881 9.98712L10.0039 9.9969C10.2092 9.99818 10.3255 10.2327 10.2023 10.3969L9.075 11.9C9.02779 11.963 8.95369 12 8.875 12H6.55383C6.51835 12 6.48328 12.0076 6.45094 12.0222L4.32473 12.9824C4.10122 13.0833 3.88113 12.8356 4.00771 12.6255L7.77161 6.37903C7.87201 6.2124 7.75202 6 7.55748 6Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

7
assets/icons/list_x.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.33333 8H3" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.6667 4H3" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.6667 12H3" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.6667 6.66663L11 9.33329" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 6.66663L13.6667 9.33329" stroke="#FBF1C7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 579 B

View File

@@ -313,6 +313,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", { "level": 1 }],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", { "level": 2 }],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", { "level": 3 }],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", { "level": 4 }],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", { "level": 5 }],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", { "level": 6 }],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", { "level": 7 }],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", { "level": 8 }],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", { "level": 9 }],
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -505,6 +514,13 @@
"ctrl-enter": "assistant::InlineAssist"
}
},
{
"context": "ProposedChangesEditor",
"bindings": {
"ctrl-shift-y": "editor::ApplyDiffHunk",
"ctrl-alt-a": "editor::ApplyAllDiffHunks"
}
},
{
"context": "Editor && jupyter && !ContextEditor",
"bindings": {
@@ -516,6 +532,7 @@
"context": "ContextEditor > Editor",
"bindings": {
"ctrl-enter": "assistant::Assist",
"ctrl-shift-enter": "assistant::Edit",
"ctrl-s": "workspace::Save",
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",

View File

@@ -201,6 +201,7 @@
"context": "ContextEditor > Editor",
"bindings": {
"cmd-enter": "assistant::Assist",
"cmd-shift-enter": "assistant::Edit",
"cmd-s": "workspace::Save",
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
@@ -350,6 +351,15 @@
"cmd-k cmd-l": "editor::ToggleFold",
"cmd-k cmd-[": "editor::FoldRecursive",
"cmd-k cmd-]": "editor::UnfoldRecursive",
"cmd-k cmd-1": ["editor::FoldAtLevel", { "level": 1 }],
"cmd-k cmd-2": ["editor::FoldAtLevel", { "level": 2 }],
"cmd-k cmd-3": ["editor::FoldAtLevel", { "level": 3 }],
"cmd-k cmd-4": ["editor::FoldAtLevel", { "level": 4 }],
"cmd-k cmd-5": ["editor::FoldAtLevel", { "level": 5 }],
"cmd-k cmd-6": ["editor::FoldAtLevel", { "level": 6 }],
"cmd-k cmd-7": ["editor::FoldAtLevel", { "level": 7 }],
"cmd-k cmd-8": ["editor::FoldAtLevel", { "level": 8 }],
"cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -538,6 +548,13 @@
"ctrl-enter": "assistant::InlineAssist"
}
},
{
"context": "ProposedChangesEditor",
"bindings": {
"cmd-shift-y": "editor::ApplyDiffHunk",
"cmd-shift-a": "editor::ApplyAllDiffHunks"
}
},
{
"context": "PromptEditor",
"bindings": {

View File

@@ -34,7 +34,7 @@
"cmd-]": "pane::GoForward",
"alt-f7": "editor::FindAllReferences",
"cmd-alt-f7": "editor::FindAllReferences",
"cmd-b": "editor::GoToDefinition",
"cmd-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
"cmd-alt-b": "editor::GoToDefinitionSplit",
"cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
@@ -64,7 +64,8 @@
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle",
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
}

View File

@@ -1,312 +0,0 @@
<task_description>
# Code Change Workflow
Your task is to guide the user through code changes using a series of steps. Each step should describe a high-level change, which can consist of multiple edits to distinct locations in the codebase.
## Output Example
Provide output as XML, with the following format:
<step>
Update the Person struct to store an age
```rust
struct Person {
// existing fields...
age: u8,
height: f32,
// existing fields...
}
impl Person {
fn age(&self) -> u8 {
self.age
}
}
```
<edit>
<path>src/person.rs</path>
<operation>insert_before</operation>
<search>height: f32,</search>
<description>Add the age field</description>
</edit>
<edit>
<path>src/person.rs</path>
<operation>insert_after</operation>
<search>impl Person {</search>
<description>Add the age getter</description>
</edit>
</step>
## Output Format
First, each `<step>` must contain a written description of the change that should be made. The description should begin with a high-level overview, and can contain markdown code blocks as well. The description should be self-contained and actionable.
After the description, each `<step>` must contain one or more `<edit>` tags, each of which refer to a specific range in a source file. Each `<edit>` tag must contain the following child tags:
### `<path>` (required)
This tag contains the path to the file that will be changed. It can be an existing path, or a path that should be created.
### `<search>` (optional)
This tag contains a search string to locate in the source file, e.g. `pub fn baz() {`. If not provided, the new content will be inserted at the top of the file. Make sure to produce a string that exists in the source file and that isn't ambiguous. When there's ambiguity, add more lines to the search to eliminate it.
### `<description>` (required)
This tag contains a single-line description of the edit that should be made at the given location.
### `<operation>` (required)
This tag indicates what type of change should be made, relative to the given location. It can be one of the following:
- `update`: Rewrites the specified string entirely based on the given description.
- `create`: Creates a new file with the given path based on the provided description.
- `insert_before`: Inserts new text based on the given description before the specified search string.
- `insert_after`: Inserts new text based on the given description after the specified search string.
- `delete`: Deletes the specified string from the containing file.
<guidelines>
- There's no need to describe *what* to do, just *where* to do it.
- Only reference locations that actually exist (unless you're creating a file).
- If creating a file, assume any subsequent updates are included at the time of creation.
- Don't create and then update a file. Always create new files in one hot.
- Prefer multiple edits to smaller regions, as opposed to one big edit to a larger region.
- Don't produce edits that intersect each other. In that case, merge them into a bigger edit.
- Never nest an edit with another edit. Never include CDATA. All edits are leaf nodes.
- Descriptions are required for all edits except delete.
- When generating multiple edits, ensure the descriptions are specific to each individual operation.
- Avoid referring to the search string in the description. Focus on the change to be made, not the location where it's made. That's implicit with the `search` string you provide.
- Don't generate multiple edits at the same location. Instead, combine them together in a single edit with a succinct combined description.
- Always ensure imports are added if you're referencing symbols that are not in scope.
</guidelines>
Here are some concrete examples.
<example>
<message role="user">
```rs src/shapes.rs
pub mod rectangle;
pub mod circle;
```
```rs src/shapes/rectangle.rs
pub struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
pub fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
}
```
```rs src/shapes/circle.rs
pub struct Circle {
radius: f64,
}
impl Circle {
pub fn new(radius: f64) -> Self {
Circle { radius }
}
}
```
Update all shapes to store their origin as an (x, y) tuple and implement Display.
</message>
<message role="assistant">
We'll need to update both the rectangle and circle modules.
<step>
Add origin fields to both shape types.
```rust
struct Rectangle {
// existing fields ...
origin: (f64, f64),
}
```
```rust
struct Circle {
// existing fields ...
origin: (f64, f64),
}
```
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>insert_before</operation>
<search>
width: f64,
height: f64,
</search>
<description>Add the origin field to Rectangle</description>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_before</operation>
<search>
radius: f64,
</search>
<description>Add the origin field to Circle</description>
</edit>
<step>
Update both shape's constructors to take an origin.
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>update</operation>
<search>
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
</search>
<description>Update the Rectangle new function to take an origin</description>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>update</operation>
<search>
fn new(radius: f64) -> Self {
Circle { radius }
}
</search>
<description>Update the Circle new function to take an origin</description>
</edit>
</step>
<step>
Implement Display for both shapes
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>insert_before</operation>
<search>
struct Rectangle {
</search>
<description>Add an import for the `std::fmt` module</description>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>insert_after</operation>
<search>
Rectangle { width, height }
}
}
</search>
<description>Add a Display implementation for Rectangle</description>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_before</operation>
<search>
struct Circle {
</search>
<description>Add an import for the `std::fmt` module</description>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_after</operation>
<search>
Circle { radius }
}
}
</search>
<description>Add a Display implementation for Circle</description>
</edit>
</step>
</message>
</example>
<example number=2>
<message role="user">
```rs src/user.rs
struct User {
pub name: String,
age: u32,
email: String,
}
impl User {
fn new(name: String, age: u32, email: String) -> Self {
User { name, age, email }
}
pub fn print_info(&self) {
todo!()
}
}
```
Let's print all the user information and delete the email field.
</message>
<message role="assistant">
<step>
Update the 'print_info' method to print user information
```rust
impl User {
// ... other methods ...
pub fn print_info(&self) {
println!("Name: {name}, Age: {age}", name = self.name, age = self.age);
}
}
```
<edit>
<path>src/user.rs</path>
<operation>update</operation>
<search>
pub fn print_info(&self) {
todo!()
}
</search>
<description>Print all the user information</description>
</edit>
</step>
<step>
Remove the 'email' field from the User struct
<edit>
<path>src/user.rs</path>
<operation>delete</operation>
<search>
email: String,
</search>
</edit>
<edit>
<path>src/user.rs</path>
<operation>update</operation>
<symbol>
fn new(name: String, age: u32, email: String) -> Self {
User { name, age, email }
}
</symbol>
<description>Remove email parameter from new method</description>
</edit>
</step>
</message>
</example>
You should think step by step. When possible, produce smaller, coherent logical steps as opposed to one big step that combines lots of heterogeneous edits.
</task_description>

View File

@@ -1,496 +0,0 @@
<overview>
Your task is to map a step from a workflow to locations in source code where code needs to be changed to fulfill that step.
Given a workflow containing background context plus a series of <step> tags, you will resolve *one* of these step tags to resolve to one or more locations in the code.
With each location, you will produce a brief, one-line description of the changes to be made.
<guidelines>
- There's no need to describe *what* to do, just *where* to do it.
- Only reference locations that actually exist (unless you're creating a file).
- If creating a file, assume any subsequent updates are included at the time of creation.
- Don't create and then update a file. Always create new files in shot.
- Prefer updating symbols lower in the syntax tree if possible.
- Never include suggestions on a parent symbol and one of its children in the same suggestions block.
- Never nest an operation with another operation or include CDATA or other content. All suggestions are leaf nodes.
- Descriptions are required for all suggestions except delete.
- When generating multiple suggestions, ensure the descriptions are specific to each individual operation.
- Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide.
- Don't generate multiple suggestions at the same location. Instead, combine them together in a single operation with a succinct combined description.
- To add imports respond with a suggestion where the `"symbol"` key is set to `"#imports"`
</guidelines>
</overview>
<examples>
<example>
<workflow_context>
<message role="user">
```rs src/rectangle.rs
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
}
```
We need to add methods to calculate the area and perimeter of the rectangle. Can you help with that?
</message>
<message role="assistant">
Sure, I can help with that!
<step>Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct</step>
<step>Implement the 'Display' trait for the Rectangle struct</step>
</message>
</workflow_context>
<step_to_resolve>
Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct
</step_to_resolve>
<incorrect_output reason="NEVER append multiple children at the same location.">
{
"title": "Add Rectangle methods",
"suggestions": [
{
"kind": "AppendChild",
"path": "src/shapes.rs",
"symbol": "impl Rectangle",
"description": "Add calculate_area method"
},
{
"kind": "AppendChild",
"path": "src/shapes.rs",
"symbol": "impl Rectangle",
"description": "Add calculate_perimeter method"
}
]
}
</incorrect_output>
<correct_output>
{
"title": "Add Rectangle methods",
"suggestions": [
{
"kind": "AppendChild",
"path": "src/shapes.rs",
"symbol": "impl Rectangle",
"description": "Add calculate area and perimeter methods"
}
]
}
</correct_output>
<step_to_resolve>
Implement the 'Display' trait for the Rectangle struct
</step_to_resolve>
<output>
{
"title": "Implement Display for Rectangle",
"suggestions": [
{
"kind": "InsertSiblingAfter",
"path": "src/shapes.rs",
"symbol": "impl Rectangle",
"description": "Implement Display trait for Rectangle"
}
]
}
</output>
<example>
<workflow_context>
<message role="user">
```rs src/user.rs
struct User {
pub name: String,
age: u32,
email: String,
}
impl User {
fn new(name: String, age: u32, email: String) -> Self {
User { name, age, email }
}
pub fn print_info(&self) {
println!("Name: {}, Age: {}, Email: {}", self.name, self.age, self.email);
}
}
```
</message>
<message role="assistant">
Certainly!
<step>Update the 'print_info' method to use formatted output</step>
<step>Remove the 'email' field from the User struct</step>
</message>
</workflow_context>
<step_to_resolve>
Update the 'print_info' method to use formatted output
</step_to_resolve>
<output>
{
"title": "Use formatted output",
"suggestions": [
{
"kind": "Update",
"path": "src/user.rs",
"symbol": "impl User pub fn print_info",
"description": "Use formatted output"
}
]
}
</output>
<step_to_resolve>
Remove the 'email' field from the User struct
</step_to_resolve>
<output>
{
"title": "Remove email field",
"suggestions": [
{
"kind": "Delete",
"path": "src/user.rs",
"symbol": "struct User email"
}
]
}
</output>
</example>
<example>
<workflow_context>
<message role="user">
```rs src/vehicle.rs
struct Vehicle {
make: String,
model: String,
year: u32,
}
impl Vehicle {
fn new(make: String, model: String, year: u32) -> Self {
Vehicle { make, model, year }
}
fn print_year(&self) {
println!("Year: {}", self.year);
}
}
```
</message>
<message role="assistant">
<step>Add a 'use std::fmt;' statement at the beginning of the file</step>
<step>Add a new method 'start_engine' in the Vehicle impl block</step>
</message>
</workflow_context>
<step_to_resolve>
Add a 'use std::fmt;' statement at the beginning of the file
</step_to_resolve>
<output>
{
"title": "Add use std::fmt statement",
"suggestions": [
{
"kind": "PrependChild",
"path": "src/vehicle.rs",
"symbol": "#imports",
"description": "Add 'use std::fmt' statement"
}
]
}
</output>
<step_to_resolve>
Add a new method 'start_engine' in the Vehicle impl block
</step_to_resolve>
<output>
{
"title": "Add start_engine method",
"suggestions": [
{
"kind": "InsertSiblingAfter",
"path": "src/vehicle.rs",
"symbol": "impl Vehicle fn new",
"description": "Add start_engine method"
}
]
}
</output>
</example>
<example>
<workflow_context>
<message role="user">
```rs src/employee.rs
struct Employee {
name: String,
position: String,
salary: u32,
department: String,
}
impl Employee {
fn new(name: String, position: String, salary: u32, department: String) -> Self {
Employee { name, position, salary, department }
}
fn print_details(&self) {
println!("Name: {}, Position: {}, Salary: {}, Department: {}",
self.name, self.position, self.salary, self.department);
}
fn give_raise(&mut self, amount: u32) {
self.salary += amount;
}
}
```
</message>
<message role="assistant">
<step>Make salary an f32</step>
<step>Remove the 'department' field and update the 'print_details' method</step>
</message>
</workflow_context>
<step_to_resolve>
Make salary an f32
</step_to_resolve>
<incorrect_output reason="NEVER include suggestions on a parent symbol and one of its children in the same suggestions block.">
{
"title": "Change salary to f32",
"suggestions": [
{
"kind": "Update",
"path": "src/employee.rs",
"symbol": "struct Employee",
"description": "Change the type of salary to an f32"
},
{
"kind": "Update",
"path": "src/employee.rs",
"symbol": "struct Employee salary",
"description": "Change the type to an f32"
}
]
}
</incorrect_output>
<correct_output>
{
"title": "Change salary to f32",
"suggestions": [
{
"kind": "Update",
"path": "src/employee.rs",
"symbol": "struct Employee salary",
"description": "Change the type to an f32"
}
]
}
</correct_output>
<step_to_resolve>
Remove the 'department' field and update the 'print_details' method
</step_to_resolve>
<output>
{
"title": "Remove department",
"suggestions": [
{
"kind": "Delete",
"path": "src/employee.rs",
"symbol": "struct Employee department"
},
{
"kind": "Update",
"path": "src/employee.rs",
"symbol": "impl Employee fn print_details",
"description": "Don't print the 'department' field"
}
]
}
</output>
</example>
<example>
<workflow_context>
<message role="user">
```rs src/game.rs
struct Player {
name: String,
health: i32,
pub score: u32,
}
impl Player {
pub fn new(name: String) -> Self {
Player { name, health: 100, score: 0 }
}
}
struct Game {
players: Vec<Player>,
}
impl Game {
fn new() -> Self {
Game { players: Vec::new() }
}
}
```
</message>
<message role="assistant">
<step>Add a 'level' field to Player and update the 'new' method</step>
</message>
</workflow_context>
<step_to_resolve>
Add a 'level' field to Player and update the 'new' method
</step_to_resolve>
<output>
{
"title": "Add level field to Player",
"suggestions": [
{
"kind": "InsertSiblingAfter",
"path": "src/game.rs",
"symbol": "struct Player pub score",
"description": "Add level field to Player"
},
{
"kind": "Update",
"path": "src/game.rs",
"symbol": "impl Player pub fn new",
"description": "Initialize level in new method"
}
]
}
</output>
</example>
<example>
<workflow_context>
<message role="user">
```rs src/config.rs
use std::collections::HashMap;
struct Config {
settings: HashMap<String, String>,
}
impl Config {
fn new() -> Self {
Config { settings: HashMap::new() }
}
}
```
</message>
<message role="assistant">
<step>Add a 'load_from_file' method to Config and import necessary modules</step>
</message>
</workflow_context>
<step_to_resolve>
Add a 'load_from_file' method to Config and import necessary modules
</step_to_resolve>
<output>
{
"title": "Add load_from_file method",
"suggestions": [
{
"kind": "PrependChild",
"path": "src/config.rs",
"symbol": "#imports",
"description": "Import std::fs and std::io modules"
},
{
"kind": "AppendChild",
"path": "src/config.rs",
"symbol": "impl Config",
"description": "Add load_from_file method"
}
]
}
</output>
</example>
<example>
<workflow_context>
<message role="user">
```rs src/database.rs
pub(crate) struct Database {
connection: Connection,
}
impl Database {
fn new(url: &str) -> Result<Self, Error> {
let connection = Connection::connect(url)?;
Ok(Database { connection })
}
async fn query(&self, sql: &str) -> Result<Vec<Row>, Error> {
self.connection.query(sql, &[])
}
}
```
</message>
<message role="assistant">
<step>Add error handling to the 'query' method and create a custom error type</step>
</message>
</workflow_context>
<step_to_resolve>
Add error handling to the 'query' method and create a custom error type
</step_to_resolve>
<output>
{
"title": "Add error handling to query",
"suggestions": [
{
"kind": "PrependChild",
"path": "src/database.rs",
"description": "Import necessary error handling modules"
},
{
"kind": "InsertSiblingBefore",
"path": "src/database.rs",
"symbol": "pub(crate) struct Database",
"description": "Define custom DatabaseError enum"
},
{
"kind": "Update",
"path": "src/database.rs",
"symbol": "impl Database async fn query",
"description": "Implement error handling in query method"
}
]
}
</output>
</example>
</examples>
Now generate the suggestions for the following step:
<workflow_context>
{{{workflow_context}}}
</workflow_context>
<step_to_resolve>
{{{step_to_resolve}}}
</step_to_resolve>

View File

@@ -0,0 +1,206 @@
<task_description>
The user of a code editor wants to make a change to their codebase.
You must describe the change using the following XML structure:
- <patch> - A group of related code changes.
Child tags:
- <title> (required) - A high-level description of the changes. This should be as short
as possible, possibly using common abbreviations.
- <edit> (1 or more) - An edit to make at a particular range within a file.
Includes the following child tags:
- <path> (required) - The path to the file that will be changed.
- <description> (optional) - An arbitrarily-long comment that describes the purpose
of this edit.
- <old_text> (optional) - An excerpt from the file's current contents that uniquely
identifies a range within the file where the edit should occur. If this tag is not
specified, then the entire file will be used as the range.
- <new_text> (required) - The new text to insert into the file.
- <operation> (required) - The type of change that should occur at the given range
of the file. Must be one of the following values:
- `update`: Replaces the entire range with the new text.
- `insert_before`: Inserts the new text before the range.
- `insert_after`: Inserts new text after the range.
- `create`: Creates a new file with the given path and the new text.
- `delete`: Deletes the specified range from the file.
<guidelines>
- Never provide multiple edits whose ranges intersect each other. Instead, merge them into one edit.
- Prefer multiple edits to smaller, disjoint ranges, rather than one edit to a larger range.
- There's no need to escape angle brackets within XML tags.
- Always ensure imports are added if you're referencing symbols that are not in scope.
</guidelines>
Here are some concrete examples.
<example>
<message role="user">
```rs src/shapes.rs
pub mod rectangle;
pub mod circle;
```
```rs src/shapes/rectangle.rs
pub struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
pub fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
}
```
```rs src/shapes/circle.rs
pub struct Circle {
radius: f64,
}
impl Circle {
pub fn new(radius: f64) -> Self {
Circle { radius }
}
}
```
Update all shapes to store their origin as an (x, y) tuple and implement Display.
</message>
<message role="assistant">
We'll need to update both the rectangle and circle modules.
<patch>
<title>Add origins and display impls to shapes</title>
<edit>
<path>src/shapes/rectangle.rs</path>
<description>Add the origin field to Rectangle struct</description>
<operation>insert_after</operation>
<old_text>
pub struct Rectangle {
</old_text>
<new_text>
origin: (f64, f64),
</new_text>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>update</operation>
<old_text>
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
</old_text>
<new_text>
fn new(origin: (f64, f64), width: f64, height: f64) -> Self {
Rectangle { origin, width, height }
}
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<description>Add the origin field to Circle struct</description>
<operation>insert_after</operation>
<old_text>
pub struct Circle {
radius: f64,
</old_text>
<new_text>
origin: (f64, f64),
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>update</operation>
<old_text>
fn new(radius: f64) -> Self {
Circle { radius }
}
</old_text>
<new_text>
fn new(origin: (f64, f64), radius: f64) -> Self {
Circle { origin, radius }
}
</new_text>
</edit>
</step>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>insert_before</operation>
<old_text>
struct Rectangle {
</old_text>
<new_text>
use std::fmt;
</new_text>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<description>
Add a manual Display implementation for Rectangle.
Currently, this is the same as a derived Display implementation.
</description>
<operation>insert_after</operation>
<old_text>
Rectangle { width, height }
}
}
</old_text>
<new_text>
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.format_struct(f, "Rectangle")
.field("origin", &self.origin)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_before</operation>
<old_text>
struct Circle {
</old_text>
<new_text>
use std::fmt;
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_after</operation>
<old_text>
Circle { radius }
}
}
</old_text>
<new_text>
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.format_struct(f, "Rectangle")
.field("origin", &self.origin)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
</new_text>
</edit>
</patch>
</message>
</example>
</task_description>

View File

@@ -369,6 +369,17 @@
/// 5. Never show the scrollbar:
/// "never"
"show": null
},
// Settings related to indent guides in the project panel.
"indent_guides": {
// When to show indent guides in the project panel.
// This setting can take two values:
//
// 1. Always show indent guides:
// "always"
// 2. Never show indent guides:
// "never"
"show": "always"
}
},
"outline_panel": {
@@ -392,7 +403,35 @@
"auto_reveal_entries": true,
/// Whether to fold directories automatically
/// when a directory has only one directory inside.
"auto_fold_dirs": true
"auto_fold_dirs": true,
// Settings related to indent guides in the outline panel.
"indent_guides": {
// When to show indent guides in the outline panel.
// This setting can take two values:
//
// 1. Always show indent guides:
// "always"
// 2. Never show indent guides:
// "never"
"show": "always"
},
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take four values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default):
/// "auto"
/// 3. Match the system's configured behavior:
/// "system"
/// 4. Always show the scrollbar:
/// "always"
/// 5. Never show the scrollbar:
/// "never"
"show": null
}
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
@@ -712,10 +751,10 @@
// May take 2 values:
// 1. Rely on default platform handling of option key, on macOS
// this means generating certain unicode characters
// "option_to_meta": false,
// "option_as_meta": false,
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
// "option_to_meta": true,
"option_as_meta": true,
// "option_as_meta": true,
"option_as_meta": false,
// Whether or not selecting text in the terminal will automatically
// copy to the system clipboard.
"copy_on_select": false,
@@ -775,6 +814,7 @@
"tasks": {
"variables": {}
},
"toolchain": { "name": "default", "path": "default" },
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
// use those languages.
@@ -803,7 +843,7 @@
/// You can override this to use a version of node that is not in $PATH with:
/// {
/// "node": {
/// "node_path": "/path/to/node"
/// "path": "/path/to/node"
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
/// }
/// }
@@ -1099,13 +1139,13 @@
// }
"command_aliases": {},
// ssh_connections is an array of ssh connections.
// By default this setting is null, which disables the direct ssh connection support.
// You can configure these from `project: Open Remote` in the command palette.
// Zed's ssh support will pull configuration from your ~/.ssh too.
// Examples:
// [
// {
// "host": "example-box",
// // "port": 22, "username": "test", "args": ["-i", "/home/user/.ssh/id_rsa"]
// "projects": [
// {
// "paths": ["/home/user/code/zed"]
@@ -1113,7 +1153,7 @@
// ]
// }
// ]
"ssh_connections": null,
"ssh_connections": [],
// Configures the Context Server Protocol binaries
//
// Examples:

View File

@@ -23,6 +23,7 @@ language.workspace = true
project.workspace = true
smallvec.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
[dev-dependencies]

View File

@@ -13,7 +13,8 @@ use language::{
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle};
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
use util::truncate_and_trailoff;
use workspace::{item::ItemHandle, StatusItemView, Workspace};
actions!(activity_indicator, [ShowErrorMessage]);
@@ -446,6 +447,8 @@ impl ActivityIndicator {
impl EventEmitter<Event> for ActivityIndicator {}
const MAX_MESSAGE_LEN: usize = 50;
impl Render for ActivityIndicator {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let result = h_flex()
@@ -456,6 +459,7 @@ impl Render for ActivityIndicator {
return result;
};
let this = cx.view().downgrade();
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
result.gap_2().child(
PopoverMenu::new("activity-indicator-popover")
.trigger(
@@ -464,7 +468,21 @@ impl Render for ActivityIndicator {
.id("activity-indicator-status")
.gap_2()
.children(content.icon)
.child(Label::new(content.message).size(LabelSize::Small))
.map(|button| {
if truncate_content {
button
.child(
Label::new(truncate_and_trailoff(
&content.message,
MAX_MESSAGE_LEN,
))
.size(LabelSize::Small),
)
.tooltip(move |cx| Tooltip::text(&content.message, cx))
} else {
button.child(Label::new(content.message).size(LabelSize::Small))
}
})
.when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, cx| {
handler(this, cx);

View File

@@ -29,13 +29,13 @@ pub struct AnthropicModelCacheConfiguration {
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
#[default]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-20240620")]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-20240229")]
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-20240229")]
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
Claude3Sonnet,
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-20240307")]
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-latest")]
Claude3Haiku,
#[serde(rename = "custom")]
Custom {
@@ -69,10 +69,10 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
Model::Claude3Opus => "claude-3-opus-20240229",
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
Model::Claude3Haiku => "claude-3-haiku-20240307",
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-latest",
Model::Claude3Haiku => "claude-3-haiku-latest",
Self::Custom { name, .. } => name,
}
}

View File

@@ -97,6 +97,7 @@ language = { workspace = true, features = ["test-support"] }
language_model = { workspace = true, features = ["test-support"] }
languages = { workspace = true, features = ["test-support"] }
log.workspace = true
pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
serde_json_lenient.workspace = true

View File

@@ -6,6 +6,7 @@ mod context;
pub mod context_store;
mod inline_assistant;
mod model_selector;
mod patch;
mod prompt_library;
mod prompts;
mod slash_command;
@@ -14,7 +15,6 @@ pub mod slash_command_settings;
mod streaming_diff;
mod terminal_inline_assistant;
mod tools;
mod workflow;
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
use assistant_settings::AssistantSettings;
@@ -35,6 +35,7 @@ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
};
pub(crate) use model_selector::*;
pub use patch::*;
pub use prompts::PromptBuilder;
use prompts::PromptLoadingParams;
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
@@ -44,13 +45,11 @@ use slash_command::{
auto_command, cargo_workspace_command, context_server_command, default_command, delta_command,
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
prompt_command, search_command, symbols_command, tab_command, terminal_command,
workflow_command,
};
use std::path::PathBuf;
use std::sync::Arc;
pub(crate) use streaming_diff::*;
use util::ResultExt;
pub use workflow::*;
use crate::slash_command_settings::SlashCommandSettings;
@@ -58,6 +57,7 @@ actions!(
assistant,
[
Assist,
Edit,
Split,
CopyCode,
CycleMessageRole,
@@ -297,25 +297,64 @@ fn register_context_server_handlers(cx: &mut AppContext) {
return;
};
if let Some(prompts) = protocol.list_prompts().await.log_err() {
for prompt in prompts
.into_iter()
.filter(context_server_command::acceptable_prompt)
{
log::info!(
"registering context server command: {:?}",
prompt.name
);
context_server_registry.register_command(
server.id.clone(),
prompt.name.as_str(),
);
slash_command_registry.register_command(
context_server_command::ContextServerSlashCommand::new(
&server, prompt,
),
true,
);
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
if let Some(prompts) = protocol.list_prompts().await.log_err() {
for prompt in prompts
.into_iter()
.filter(context_server_command::acceptable_prompt)
{
log::info!(
"registering context server command: {:?}",
prompt.name
);
context_server_registry.register_command(
server.id.clone(),
prompt.name.as_str(),
);
slash_command_registry.register_command(
context_server_command::ContextServerSlashCommand::new(
&server, prompt,
),
true,
);
}
}
}
})
.detach();
}
},
);
cx.update_model(
&manager,
|manager: &mut context_servers::manager::ContextServerManager, cx| {
let tool_registry = ToolRegistry::global(cx);
let context_server_registry = ContextServerRegistry::global(cx);
if let Some(server) = manager.get_server(server_id) {
cx.spawn(|_, _| async move {
let Some(protocol) = server.client.read().clone() else {
return;
};
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
if let Some(tools) = protocol.list_tools().await.log_err() {
for tool in tools.tools {
log::info!(
"registering context server tool: {:?}",
tool.name
);
context_server_registry.register_tool(
server.id.clone(),
tool.name.as_str(),
);
tool_registry.register_tool(
tools::context_server_tool::ContextServerTool::new(
server.id.clone(),
tool
),
);
}
}
}
})
@@ -333,6 +372,14 @@ fn register_context_server_handlers(cx: &mut AppContext) {
context_server_registry.unregister_command(&server_id, &command_name);
}
}
if let Some(tools) = context_server_registry.get_tools(server_id) {
let tool_registry = ToolRegistry::global(cx);
for tool_name in tools {
tool_registry.unregister_tool_by_name(&tool_name);
context_server_registry.unregister_tool(&server_id, &tool_name);
}
}
}
},
)
@@ -393,12 +440,9 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
slash_command_registry.register_command(now_command::NowSlashCommand, false);
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
if let Some(prompt_builder) = prompt_builder {
slash_command_registry.register_command(
workflow_command::WorkflowSlashCommand::new(prompt_builder.clone()),
true,
);
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
let slash_command_registry = slash_command_registry.clone();
move |is_enabled, _cx| {

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ use std::sync::Arc;
use ::open_ai::Model as OpenAiModel;
use anthropic::Model as AnthropicModel;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::{AppContext, Pixels};
use language_model::provider::open_ai;
@@ -61,6 +62,13 @@ pub struct AssistantSettings {
pub default_model: LanguageModelSelection,
pub inline_alternatives: Vec<LanguageModelSelection>,
pub using_outdated_settings_version: bool,
pub enable_experimental_live_diffs: bool,
}
impl AssistantSettings {
pub fn are_live_diffs_enabled(&self, cx: &AppContext) -> bool {
cx.is_staff() || self.enable_experimental_live_diffs
}
}
/// Assistant panel settings
@@ -238,6 +246,7 @@ impl AssistantSettingsContent {
}
}),
inline_alternatives: None,
enable_experimental_live_diffs: None,
},
VersionedAssistantSettingsContent::V2(settings) => settings.clone(),
},
@@ -257,6 +266,7 @@ impl AssistantSettingsContent {
.to_string(),
}),
inline_alternatives: None,
enable_experimental_live_diffs: None,
},
}
}
@@ -373,6 +383,7 @@ impl Default for VersionedAssistantSettingsContent {
default_height: None,
default_model: None,
inline_alternatives: None,
enable_experimental_live_diffs: None,
})
}
}
@@ -403,6 +414,10 @@ pub struct AssistantSettingsContentV2 {
default_model: Option<LanguageModelSelection>,
/// Additional models with which to generate alternatives when performing inline assists.
inline_alternatives: Option<Vec<LanguageModelSelection>>,
/// Enable experimental live diffs in the assistant panel.
///
/// Default: false
enable_experimental_live_diffs: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
@@ -525,7 +540,10 @@ impl Settings for AssistantSettings {
);
merge(&mut settings.default_model, value.default_model);
merge(&mut settings.inline_alternatives, value.inline_alternatives);
// merge(&mut settings.infer_context, value.infer_context); TODO re-enable this once we ship context inference
merge(
&mut settings.enable_experimental_live_diffs,
value.enable_experimental_live_diffs,
);
}
Ok(settings)
@@ -584,6 +602,7 @@ mod tests {
dock: None,
default_width: None,
default_height: None,
enable_experimental_live_diffs: None,
}),
)
},

View File

@@ -2,12 +2,13 @@
mod context_tests;
use crate::{
prompts::PromptBuilder, slash_command::SlashCommandLine, MessageId, MessageStatus,
WorkflowStep, WorkflowStepEdit, WorkflowStepResolution, WorkflowSuggestionGroup,
prompts::PromptBuilder,
slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
SlashCommandOutput, SlashCommandOutputSection, SlashCommandRegistry,
SlashCommandOutput, SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
};
use assistant_tool::ToolRegistry;
use client::{self, proto, telemetry::Telemetry};
@@ -15,13 +16,10 @@ use clock::ReplicaId;
use collections::{HashMap, HashSet};
use feature_flags::{FeatureFlag, FeatureFlagAppExt};
use fs::{Fs, RemoveOptions};
use futures::{
future::{self, Shared},
FutureExt, StreamExt,
};
use futures::{future::Shared, FutureExt, StreamExt};
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage,
SharedString, Subscription, Task,
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString,
Subscription, Task,
};
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
@@ -38,7 +36,7 @@ use project::Project;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
cmp::{self, max, Ordering},
cmp::{max, Ordering},
fmt::Debug,
iter, mem,
ops::Range,
@@ -69,6 +67,14 @@ impl ContextId {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestType {
/// Request a normal chat response from the model.
Chat,
/// Add a preamble to the message, which tells the model to return a structured response that suggests edits.
SuggestEdits,
}
#[derive(Clone, Debug)]
pub enum ContextOperation {
InsertMessage {
@@ -300,7 +306,7 @@ pub enum ContextEvent {
MessagesEdited,
SummaryChanged,
StreamedCompletion,
WorkflowStepsUpdated {
PatchesUpdated {
removed: Vec<Range<language::Anchor>>,
updated: Vec<Range<language::Anchor>>,
},
@@ -454,13 +460,14 @@ pub struct XmlTag {
#[derive(Copy, Clone, Debug, strum::EnumString, PartialEq, Eq, strum::AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum XmlTagKind {
Step,
Patch,
Title,
Edit,
Path,
Search,
Within,
Operation,
Description,
OldText,
NewText,
Operation,
}
pub struct Context {
@@ -490,7 +497,7 @@ pub struct Context {
_subscriptions: Vec<Subscription>,
telemetry: Option<Arc<Telemetry>>,
language_registry: Arc<LanguageRegistry>,
workflow_steps: Vec<WorkflowStep>,
patches: Vec<AssistantPatch>,
xml_tags: Vec<XmlTag>,
project: Option<Model<Project>>,
prompt_builder: Arc<PromptBuilder>,
@@ -506,7 +513,7 @@ impl ContextAnnotation for PendingSlashCommand {
}
}
impl ContextAnnotation for WorkflowStep {
impl ContextAnnotation for AssistantPatch {
fn range(&self) -> &Range<language::Anchor> {
&self.range
}
@@ -591,7 +598,7 @@ impl Context {
telemetry,
project,
language_registry,
workflow_steps: Vec::new(),
patches: Vec::new(),
xml_tags: Vec::new(),
prompt_builder,
};
@@ -929,48 +936,49 @@ impl Context {
self.summary.as_ref()
}
pub(crate) fn workflow_step_containing(
pub(crate) fn patch_containing(
&self,
offset: usize,
position: Point,
cx: &AppContext,
) -> Option<&WorkflowStep> {
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self
.workflow_steps
.binary_search_by(|step| {
let step_range = step.range.to_offset(&buffer);
if offset < step_range.start {
Ordering::Greater
} else if offset > step_range.end {
Ordering::Less
} else {
Ordering::Equal
}
})
.ok()?;
Some(&self.workflow_steps[index])
let index = self.patches.binary_search_by(|patch| {
let patch_range = patch.range.to_point(&buffer);
if position < patch_range.start {
Ordering::Greater
} else if position > patch_range.end {
Ordering::Less
} else {
Ordering::Equal
}
});
if let Ok(ix) = index {
Some(&self.patches[ix])
} else {
None
}
}
pub fn workflow_step_ranges(&self) -> impl Iterator<Item = Range<language::Anchor>> + '_ {
self.workflow_steps.iter().map(|step| step.range.clone())
pub fn patch_ranges(&self) -> impl Iterator<Item = Range<language::Anchor>> + '_ {
self.patches.iter().map(|patch| patch.range.clone())
}
pub(crate) fn workflow_step_for_range(
pub(crate) fn patch_for_range(
&self,
range: &Range<language::Anchor>,
cx: &AppContext,
) -> Option<&WorkflowStep> {
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.workflow_step_index_for_range(range, buffer).ok()?;
Some(&self.workflow_steps[index])
let index = self.patch_index_for_range(range, buffer).ok()?;
Some(&self.patches[index])
}
fn workflow_step_index_for_range(
fn patch_index_for_range(
&self,
tagged_range: &Range<text::Anchor>,
buffer: &text::BufferSnapshot,
) -> Result<usize, usize> {
self.workflow_steps
self.patches
.binary_search_by(|probe| probe.range.cmp(&tagged_range, buffer))
}
@@ -982,6 +990,20 @@ impl Context {
&self.slash_command_output_sections
}
pub fn contains_files(&self, cx: &AppContext) -> bool {
let buffer = self.buffer.read(cx);
self.slash_command_output_sections.iter().any(|section| {
section.is_valid(buffer)
&& section
.metadata
.as_ref()
.and_then(|metadata| {
serde_json::from_value::<FileCommandMetadata>(metadata.clone()).ok()
})
.is_some()
})
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect()
}
@@ -1018,8 +1040,6 @@ impl Context {
language::BufferEvent::Edited => {
self.count_remaining_tokens(cx);
self.reparse(cx);
// Use `inclusive = true` to invalidate a step when an edit occurs
// at the start/end of a parsed step.
cx.emit(ContextEvent::MessagesEdited);
}
_ => {}
@@ -1031,7 +1051,7 @@ impl Context {
}
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
let request = self.to_completion_request(cx);
let request = self.to_completion_request(RequestType::SuggestEdits, cx); // Conservatively assume SuggestEdits, since it takes more tokens.
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
@@ -1174,7 +1194,7 @@ impl Context {
}
let request = {
let mut req = self.to_completion_request(cx);
let mut req = self.to_completion_request(RequestType::Chat, cx);
// Skip the last message because it's likely to change and
// therefore would be a waste to cache.
req.messages.pop();
@@ -1248,8 +1268,8 @@ impl Context {
let mut removed_slash_command_ranges = Vec::new();
let mut updated_slash_commands = Vec::new();
let mut removed_steps = Vec::new();
let mut updated_steps = Vec::new();
let mut removed_patches = Vec::new();
let mut updated_patches = Vec::new();
while let Some(mut row_range) = row_ranges.next() {
while let Some(next_row_range) = row_ranges.peek() {
if row_range.end >= next_row_range.start {
@@ -1273,11 +1293,11 @@ impl Context {
&mut removed_slash_command_ranges,
cx,
);
self.reparse_workflow_steps_in_range(
self.reparse_patches_in_range(
start..end,
&buffer,
&mut updated_steps,
&mut removed_steps,
&mut updated_patches,
&mut removed_patches,
cx,
);
}
@@ -1289,10 +1309,10 @@ impl Context {
});
}
if !updated_steps.is_empty() || !removed_steps.is_empty() {
cx.emit(ContextEvent::WorkflowStepsUpdated {
removed: removed_steps,
updated: updated_steps,
if !updated_patches.is_empty() || !removed_patches.is_empty() {
cx.emit(ContextEvent::PatchesUpdated {
removed: removed_patches,
updated: updated_patches,
});
}
}
@@ -1354,7 +1374,7 @@ impl Context {
removed.extend(removed_commands.map(|command| command.source_range));
}
fn reparse_workflow_steps_in_range(
fn reparse_patches_in_range(
&mut self,
range: Range<text::Anchor>,
buffer: &BufferSnapshot,
@@ -1369,41 +1389,32 @@ impl Context {
self.xml_tags
.splice(intersecting_tags_range.clone(), new_tags);
// Find which steps intersect the changed range.
let intersecting_steps_range =
self.indices_intersecting_buffer_range(&self.workflow_steps, range.clone(), cx);
// Find which patches intersect the changed range.
let intersecting_patches_range =
self.indices_intersecting_buffer_range(&self.patches, range.clone(), cx);
// Reparse all tags after the last unchanged step before the change.
// Reparse all tags after the last unchanged patch before the change.
let mut tags_start_ix = 0;
if let Some(preceding_unchanged_step) =
self.workflow_steps[..intersecting_steps_range.start].last()
if let Some(preceding_unchanged_patch) =
self.patches[..intersecting_patches_range.start].last()
{
tags_start_ix = match self.xml_tags.binary_search_by(|tag| {
tag.range
.start
.cmp(&preceding_unchanged_step.range.end, buffer)
.cmp(&preceding_unchanged_patch.range.end, buffer)
.then(Ordering::Less)
}) {
Ok(ix) | Err(ix) => ix,
};
}
// Rebuild the edit suggestions in the range.
let mut new_steps = self.parse_steps(tags_start_ix, range.end, buffer);
if let Some(project) = self.project() {
for step in &mut new_steps {
Self::resolve_workflow_step_internal(step, &project, cx);
}
}
updated.extend(new_steps.iter().map(|step| step.range.clone()));
let removed_steps = self
.workflow_steps
.splice(intersecting_steps_range, new_steps);
// Rebuild the patches in the range.
let new_patches = self.parse_patches(tags_start_ix, range.end, buffer, cx);
updated.extend(new_patches.iter().map(|patch| patch.range.clone()));
let removed_patches = self.patches.splice(intersecting_patches_range, new_patches);
removed.extend(
removed_steps
.map(|step| step.range)
removed_patches
.map(|patch| patch.range)
.filter(|range| !updated.contains(&range)),
);
}
@@ -1464,60 +1475,95 @@ impl Context {
tags
}
fn parse_steps(
fn parse_patches(
&mut self,
tags_start_ix: usize,
buffer_end: text::Anchor,
buffer: &BufferSnapshot,
) -> Vec<WorkflowStep> {
let mut new_steps = Vec::new();
let mut pending_step = None;
let mut edit_step_depth = 0;
cx: &AppContext,
) -> Vec<AssistantPatch> {
let mut new_patches = Vec::new();
let mut pending_patch = None;
let mut patch_tag_depth = 0;
let mut tags = self.xml_tags[tags_start_ix..].iter().peekable();
'tags: while let Some(tag) = tags.next() {
if tag.range.start.cmp(&buffer_end, buffer).is_gt() && edit_step_depth == 0 {
if tag.range.start.cmp(&buffer_end, buffer).is_gt() && patch_tag_depth == 0 {
break;
}
if tag.kind == XmlTagKind::Step && tag.is_open_tag {
edit_step_depth += 1;
let edit_start = tag.range.start;
let mut edits = Vec::new();
let mut step = WorkflowStep {
range: edit_start..edit_start,
leading_tags_end: tag.range.end,
trailing_tag_start: None,
if tag.kind == XmlTagKind::Patch && tag.is_open_tag {
patch_tag_depth += 1;
let patch_start = tag.range.start;
let mut edits = Vec::<Result<AssistantEdit>>::new();
let mut patch = AssistantPatch {
range: patch_start..patch_start,
title: String::new().into(),
edits: Default::default(),
resolution: None,
resolution_task: None,
status: crate::AssistantPatchStatus::Pending,
};
while let Some(tag) = tags.next() {
step.trailing_tag_start.get_or_insert(tag.range.start);
if tag.kind == XmlTagKind::Patch && !tag.is_open_tag {
patch_tag_depth -= 1;
if patch_tag_depth == 0 {
patch.range.end = tag.range.end;
if tag.kind == XmlTagKind::Step && !tag.is_open_tag {
// step.trailing_tag_start = Some(tag.range.start);
edit_step_depth -= 1;
if edit_step_depth == 0 {
step.range.end = tag.range.end;
step.edits = edits.into();
new_steps.push(step);
// Include the line immediately after this <patch> tag if it's empty.
let patch_end_offset = patch.range.end.to_offset(buffer);
let mut patch_end_chars = buffer.chars_at(patch_end_offset);
if patch_end_chars.next() == Some('\n')
&& patch_end_chars.next().map_or(true, |ch| ch == '\n')
{
let messages = self.messages_for_offsets(
[patch_end_offset, patch_end_offset + 1],
cx,
);
if messages.len() == 1 {
patch.range.end = buffer.anchor_before(patch_end_offset + 1);
}
}
edits.sort_unstable_by(|a, b| {
if let (Ok(a), Ok(b)) = (a, b) {
a.path.cmp(&b.path)
} else {
Ordering::Equal
}
});
patch.edits = edits.into();
patch.status = AssistantPatchStatus::Ready;
new_patches.push(patch);
continue 'tags;
}
}
if tag.kind == XmlTagKind::Title && tag.is_open_tag {
let content_start = tag.range.end;
while let Some(tag) = tags.next() {
if tag.kind == XmlTagKind::Title && !tag.is_open_tag {
let content_end = tag.range.start;
patch.title =
trimmed_text_in_range(buffer, content_start..content_end)
.into();
break;
}
}
}
if tag.kind == XmlTagKind::Edit && tag.is_open_tag {
let mut path = None;
let mut search = None;
let mut old_text = None;
let mut new_text = None;
let mut operation = None;
let mut description = None;
while let Some(tag) = tags.next() {
if tag.kind == XmlTagKind::Edit && !tag.is_open_tag {
edits.push(WorkflowStepEdit::new(
edits.push(AssistantEdit::new(
path,
operation,
search,
old_text,
new_text,
description,
));
break;
@@ -1526,7 +1572,8 @@ impl Context {
if tag.is_open_tag
&& [
XmlTagKind::Path,
XmlTagKind::Search,
XmlTagKind::OldText,
XmlTagKind::NewText,
XmlTagKind::Operation,
XmlTagKind::Description,
]
@@ -1538,15 +1585,18 @@ impl Context {
if tag.kind == kind && !tag.is_open_tag {
let tag = tags.next().unwrap();
let content_end = tag.range.start;
let mut content = buffer
.text_for_range(content_start..content_end)
.collect::<String>();
content.truncate(content.trim_end().len());
let content = trimmed_text_in_range(
buffer,
content_start..content_end,
);
match kind {
XmlTagKind::Path => path = Some(content),
XmlTagKind::Operation => operation = Some(content),
XmlTagKind::Search => {
search = Some(content).filter(|s| !s.is_empty())
XmlTagKind::OldText => {
old_text = Some(content).filter(|s| !s.is_empty())
}
XmlTagKind::NewText => {
new_text = Some(content).filter(|s| !s.is_empty())
}
XmlTagKind::Description => {
description =
@@ -1561,162 +1611,28 @@ impl Context {
}
}
pending_step = Some(step);
patch.edits = edits.into();
pending_patch = Some(patch);
}
}
if let Some(mut pending_step) = pending_step {
pending_step.range.end = text::Anchor::MAX;
new_steps.push(pending_step);
}
new_steps
}
pub fn resolve_workflow_step(
&mut self,
tagged_range: Range<text::Anchor>,
cx: &mut ModelContext<Self>,
) -> Option<()> {
let index = self
.workflow_step_index_for_range(&tagged_range, self.buffer.read(cx))
.ok()?;
let step = &mut self.workflow_steps[index];
let project = self.project.as_ref()?;
step.resolution.take();
Self::resolve_workflow_step_internal(step, project, cx);
None
}
fn resolve_workflow_step_internal(
step: &mut WorkflowStep,
project: &Model<Project>,
cx: &mut ModelContext<'_, Context>,
) {
step.resolution_task = Some(cx.spawn({
let range = step.range.clone();
let edits = step.edits.clone();
let project = project.clone();
|this, mut cx| async move {
let suggestion_groups =
Self::compute_step_resolution(project, edits, &mut cx).await;
this.update(&mut cx, |this, cx| {
let buffer = this.buffer.read(cx).text_snapshot();
let ix = this.workflow_step_index_for_range(&range, &buffer).ok();
if let Some(ix) = ix {
let step = &mut this.workflow_steps[ix];
let resolution = suggestion_groups.map(|suggestion_groups| {
let mut title = String::new();
for mut chunk in buffer.text_for_range(
step.leading_tags_end
..step.trailing_tag_start.unwrap_or(step.range.end),
) {
if title.is_empty() {
chunk = chunk.trim_start();
}
if let Some((prefix, _)) = chunk.split_once('\n') {
title.push_str(prefix);
break;
} else {
title.push_str(chunk);
}
}
WorkflowStepResolution {
title,
suggestion_groups,
}
});
step.resolution = Some(Arc::new(resolution));
cx.emit(ContextEvent::WorkflowStepsUpdated {
removed: vec![],
updated: vec![range],
})
}
})
.ok();
}
}));
}
async fn compute_step_resolution(
project: Model<Project>,
edits: Arc<[Result<WorkflowStepEdit>]>,
cx: &mut AsyncAppContext,
) -> Result<HashMap<Model<Buffer>, Vec<WorkflowSuggestionGroup>>> {
let mut suggestion_tasks = Vec::new();
for edit in edits.iter() {
let edit = edit.as_ref().map_err(|e| anyhow!("{e}"))?;
suggestion_tasks.push(edit.resolve(project.clone(), cx.clone()));
}
// Expand the context ranges of each suggestion and group suggestions with overlapping context ranges.
let suggestions = future::try_join_all(suggestion_tasks).await?;
let mut suggestions_by_buffer = HashMap::default();
for (buffer, suggestion) in suggestions {
suggestions_by_buffer
.entry(buffer)
.or_insert_with(Vec::new)
.push(suggestion);
}
let mut suggestion_groups_by_buffer = HashMap::default();
for (buffer, mut suggestions) in suggestions_by_buffer {
let mut suggestion_groups = Vec::<WorkflowSuggestionGroup>::new();
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?;
// Sort suggestions by their range so that earlier, larger ranges come first
suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
// Merge overlapping suggestions
suggestions.dedup_by(|a, b| b.try_merge(a, &snapshot));
// Create context ranges for each suggestion
for suggestion in suggestions {
let context_range = {
let suggestion_point_range = suggestion.range().to_point(&snapshot);
let start_row = suggestion_point_range.start.row.saturating_sub(5);
let end_row =
cmp::min(suggestion_point_range.end.row + 5, snapshot.max_point().row);
let start = snapshot.anchor_before(Point::new(start_row, 0));
let end =
snapshot.anchor_after(Point::new(end_row, snapshot.line_len(end_row)));
start..end
};
if let Some(last_group) = suggestion_groups.last_mut() {
if last_group
.context_range
.end
.cmp(&context_range.start, &snapshot)
.is_ge()
{
// Merge with the previous group if context ranges overlap
last_group.context_range.end = context_range.end;
last_group.suggestions.push(suggestion);
} else {
// Create a new group
suggestion_groups.push(WorkflowSuggestionGroup {
context_range,
suggestions: vec![suggestion],
});
}
if let Some(mut pending_patch) = pending_patch {
let patch_start = pending_patch.range.start.to_offset(buffer);
if let Some(message) = self.message_for_offset(patch_start, cx) {
if message.anchor_range.end == text::Anchor::MAX {
pending_patch.range.end = text::Anchor::MAX;
} else {
// Create the first group
suggestion_groups.push(WorkflowSuggestionGroup {
context_range,
suggestions: vec![suggestion],
});
let message_end = buffer.anchor_after(message.offset_range.end - 1);
pending_patch.range.end = message_end;
}
} else {
pending_patch.range.end = text::Anchor::MAX;
}
suggestion_groups_by_buffer.insert(buffer, suggestion_groups);
new_patches.push(pending_patch);
}
Ok(suggestion_groups_by_buffer)
new_patches
}
pub fn pending_command_for_position(
@@ -1784,7 +1700,7 @@ impl Context {
pub fn insert_command_output(
&mut self,
command_range: Range<language::Anchor>,
output: Task<Result<SlashCommandOutput>>,
output: Task<SlashCommandResult>,
ensure_trailing_newline: bool,
expand_result: bool,
cx: &mut ModelContext<Self>,
@@ -1795,19 +1711,13 @@ impl Context {
let command_range = command_range.clone();
async move {
let output = output.await;
let output = match output {
Ok(output) => SlashCommandOutput::from_event_stream(output).await,
Err(err) => Err(err),
};
this.update(&mut cx, |this, cx| match output {
Ok(mut output) => {
// Ensure section ranges are valid.
for section in &mut output.sections {
section.range.start = section.range.start.min(output.text.len());
section.range.end = section.range.end.min(output.text.len());
while !output.text.is_char_boundary(section.range.start) {
section.range.start -= 1;
}
while !output.text.is_char_boundary(section.range.end) {
section.range.end += 1;
}
}
output.ensure_valid_section_ranges();
// Ensure there is a newline after the last section.
if ensure_trailing_newline {
@@ -1972,7 +1882,11 @@ impl Context {
})
}
pub fn assist(&mut self, cx: &mut ModelContext<Self>) -> Option<MessageAnchor> {
pub fn assist(
&mut self,
request_type: RequestType,
cx: &mut ModelContext<Self>,
) -> Option<MessageAnchor> {
let model_registry = LanguageModelRegistry::read_global(cx);
let provider = model_registry.active_provider()?;
let model = model_registry.active_model()?;
@@ -1985,7 +1899,7 @@ impl Context {
// Compute which messages to cache, including the last one.
self.mark_cache_anchors(&model.cache_configuration(), false, cx);
let mut request = self.to_completion_request(cx);
let mut request = self.to_completion_request(request_type, cx);
if cx.has_flag::<ToolUseFeatureFlag>() {
let tool_registry = ToolRegistry::global(cx);
@@ -2187,7 +2101,11 @@ impl Context {
Some(user_message)
}
pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
pub fn to_completion_request(
&self,
request_type: RequestType,
cx: &AppContext,
) -> LanguageModelRequest {
let buffer = self.buffer.read(cx);
let mut contents = self.contents(cx).peekable();
@@ -2276,6 +2194,25 @@ impl Context {
completion_request.messages.push(request_message);
}
if let RequestType::SuggestEdits = request_type {
if let Ok(preamble) = self.prompt_builder.generate_workflow_prompt() {
let last_elem_index = completion_request.messages.len();
completion_request
.messages
.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::Text(preamble)],
cache: false,
});
// The preamble message should be sent right before the last actual user message.
completion_request
.messages
.swap(last_elem_index, last_elem_index.saturating_sub(1));
}
}
completion_request
}
@@ -2315,11 +2252,11 @@ impl Context {
let mut updated = Vec::new();
let mut removed = Vec::new();
for range in ranges {
self.reparse_workflow_steps_in_range(range, &buffer, &mut updated, &mut removed, cx);
self.reparse_patches_in_range(range, &buffer, &mut updated, &mut removed, cx);
}
if !updated.is_empty() || !removed.is_empty() {
cx.emit(ContextEvent::WorkflowStepsUpdated { removed, updated })
cx.emit(ContextEvent::PatchesUpdated { removed, updated })
}
}
@@ -2590,11 +2527,12 @@ impl Context {
return;
}
let mut request = self.to_completion_request(cx);
let mut request = self.to_completion_request(RequestType::Chat, cx);
request.messages.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![
"Summarize the context into a short title without punctuation.".into(),
"Generate a concise 3-7 word title for this conversation, omitting punctuation"
.into(),
],
cache: false,
});
@@ -2825,6 +2763,24 @@ impl Context {
}
}
fn trimmed_text_in_range(buffer: &BufferSnapshot, range: Range<text::Anchor>) -> String {
let mut is_start = true;
let mut content = buffer
.text_for_range(range)
.map(|mut chunk| {
if is_start {
chunk = chunk.trim_start_matches('\n');
if !chunk.is_empty() {
is_start = false;
}
}
chunk
})
.collect::<String>();
content.truncate(content.trim_end().len());
content
}
#[derive(Debug, Default)]
pub struct ContextVersion {
context: clock::Global,

View File

@@ -1,13 +1,12 @@
use super::{MessageCacheMetadata, WorkflowStepEdit};
use super::{AssistantEdit, MessageCacheMetadata};
use crate::{
assistant_panel, prompt_library, slash_command::file_command, CacheStatus, Context,
ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
WorkflowStepEditKind,
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
Context, ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
};
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandRegistry,
SlashCommandRegistry, SlashCommandResult,
};
use collections::HashSet;
use fs::FakeFs;
@@ -15,6 +14,7 @@ use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
use pretty_assertions::assert_eq;
use project::Project;
use rand::prelude::*;
use serde_json::json;
@@ -478,7 +478,15 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
#[gpui::test]
async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx.update(prompt_library::init);
let settings_store = cx.update(SettingsStore::test);
let mut settings_store = cx.update(SettingsStore::test);
cx.update(|cx| {
settings_store
.set_user_settings(
r#"{ "assistant": { "enable_experimental_live_diffs": true } }"#,
cx,
)
.unwrap()
});
cx.set_global(settings_store);
cx.update(language::init);
cx.update(Project::init_settings);
@@ -520,7 +528,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
»",
cx,
);
expect_steps(
expect_patches(
&context,
"
@@ -539,17 +547,17 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
one
two
«
<step»",
<patch»",
cx,
);
expect_steps(
expect_patches(
&context,
"
one
two
<step",
<patch",
&[],
cx,
);
@@ -563,36 +571,24 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
one
two
<step«>
Add a second function
```rust
fn two() {}
```
<patch«>
<edit>»",
cx,
);
expect_steps(
expect_patches(
&context,
"
one
two
«<step>
Add a second function
```rust
fn two() {}
```
«<patch>
<edit>»",
&[&[]],
cx,
);
// The full suggestion is added
// The full patch is added
edit(
&context,
"
@@ -600,52 +596,47 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
one
two
<step>
Add a second function
```rust
fn two() {}
```
<patch>
<edit>«
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>fn one</search>
<description>add a `two` function</description>
<old_text>fn one</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>
</patch>
also,»",
cx,
);
expect_steps(
expect_patches(
&context,
"
one
two
«<step>
Add a second function
```rust
fn two() {}
```
«<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>fn one</search>
<description>add a `two` function</description>
<old_text>fn one</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>»
</patch>
»
also,",
&[&[WorkflowStepEdit {
&[&[AssistantEdit {
path: "src/lib.rs".into(),
kind: WorkflowStepEditKind::InsertAfter {
search: "fn one".into(),
description: "add a `two` function".into(),
kind: AssistantEditKind::InsertAfter {
old_text: "fn one".into(),
new_text: "fn two() {}".into(),
description: Some("add a `two` function".into()),
},
}]],
cx,
@@ -659,52 +650,47 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
one
two
<step>
Add a second function
```rust
fn two() {}
```
<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>«fn zero»</search>
<description>add a `two` function</description>
<old_text>«fn zero»</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>
</patch>
also,",
cx,
);
expect_steps(
expect_patches(
&context,
"
one
two
«<step>
Add a second function
```rust
fn two() {}
```
«<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>fn zero</search>
<description>add a `two` function</description>
<old_text>fn zero</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>»
</patch>
»
also,",
&[&[WorkflowStepEdit {
&[&[AssistantEdit {
path: "src/lib.rs".into(),
kind: WorkflowStepEditKind::InsertAfter {
search: "fn zero".into(),
description: "add a `two` function".into(),
kind: AssistantEditKind::InsertAfter {
old_text: "fn zero".into(),
new_text: "fn two() {}".into(),
description: Some("add a `two` function".into()),
},
}]],
cx,
@@ -715,27 +701,24 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
context.cycle_message_roles(HashSet::from_iter([assistant_message_id]), cx);
context.cycle_message_roles(HashSet::from_iter([assistant_message_id]), cx);
});
expect_steps(
expect_patches(
&context,
"
one
two
<step>
Add a second function
```rust
fn two() {}
```
<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>fn zero</search>
<description>add a `two` function</description>
<old_text>fn zero</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>
</patch>
also,",
&[],
@@ -746,34 +729,32 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
context.update(cx, |context, cx| {
context.cycle_message_roles(HashSet::from_iter([assistant_message_id]), cx);
});
expect_steps(
expect_patches(
&context,
"
one
two
«<step>
Add a second function
```rust
fn two() {}
```
«<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>fn zero</search>
<description>add a `two` function</description>
<old_text>fn zero</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>»
</patch>
»
also,",
&[&[WorkflowStepEdit {
&[&[AssistantEdit {
path: "src/lib.rs".into(),
kind: WorkflowStepEditKind::InsertAfter {
search: "fn zero".into(),
description: "add a `two` function".into(),
kind: AssistantEditKind::InsertAfter {
old_text: "fn zero".into(),
new_text: "fn two() {}".into(),
description: Some("add a `two` function".into()),
},
}]],
cx,
@@ -792,34 +773,32 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx,
)
});
expect_steps(
expect_patches(
&deserialized_context,
"
one
two
«<step>
Add a second function
```rust
fn two() {}
```
«<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<search>fn zero</search>
<description>add a `two` function</description>
<old_text>fn zero</old_text>
<new_text>
fn two() {}
</new_text>
</edit>
</step>»
</patch>
»
also,",
&[&[WorkflowStepEdit {
&[&[AssistantEdit {
path: "src/lib.rs".into(),
kind: WorkflowStepEditKind::InsertAfter {
search: "fn zero".into(),
description: "add a `two` function".into(),
kind: AssistantEditKind::InsertAfter {
old_text: "fn zero".into(),
new_text: "fn two() {}".into(),
description: Some("add a `two` function".into()),
},
}]],
cx,
@@ -834,48 +813,58 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx.executor().run_until_parked();
}
fn expect_steps(
#[track_caller]
fn expect_patches(
context: &Model<Context>,
expected_marked_text: &str,
expected_suggestions: &[&[WorkflowStepEdit]],
expected_suggestions: &[&[AssistantEdit]],
cx: &mut TestAppContext,
) {
context.update(cx, |context, cx| {
let expected_marked_text = expected_marked_text.unindent();
let (expected_text, expected_ranges) = marked_text_ranges(&expected_marked_text, false);
let expected_marked_text = expected_marked_text.unindent();
let (expected_text, _) = marked_text_ranges(&expected_marked_text, false);
let (buffer_text, ranges, patches) = context.update(cx, |context, cx| {
context.buffer.read_with(cx, |buffer, _| {
assert_eq!(buffer.text(), expected_text);
let ranges = context
.workflow_steps
.patches
.iter()
.map(|entry| entry.range.to_offset(buffer))
.collect::<Vec<_>>();
let marked = generate_marked_text(&expected_text, &ranges, false);
assert_eq!(
marked,
expected_marked_text,
"unexpected suggestion ranges. actual: {ranges:?}, expected: {expected_ranges:?}"
);
let suggestions = context
.workflow_steps
.iter()
.map(|step| {
step.edits
.iter()
.map(|edit| {
let edit = edit.as_ref().unwrap();
WorkflowStepEdit {
path: edit.path.clone(),
kind: edit.kind.clone(),
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
assert_eq!(suggestions, expected_suggestions);
});
(
buffer.text(),
ranges,
context
.patches
.iter()
.map(|step| step.edits.clone())
.collect::<Vec<_>>(),
)
})
});
assert_eq!(buffer_text, expected_text);
let actual_marked_text = generate_marked_text(&expected_text, &ranges, false);
assert_eq!(actual_marked_text, expected_marked_text);
assert_eq!(
patches
.iter()
.map(|patch| {
patch
.iter()
.map(|edit| {
let edit = edit.as_ref().unwrap();
AssistantEdit {
path: edit.path.clone(),
kind: edit.kind.clone(),
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
expected_suggestions
);
}
}
@@ -1108,7 +1097,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
text: output_text,
sections,
run_commands_in_text: false,
})),
}
.to_event_stream())),
true,
false,
cx,
@@ -1427,11 +1417,12 @@ impl SlashCommand for FakeSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0),
sections: vec![],
run_commands_in_text: false,
}))
}
.to_event_stream()))
}
}

View File

@@ -1,7 +1,7 @@
use crate::{
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, StreamingDiff,
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff,
};
use anyhow::{anyhow, Context as _, Result};
use client::{telemetry::Telemetry, ErrorExt};
@@ -9,7 +9,7 @@ use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
actions::{MoveDown, MoveUp, SelectAll},
display_map::{
BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
ToDisplayPoint,
},
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
@@ -54,7 +54,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::terminal_panel::TerminalPanel;
use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings;
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
use ui::{prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
use util::{RangeExt, ResultExt};
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
@@ -82,13 +82,6 @@ pub struct InlineAssistant {
assists: HashMap<InlineAssistId, InlineAssist>,
assists_by_editor: HashMap<WeakView<Editor>, EditorInlineAssists>,
assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>,
assist_observations: HashMap<
InlineAssistId,
(
async_watch::Sender<AssistStatus>,
async_watch::Receiver<AssistStatus>,
),
>,
confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>,
prompt_history: VecDeque<String>,
prompt_builder: Arc<PromptBuilder>,
@@ -96,19 +89,6 @@ pub struct InlineAssistant {
fs: Arc<dyn Fs>,
}
pub enum AssistStatus {
Idle,
Started,
Stopped,
Finished,
}
impl AssistStatus {
pub fn is_done(&self) -> bool {
matches!(self, Self::Stopped | Self::Finished)
}
}
impl Global for InlineAssistant {}
impl InlineAssistant {
@@ -123,7 +103,6 @@ impl InlineAssistant {
assists: HashMap::default(),
assists_by_editor: HashMap::default(),
assist_groups: HashMap::default(),
assist_observations: HashMap::default(),
confirmed_assists: HashMap::default(),
prompt_history: VecDeque::default(),
prompt_builder,
@@ -210,11 +189,16 @@ impl InlineAssistant {
initial_prompt: Option<String>,
cx: &mut WindowContext,
) {
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
(
editor.buffer().read(cx).snapshot(cx),
editor.selections.all::<Point>(cx),
)
});
let mut selections = Vec::<Selection<Point>>::new();
let mut newest_selection = None;
for mut selection in editor.read(cx).selections.all::<Point>(cx) {
for mut selection in initial_selections {
if selection.end > selection.start {
selection.start.column = 0;
// If the selection ends at the start of the line, we don't want to include it.
@@ -467,15 +451,14 @@ impl InlineAssistant {
let assist_blocks = vec![
BlockProperties {
style: BlockStyle::Sticky,
position: range.start,
placement: BlockPlacement::Above(range.start),
height: prompt_editor_height,
render: build_assist_editor_renderer(prompt_editor),
disposition: BlockDisposition::Above,
priority: 0,
},
BlockProperties {
style: BlockStyle::Sticky,
position: range.end,
placement: BlockPlacement::Below(range.end),
height: 0,
render: Box::new(|cx| {
v_flex()
@@ -485,7 +468,6 @@ impl InlineAssistant {
.border_color(cx.theme().status().info_border)
.into_any_element()
}),
disposition: BlockDisposition::Below,
priority: 0,
},
];
@@ -589,10 +571,13 @@ impl InlineAssistant {
return;
};
let editor = editor.read(cx);
if editor.selections.count() == 1 {
let selection = editor.selections.newest::<usize>(cx);
let buffer = editor.buffer().read(cx).snapshot(cx);
if editor.read(cx).selections.count() == 1 {
let (selection, buffer) = editor.update(cx, |editor, cx| {
(
editor.selections.newest::<usize>(cx),
editor.buffer().read(cx).snapshot(cx),
)
});
for assist_id in &editor_assists.assist_ids {
let assist = &self.assists[assist_id];
let assist_range = assist.range.to_offset(&buffer);
@@ -617,10 +602,13 @@ impl InlineAssistant {
return;
};
let editor = editor.read(cx);
if editor.selections.count() == 1 {
let selection = editor.selections.newest::<usize>(cx);
let buffer = editor.buffer().read(cx).snapshot(cx);
if editor.read(cx).selections.count() == 1 {
let (selection, buffer) = editor.update(cx, |editor, cx| {
(
editor.selections.newest::<usize>(cx),
editor.buffer().read(cx).snapshot(cx),
)
});
let mut closest_assist_fallback = None;
for assist_id in &editor_assists.assist_ids {
let assist = &self.assists[assist_id];
@@ -835,17 +823,6 @@ impl InlineAssistant {
.insert(assist_id, confirmed_alternative);
}
}
// Remove the assist from the status updates map
self.assist_observations.remove(&assist_id);
}
pub fn undo_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
let Some(codegen) = self.confirmed_assists.remove(&assist_id) else {
return false;
};
codegen.update(cx, |this, cx| this.undo(cx));
true
}
fn dismiss_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
@@ -1039,10 +1016,6 @@ impl InlineAssistant {
codegen.start(user_prompt, assistant_panel_context, cx)
})
.log_err();
if let Some((tx, _)) = self.assist_observations.get(&assist_id) {
tx.send(AssistStatus::Started).ok();
}
}
pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
@@ -1053,25 +1026,6 @@ impl InlineAssistant {
};
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
if let Some((tx, _)) = self.assist_observations.get(&assist_id) {
tx.send(AssistStatus::Stopped).ok();
}
}
pub fn assist_status(&self, assist_id: InlineAssistId, cx: &AppContext) -> InlineAssistStatus {
if let Some(assist) = self.assists.get(&assist_id) {
match assist.codegen.read(cx).status(cx) {
CodegenStatus::Idle => InlineAssistStatus::Idle,
CodegenStatus::Pending => InlineAssistStatus::Pending,
CodegenStatus::Done => InlineAssistStatus::Done,
CodegenStatus::Error(_) => InlineAssistStatus::Error,
}
} else if self.confirmed_assists.contains_key(&assist_id) {
InlineAssistStatus::Confirmed
} else {
InlineAssistStatus::Canceled
}
}
fn update_editor_highlights(&self, editor: &View<Editor>, cx: &mut WindowContext) {
@@ -1234,7 +1188,7 @@ impl InlineAssistant {
let height =
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
new_blocks.push(BlockProperties {
position: new_row,
placement: BlockPlacement::Above(new_row),
height,
style: BlockStyle::Flex,
render: Box::new(move |cx| {
@@ -1246,7 +1200,6 @@ impl InlineAssistant {
.child(deleted_lines_editor.clone())
.into_any_element()
}),
disposition: BlockDisposition::Above,
priority: 0,
});
}
@@ -1257,42 +1210,6 @@ impl InlineAssistant {
.collect();
})
}
pub fn observe_assist(
&mut self,
assist_id: InlineAssistId,
) -> async_watch::Receiver<AssistStatus> {
if let Some((_, rx)) = self.assist_observations.get(&assist_id) {
rx.clone()
} else {
let (tx, rx) = async_watch::channel(AssistStatus::Idle);
self.assist_observations.insert(assist_id, (tx, rx.clone()));
rx
}
}
}
pub enum InlineAssistStatus {
Idle,
Pending,
Done,
Error,
Confirmed,
Canceled,
}
impl InlineAssistStatus {
pub(crate) fn is_pending(&self) -> bool {
matches!(self, Self::Pending)
}
pub(crate) fn is_confirmed(&self) -> bool {
matches!(self, Self::Confirmed)
}
pub(crate) fn is_done(&self) -> bool {
matches!(self, Self::Done)
}
}
struct EditorInlineAssists {
@@ -1690,7 +1607,7 @@ impl PromptEditor {
// always show the cursor (even when it isn't focused) because
// typing in one will make what you typed appear in all of them.
editor.set_show_cursor_when_unfocused(true, cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
editor
});
@@ -1747,6 +1664,7 @@ impl PromptEditor {
self.editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor.set_text(prompt, cx);
if focus {
@@ -1757,6 +1675,20 @@ impl PromptEditor {
self.subscribe_to_editor(cx);
}
fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
let action = if codegen.is_insertion {
"Generate"
} else {
"Transform"
};
format!("{action}{context_keybinding} • ↓↑ for history")
}
fn prompt(&self, cx: &AppContext) -> String {
self.editor.read(cx).text(cx)
}
@@ -2290,8 +2222,6 @@ impl InlineAssist {
if assist.decorations.is_none() {
this.finish_assist(assist_id, false, cx);
} else if let Some(tx) = this.assist_observations.get(&assist_id) {
tx.0.send(AssistStatus::Finished).ok();
}
}
})
@@ -2315,7 +2245,7 @@ impl InlineAssist {
.read(cx)
.active_context(cx)?
.read(cx)
.to_completion_request(cx),
.to_completion_request(RequestType::Chat, cx),
)
} else {
None
@@ -2349,12 +2279,14 @@ pub enum CodegenEvent {
pub struct Codegen {
alternatives: Vec<Model<CodegenAlternative>>,
active_alternative: usize,
seen_alternatives: HashSet<usize>,
subscriptions: Vec<Subscription>,
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
telemetry: Option<Arc<Telemetry>>,
builder: Arc<PromptBuilder>,
is_insertion: bool,
}
impl Codegen {
@@ -2377,8 +2309,10 @@ impl Codegen {
)
});
let mut this = Self {
is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
alternatives: vec![codegen],
active_alternative: 0,
seen_alternatives: HashSet::default(),
subscriptions: Vec::new(),
buffer,
range,
@@ -2431,6 +2365,7 @@ impl Codegen {
fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
self.active_alternative()
.update(cx, |codegen, cx| codegen.set_active(false, cx));
self.seen_alternatives.insert(index);
self.active_alternative = index;
self.active_alternative()
.update(cx, |codegen, cx| codegen.set_active(true, cx));
@@ -2560,6 +2495,8 @@ pub struct CodegenAlternative {
active: bool,
edits: Vec<(Range<Anchor>, String)>,
line_operations: Vec<LineOperation>,
request: Option<LanguageModelRequest>,
elapsed_time: Option<f64>,
}
enum CodegenStatus {
@@ -2631,6 +2568,8 @@ impl CodegenAlternative {
edits: Vec::new(),
line_operations: Vec::new(),
range,
request: None,
elapsed_time: None,
}
}
@@ -2727,6 +2666,7 @@ impl CodegenAlternative {
async { Ok(stream::empty().boxed()) }.boxed_local()
} else {
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
self.request = Some(request.clone());
let chunks = cx
.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await });
@@ -2771,7 +2711,7 @@ impl CodegenAlternative {
let prompt = self
.builder
.generate_content_prompt(user_prompt, language_name, buffer, range)
.generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
.map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
let mut messages = Vec::new();
@@ -2800,6 +2740,7 @@ impl CodegenAlternative {
stream: impl 'static + Future<Output = Result<BoxStream<'static, Result<String>>>>,
cx: &mut ModelContext<Self>,
) {
let start_time = Instant::now();
let snapshot = self.snapshot.clone();
let selected_text = snapshot
.text_for_range(self.range.start..self.range.end)
@@ -3016,6 +2957,8 @@ impl CodegenAlternative {
};
let result = generate.await;
let elapsed_time = start_time.elapsed().as_secs_f64();
codegen
.update(&mut cx, |this, cx| {
this.last_equal_ranges.clear();
@@ -3024,6 +2967,7 @@ impl CodegenAlternative {
} else {
this.status = CodegenStatus::Done;
}
this.elapsed_time = Some(elapsed_time);
cx.emit(CodegenEvent::Finished);
cx.notify();
})
@@ -3370,6 +3314,10 @@ impl CodeActionProvider for AssistantCodeActionProvider {
range: Range<text::Anchor>,
cx: &mut WindowContext,
) -> Task<Result<Vec<CodeAction>>> {
if !AssistantSettings::get_global(cx).enabled {
return Task::ready(Ok(Vec::new()));
}
let snapshot = buffer.read(cx).snapshot();
let mut range = range.to_point(&snapshot);

View File

@@ -158,39 +158,34 @@ impl PickerDelegate for ModelPickerDelegate {
.spacing(ListItemSpacing::Sparse)
.selected(selected)
.start_slot(
div().pr_1().child(
div().pr_0p5().child(
Icon::new(model_info.icon)
.color(Color::Muted)
.size(IconSize::Medium),
),
)
.child(
h_flex()
.w_full()
.justify_between()
.font_buffer(cx)
.min_w(px(240.))
.child(
h_flex()
.gap_2()
.child(Label::new(model_info.model.name().0.clone()))
.child(
Label::new(provider_name)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.children(match model_info.availability {
LanguageModelAvailability::Public => None,
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
show_badges.then(|| {
Label::new("Pro")
.size(LabelSize::XSmall)
.color(Color::Muted)
})
}
}),
),
h_flex().w_full().justify_between().min_w(px(200.)).child(
h_flex()
.gap_1p5()
.child(Label::new(model_info.model.name().0.clone()))
.child(
Label::new(provider_name)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.children(match model_info.availability {
LanguageModelAvailability::Public => None,
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
show_badges.then(|| {
Label::new("Pro")
.size(LabelSize::XSmall)
.color(Color::Muted)
})
}
}),
),
)
.end_slot(div().when(model_info.is_selected, |this| {
this.child(
@@ -212,7 +207,7 @@ impl PickerDelegate for ModelPickerDelegate {
h_flex()
.w_full()
.border_t_1()
.border_color(cx.theme().colors().border)
.border_color(cx.theme().colors().border_variant)
.p_1()
.gap_4()
.justify_between()

View File

@@ -0,0 +1,970 @@
use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
use editor::ProposedChangesEditor;
use futures::{future, TryFutureExt as _};
use gpui::{AppContext, AsyncAppContext, Model, SharedString};
use language::{AutoindentMode, Buffer, BufferSnapshot};
use project::{Project, ProjectPath};
use std::{cmp, ops::Range, path::Path, sync::Arc};
use text::{AnchorRangeExt as _, Bias, OffsetRangeExt as _, Point};
#[derive(Clone, Debug)]
pub(crate) struct AssistantPatch {
pub range: Range<language::Anchor>,
pub title: SharedString,
pub edits: Arc<[Result<AssistantEdit>]>,
pub status: AssistantPatchStatus,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum AssistantPatchStatus {
Pending,
Ready,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct AssistantEdit {
pub path: String,
pub kind: AssistantEditKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssistantEditKind {
Update {
old_text: String,
new_text: String,
description: Option<String>,
},
Create {
new_text: String,
description: Option<String>,
},
InsertBefore {
old_text: String,
new_text: String,
description: Option<String>,
},
InsertAfter {
old_text: String,
new_text: String,
description: Option<String>,
},
Delete {
old_text: String,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ResolvedPatch {
pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>,
pub errors: Vec<AssistantPatchResolutionError>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedEditGroup {
pub context_range: Range<language::Anchor>,
pub edits: Vec<ResolvedEdit>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedEdit {
range: Range<language::Anchor>,
new_text: String,
description: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct AssistantPatchResolutionError {
pub edit_ix: usize,
pub message: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum SearchDirection {
Up,
Left,
Diagonal,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct SearchState {
cost: u32,
direction: SearchDirection,
}
impl SearchState {
fn new(cost: u32, direction: SearchDirection) -> Self {
Self { cost, direction }
}
}
struct SearchMatrix {
cols: usize,
data: Vec<SearchState>,
}
impl SearchMatrix {
fn new(rows: usize, cols: usize) -> Self {
SearchMatrix {
cols,
data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
}
}
fn get(&self, row: usize, col: usize) -> SearchState {
self.data[row * self.cols + col]
}
fn set(&mut self, row: usize, col: usize, cost: SearchState) {
self.data[row * self.cols + col] = cost;
}
}
impl ResolvedPatch {
pub fn apply(&self, editor: &ProposedChangesEditor, cx: &mut AppContext) {
for (buffer, groups) in &self.edit_groups {
let branch = editor.branch_buffer_for_base(buffer).unwrap();
Self::apply_edit_groups(groups, &branch, cx);
}
editor.recalculate_all_buffer_diffs();
}
fn apply_edit_groups(
groups: &Vec<ResolvedEditGroup>,
buffer: &Model<Buffer>,
cx: &mut AppContext,
) {
let mut edits = Vec::new();
for group in groups {
for suggestion in &group.edits {
edits.push((suggestion.range.clone(), suggestion.new_text.clone()));
}
}
buffer.update(cx, |buffer, cx| {
buffer.edit(
edits,
Some(AutoindentMode::Block {
original_indent_columns: Vec::new(),
}),
cx,
);
});
}
}
impl ResolvedEdit {
pub fn try_merge(&mut self, other: &Self, buffer: &text::BufferSnapshot) -> bool {
let range = &self.range;
let other_range = &other.range;
// Don't merge if we don't contain the other suggestion.
if range.start.cmp(&other_range.start, buffer).is_gt()
|| range.end.cmp(&other_range.end, buffer).is_lt()
{
return false;
}
let other_offset_range = other_range.to_offset(buffer);
let offset_range = range.to_offset(buffer);
// If the other range is empty at the start of this edit's range, combine the new text
if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
self.new_text = format!("{}\n{}", other.new_text, self.new_text);
self.range.start = other_range.start;
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
*description = format!("{}\n{}", other_description, description)
}
} else {
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
description.push('\n');
description.push_str(other_description);
}
}
true
}
}
impl AssistantEdit {
pub fn new(
path: Option<String>,
operation: Option<String>,
old_text: Option<String>,
new_text: Option<String>,
description: Option<String>,
) -> Result<Self> {
let path = path.ok_or_else(|| anyhow!("missing path"))?;
let operation = operation.ok_or_else(|| anyhow!("missing operation"))?;
let kind = match operation.as_str() {
"update" => AssistantEditKind::Update {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
description,
},
"insert_before" => AssistantEditKind::InsertBefore {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
description,
},
"insert_after" => AssistantEditKind::InsertAfter {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
description,
},
"delete" => AssistantEditKind::Delete {
old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
},
"create" => AssistantEditKind::Create {
description,
new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
},
_ => Err(anyhow!("unknown operation {operation:?}"))?,
};
Ok(Self { path, kind })
}
pub async fn resolve(
&self,
project: Model<Project>,
mut cx: AsyncAppContext,
) -> Result<(Model<Buffer>, ResolvedEdit)> {
let path = self.path.clone();
let kind = self.kind.clone();
let buffer = project
.update(&mut cx, |project, cx| {
let project_path = project
.find_project_path(Path::new(&path), cx)
.or_else(|| {
// If we couldn't find a project path for it, put it in the active worktree
// so that when we create the buffer, it can be saved.
let worktree = project
.active_entry()
.and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
.or_else(|| project.worktrees(cx).next())?;
let worktree = worktree.read(cx);
Some(ProjectPath {
worktree_id: worktree.id(),
path: Arc::from(Path::new(&path)),
})
})
.with_context(|| format!("worktree not found for {:?}", path))?;
anyhow::Ok(project.open_buffer(project_path, cx))
})??
.await?;
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
let suggestion = cx
.background_executor()
.spawn(async move { kind.resolve(&snapshot) })
.await;
Ok((buffer, suggestion))
}
}
impl AssistantEditKind {
fn resolve(self, snapshot: &BufferSnapshot) -> ResolvedEdit {
match self {
Self::Update {
old_text,
new_text,
description,
} => {
let range = Self::resolve_location(&snapshot, &old_text);
ResolvedEdit {
range,
new_text,
description,
}
}
Self::Create {
new_text,
description,
} => ResolvedEdit {
range: text::Anchor::MIN..text::Anchor::MAX,
description,
new_text,
},
Self::InsertBefore {
old_text,
mut new_text,
description,
} => {
let range = Self::resolve_location(&snapshot, &old_text);
new_text.push('\n');
ResolvedEdit {
range: range.start..range.start,
new_text,
description,
}
}
Self::InsertAfter {
old_text,
mut new_text,
description,
} => {
let range = Self::resolve_location(&snapshot, &old_text);
new_text.insert(0, '\n');
ResolvedEdit {
range: range.end..range.end,
new_text,
description,
}
}
Self::Delete { old_text } => {
let range = Self::resolve_location(&snapshot, &old_text);
ResolvedEdit {
range,
new_text: String::new(),
description: None,
}
}
}
}
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
const INSERTION_COST: u32 = 3;
const DELETION_COST: u32 = 10;
const WHITESPACE_INSERTION_COST: u32 = 1;
const WHITESPACE_DELETION_COST: u32 = 1;
let buffer_len = buffer.len();
let query_len = search_query.len();
let mut matrix = SearchMatrix::new(query_len + 1, buffer_len + 1);
let mut leading_deletion_cost = 0_u32;
for (row, query_byte) in search_query.bytes().enumerate() {
let deletion_cost = if query_byte.is_ascii_whitespace() {
WHITESPACE_DELETION_COST
} else {
DELETION_COST
};
leading_deletion_cost = leading_deletion_cost.saturating_add(deletion_cost);
matrix.set(
row + 1,
0,
SearchState::new(leading_deletion_cost, SearchDirection::Diagonal),
);
for (col, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
let insertion_cost = if buffer_byte.is_ascii_whitespace() {
WHITESPACE_INSERTION_COST
} else {
INSERTION_COST
};
let up = SearchState::new(
matrix.get(row, col + 1).cost.saturating_add(deletion_cost),
SearchDirection::Up,
);
let left = SearchState::new(
matrix.get(row + 1, col).cost.saturating_add(insertion_cost),
SearchDirection::Left,
);
let diagonal = SearchState::new(
if query_byte == *buffer_byte {
matrix.get(row, col).cost
} else {
matrix
.get(row, col)
.cost
.saturating_add(deletion_cost + insertion_cost)
},
SearchDirection::Diagonal,
);
matrix.set(row + 1, col + 1, up.min(left).min(diagonal));
}
}
// Traceback to find the best match
let mut best_buffer_end = buffer_len;
let mut best_cost = u32::MAX;
for col in 1..=buffer_len {
let cost = matrix.get(query_len, col).cost;
if cost < best_cost {
best_cost = cost;
best_buffer_end = col;
}
}
let mut query_ix = query_len;
let mut buffer_ix = best_buffer_end;
while query_ix > 0 && buffer_ix > 0 {
let current = matrix.get(query_ix, buffer_ix);
match current.direction {
SearchDirection::Diagonal => {
query_ix -= 1;
buffer_ix -= 1;
}
SearchDirection::Up => {
query_ix -= 1;
}
SearchDirection::Left => {
buffer_ix -= 1;
}
}
}
let mut start = buffer.offset_to_point(buffer.clip_offset(buffer_ix, Bias::Left));
start.column = 0;
let mut end = buffer.offset_to_point(buffer.clip_offset(best_buffer_end, Bias::Right));
if end.column > 0 {
end.column = buffer.line_len(end.row);
}
buffer.anchor_after(start)..buffer.anchor_before(end)
}
}
impl AssistantPatch {
pub(crate) async fn resolve(
&self,
project: Model<Project>,
cx: &mut AsyncAppContext,
) -> ResolvedPatch {
let mut resolve_tasks = Vec::new();
for (ix, edit) in self.edits.iter().enumerate() {
if let Ok(edit) = edit.as_ref() {
resolve_tasks.push(
edit.resolve(project.clone(), cx.clone())
.map_err(move |error| (ix, error)),
);
}
}
let edits = future::join_all(resolve_tasks).await;
let mut errors = Vec::new();
let mut edits_by_buffer = HashMap::default();
for entry in edits {
match entry {
Ok((buffer, edit)) => {
edits_by_buffer
.entry(buffer)
.or_insert_with(Vec::new)
.push(edit);
}
Err((edit_ix, error)) => errors.push(AssistantPatchResolutionError {
edit_ix,
message: error.to_string(),
}),
}
}
// Expand the context ranges of each edit and group edits with overlapping context ranges.
let mut edit_groups_by_buffer = HashMap::default();
for (buffer, edits) in edits_by_buffer {
if let Ok(snapshot) = buffer.update(cx, |buffer, _| buffer.text_snapshot()) {
edit_groups_by_buffer.insert(buffer, Self::group_edits(edits, &snapshot));
}
}
ResolvedPatch {
edit_groups: edit_groups_by_buffer,
errors,
}
}
fn group_edits(
mut edits: Vec<ResolvedEdit>,
snapshot: &text::BufferSnapshot,
) -> Vec<ResolvedEditGroup> {
let mut edit_groups = Vec::<ResolvedEditGroup>::new();
// Sort edits by their range so that earlier, larger ranges come first
edits.sort_by(|a, b| a.range.cmp(&b.range, &snapshot));
// Merge overlapping edits
edits.dedup_by(|a, b| b.try_merge(a, &snapshot));
// Create context ranges for each edit
for edit in edits {
let context_range = {
let edit_point_range = edit.range.to_point(&snapshot);
let start_row = edit_point_range.start.row.saturating_sub(5);
let end_row = cmp::min(edit_point_range.end.row + 5, snapshot.max_point().row);
let start = snapshot.anchor_before(Point::new(start_row, 0));
let end = snapshot.anchor_after(Point::new(end_row, snapshot.line_len(end_row)));
start..end
};
if let Some(last_group) = edit_groups.last_mut() {
if last_group
.context_range
.end
.cmp(&context_range.start, &snapshot)
.is_ge()
{
// Merge with the previous group if context ranges overlap
last_group.context_range.end = context_range.end;
last_group.edits.push(edit);
} else {
// Create a new group
edit_groups.push(ResolvedEditGroup {
context_range,
edits: vec![edit],
});
}
} else {
// Create the first group
edit_groups.push(ResolvedEditGroup {
context_range,
edits: vec![edit],
});
}
}
edit_groups
}
pub fn path_count(&self) -> usize {
self.paths().count()
}
pub fn paths(&self) -> impl '_ + Iterator<Item = &str> {
let mut prev_path = None;
self.edits.iter().filter_map(move |edit| {
if let Ok(edit) = edit {
let path = Some(edit.path.as_str());
if path != prev_path {
prev_path = path;
return path;
}
}
None
})
}
}
impl PartialEq for AssistantPatch {
fn eq(&self, other: &Self) -> bool {
self.range == other.range
&& self.title == other.title
&& Arc::ptr_eq(&self.edits, &other.edits)
}
}
impl Eq for AssistantPatch {}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{AppContext, Context};
use language::{
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
};
use settings::SettingsStore;
use ui::BorrowAppContext;
use unindent::Unindent as _;
use util::test::{generate_marked_text, marked_text_ranges};
#[gpui::test]
fn test_resolve_location(cx: &mut AppContext) {
assert_location_resolution(
concat!(
" Lorem\n",
"« ipsum\n",
" dolor sit amet»\n",
" consecteur",
),
"ipsum\ndolor",
cx,
);
assert_location_resolution(
&"
«fn foo1(a: usize) -> usize {
40
fn foo2(b: usize) -> usize {
42
}
"
.unindent(),
"fn foo1(b: usize) {\n40\n}",
cx,
);
assert_location_resolution(
&"
fn main() {
« Foo
.bar()
.baz()
.qux()»
}
fn foo2(b: usize) -> usize {
42
}
"
.unindent(),
"Foo.bar.baz.qux()",
cx,
);
assert_location_resolution(
&"
class Something {
one() { return 1; }
« two() { return 2222; }
three() { return 333; }
four() { return 4444; }
five() { return 5555; }
six() { return 6666; }
» seven() { return 7; }
eight() { return 8; }
}
"
.unindent(),
&"
two() { return 2222; }
four() { return 4444; }
five() { return 5555; }
six() { return 6666; }
"
.unindent(),
cx,
);
}
#[gpui::test]
fn test_resolve_edits(cx: &mut AppContext) {
init_test(cx);
assert_edits(
"
/// A person
struct Person {
name: String,
age: usize,
}
/// A dog
struct Dog {
weight: f32,
}
impl Person {
fn name(&self) -> &str {
&self.name
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "
name: String,
"
.unindent(),
new_text: "
first_name: String,
last_name: String,
"
.unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "
fn name(&self) -> &str {
&self.name
}
"
.unindent(),
new_text: "
fn name(&self) -> String {
format!(\"{} {}\", self.first_name, self.last_name)
}
"
.unindent(),
description: None,
},
],
"
/// A person
struct Person {
first_name: String,
last_name: String,
age: usize,
}
/// A dog
struct Dog {
weight: f32,
}
impl Person {
fn name(&self) -> String {
format!(\"{} {}\", self.first_name, self.last_name)
}
}
"
.unindent(),
cx,
);
// Ensure InsertBefore merges correctly with Update of the same text
assert_edits(
"
fn foo() {
}
"
.unindent(),
vec![
AssistantEditKind::InsertBefore {
old_text: "
fn foo() {"
.unindent(),
new_text: "
fn bar() {
qux();
}"
.unindent(),
description: Some("implement bar".into()),
},
AssistantEditKind::Update {
old_text: "
fn foo() {
}"
.unindent(),
new_text: "
fn foo() {
bar();
}"
.unindent(),
description: Some("call bar in foo".into()),
},
AssistantEditKind::InsertAfter {
old_text: "
fn foo() {
}
"
.unindent(),
new_text: "
fn qux() {
// todo
}
"
.unindent(),
description: Some("implement qux".into()),
},
],
"
fn bar() {
qux();
}
fn foo() {
bar();
}
fn qux() {
// todo
}
"
.unindent(),
cx,
);
// Correctly indent new text when replacing multiple adjacent indented blocks.
assert_edits(
"
impl Numbers {
fn one() {
1
}
fn two() {
2
}
fn three() {
3
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "
fn one() {
1
}
"
.unindent(),
new_text: "
fn one() {
101
}
"
.unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "
fn two() {
2
}
"
.unindent(),
new_text: "
fn two() {
102
}
"
.unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "
fn three() {
3
}
"
.unindent(),
new_text: "
fn three() {
103
}
"
.unindent(),
description: None,
},
],
"
impl Numbers {
fn one() {
101
}
fn two() {
102
}
fn three() {
103
}
}
"
.unindent(),
cx,
);
assert_edits(
"
impl Person {
fn set_name(&mut self, name: String) {
self.name = name;
}
fn name(&self) -> String {
return self.name;
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "self.name = name;".unindent(),
new_text: "self._name = name;".unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "return self.name;\n".unindent(),
new_text: "return self._name;\n".unindent(),
description: None,
},
],
"
impl Person {
fn set_name(&mut self, name: String) {
self._name = name;
}
fn name(&self) -> String {
return self._name;
}
}
"
.unindent(),
cx,
);
}
fn init_test(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
});
}
#[track_caller]
fn assert_location_resolution(
text_with_expected_range: &str,
query: &str,
cx: &mut AppContext,
) {
let (text, _) = marked_text_ranges(text_with_expected_range, false);
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
let snapshot = buffer.read(cx).snapshot();
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
let text_with_actual_range = generate_marked_text(&text, &[range], false);
pretty_assertions::assert_eq!(text_with_actual_range, text_with_expected_range);
}
#[track_caller]
fn assert_edits(
old_text: String,
edits: Vec<AssistantEditKind>,
new_text: String,
cx: &mut AppContext,
) {
let buffer =
cx.new_model(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.read(cx).snapshot();
let resolved_edits = edits
.into_iter()
.map(|kind| kind.resolve(&snapshot))
.collect();
let edit_groups = AssistantPatch::group_edits(resolved_edits, &snapshot);
ResolvedPatch::apply_edit_groups(&edit_groups, &buffer, cx);
let actual_new_text = buffer.read(cx).text();
pretty_assertions::assert_eq!(actual_new_text, new_text);
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
..Default::default()
},
Some(language::tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(
r#"
(call_expression) @indent
(field_expression) @indent
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
"#,
)
.unwrap()
}
}

View File

@@ -45,15 +45,6 @@ pub struct ProjectSlashCommandPromptContext {
pub context_buffer: String,
}
/// Context required to generate a workflow step resolution prompt.
#[derive(Debug, Serialize)]
pub struct StepResolutionContext {
/// The full context, including <step>...</step> tags
pub workflow_context: String,
/// The text of the specific step from the context to resolve
pub step_to_resolve: String,
}
pub struct PromptLoadingParams<'a> {
pub fs: Arc<dyn Fs>,
pub repo_path: Option<PathBuf>,
@@ -213,7 +204,7 @@ impl PromptBuilder {
Ok(())
}
pub fn generate_content_prompt(
pub fn generate_inline_transformation_prompt(
&self,
user_prompt: String,
language_name: Option<&LanguageName>,
@@ -320,7 +311,7 @@ impl PromptBuilder {
}
pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
self.handlebars.lock().render("edit_workflow", &())
self.handlebars.lock().render("suggest_edits", &())
}
pub fn generate_project_slash_command_prompt(

View File

@@ -34,7 +34,6 @@ pub mod search_command;
pub mod symbols_command;
pub mod tab_command;
pub mod terminal_command;
pub mod workflow_command;
pub(crate) struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>,

View File

@@ -1,7 +1,8 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
@@ -17,6 +18,8 @@ use ui::{BorrowAppContext, WindowContext};
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub struct AutoSlashCommandFeatureFlag;
impl FeatureFlag for AutoSlashCommandFeatureFlag {
@@ -92,7 +95,7 @@ impl SlashCommand for AutoCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};
@@ -144,7 +147,8 @@ impl SlashCommand for AutoCommand {
text: prompt,
sections: Vec::new(),
run_commands_in_text: true,
})
}
.to_event_stream())
})
}
}

View File

@@ -1,6 +1,8 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -123,7 +125,7 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let project = workspace.project().clone();
let fs = workspace.project().read(cx).fs().clone();
@@ -145,7 +147,8 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
metadata: None,
}],
run_commands_in_text: false,
})
}
.to_event_stream())
})
});
output.unwrap_or_else(|error| Task::ready(Err(error)))

View File

@@ -1,14 +1,14 @@
use anyhow::{anyhow, Result};
use assistant_slash_command::{
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
SlashCommandOutputSection,
SlashCommandOutputSection, SlashCommandResult,
};
use collections::HashMap;
use context_servers::{
manager::{ContextServer, ContextServerManager},
protocol::PromptInfo,
types::Prompt,
};
use gpui::{Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -16,13 +16,15 @@ use text::LineEnding;
use ui::{IconName, SharedString};
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub struct ContextServerSlashCommand {
server_id: String,
prompt: PromptInfo,
prompt: Prompt,
}
impl ContextServerSlashCommand {
pub fn new(server: &Arc<ContextServer>, prompt: PromptInfo) -> Self {
pub fn new(server: &Arc<ContextServer>, prompt: Prompt) -> Self {
Self {
server_id: server.id.clone(),
prompt,
@@ -35,12 +37,28 @@ impl SlashCommand for ContextServerSlashCommand {
self.prompt.name.clone()
}
fn label(&self, cx: &AppContext) -> language::CodeLabel {
let mut parts = vec![self.prompt.name.as_str()];
if let Some(args) = &self.prompt.arguments {
if let Some(arg) = args.first() {
parts.push(arg.name.as_str());
}
}
create_label_for_command(&parts[0], &parts[1..], cx)
}
fn description(&self) -> String {
format!("Run context server command: {}", self.prompt.name)
match &self.prompt.description {
Some(desc) => desc.clone(),
None => format!("Run '{}' from {}", self.prompt.name, self.server_id),
}
}
fn menu_text(&self) -> String {
format!("Run '{}' from {}", self.prompt.name, self.server_id)
match &self.prompt.description {
Some(desc) => desc.clone(),
None => format!("Run '{}' from {}", self.prompt.name, self.server_id),
}
}
fn requires_argument(&self) -> bool {
@@ -111,7 +129,7 @@ impl SlashCommand for ContextServerSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone();
@@ -128,7 +146,28 @@ impl SlashCommand for ContextServerSlashCommand {
return Err(anyhow!("Context server not initialized"));
};
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
let mut prompt = result.prompt;
// Check that there are only user roles
if result
.messages
.iter()
.any(|msg| !matches!(msg.role, context_servers::types::SamplingRole::User))
{
return Err(anyhow!(
"Prompt contains non-user roles, which is not supported"
));
}
// Extract text from user messages into a single prompt string
let mut prompt = result
.messages
.into_iter()
.filter_map(|msg| match msg.content {
context_servers::types::SamplingContent::Text { text } => Some(text),
_ => None,
})
.collect::<Vec<String>>()
.join("\n\n");
// We must normalize the line endings here, since servers might return CR characters.
LineEnding::normalize(&mut prompt);
@@ -146,7 +185,8 @@ impl SlashCommand for ContextServerSlashCommand {
}],
text: prompt,
run_commands_in_text: false,
})
}
.to_event_stream())
})
} else {
Task::ready(Err(anyhow!("Context server not found")))
@@ -154,7 +194,7 @@ impl SlashCommand for ContextServerSlashCommand {
}
}
fn completion_argument(prompt: &PromptInfo, arguments: &[String]) -> Result<(String, String)> {
fn completion_argument(prompt: &Prompt, arguments: &[String]) -> Result<(String, String)> {
if arguments.is_empty() {
return Err(anyhow!("No arguments given"));
}
@@ -170,7 +210,7 @@ fn completion_argument(prompt: &PromptInfo, arguments: &[String]) -> Result<(Str
}
}
fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap<String, String>> {
fn prompt_arguments(prompt: &Prompt, arguments: &[String]) -> Result<HashMap<String, String>> {
match &prompt.arguments {
Some(args) if args.len() > 1 => Err(anyhow!(
"Prompt has more than one argument, which is not supported"
@@ -199,7 +239,7 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
/// MCP servers can return prompts with multiple arguments. Since we only
/// support one argument, we ignore all others. This is the necessary predicate
/// for this.
pub fn acceptable_prompt(prompt: &PromptInfo) -> bool {
pub fn acceptable_prompt(prompt: &Prompt) -> bool {
match &prompt.arguments {
None => true,
Some(args) if args.len() <= 1 => true,

View File

@@ -1,7 +1,9 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::{
@@ -48,7 +50,7 @@ impl SlashCommand for DefaultSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let store = PromptStore::global(cx);
cx.background_executor().spawn(async move {
let store = store.await?;
@@ -76,7 +78,8 @@ impl SlashCommand for DefaultSlashCommand {
}],
text,
run_commands_in_text: true,
})
}
.to_event_stream())
})
}
}

View File

@@ -1,7 +1,8 @@
use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
use anyhow::Result;
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use collections::HashSet;
use futures::future;
@@ -37,7 +38,7 @@ impl SlashCommand for DeltaSlashCommand {
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
unimplemented!()
Task::ready(Err(anyhow!("this command does not require argument")))
}
fn run(
@@ -48,7 +49,7 @@ impl SlashCommand for DeltaSlashCommand {
workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new();
let mut file_command_new_outputs = Vec::new();
@@ -85,25 +86,28 @@ impl SlashCommand for DeltaSlashCommand {
.zip(file_command_new_outputs)
{
if let Ok(new_output) = new_output {
if let Some(file_command_range) = new_output.sections.first() {
let new_text = &new_output.text[file_command_range.range.clone()];
if old_text.chars().ne(new_text.chars()) {
output.sections.extend(new_output.sections.into_iter().map(
|section| SlashCommandOutputSection {
range: output.text.len() + section.range.start
..output.text.len() + section.range.end,
icon: section.icon,
label: section.label,
metadata: section.metadata,
},
));
output.text.push_str(&new_output.text);
if let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await
{
if let Some(file_command_range) = new_output.sections.first() {
let new_text = &new_output.text[file_command_range.range.clone()];
if old_text.chars().ne(new_text.chars()) {
output.sections.extend(new_output.sections.into_iter().map(
|section| SlashCommandOutputSection {
range: output.text.len() + section.range.start
..output.text.len() + section.range.end,
icon: section.icon,
label: section.label,
metadata: section.metadata,
},
));
output.text.push_str(&new_output.text);
}
}
}
}
}
Ok(output)
Ok(output.to_event_stream())
})
}
}

View File

@@ -1,6 +1,8 @@
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{
@@ -19,6 +21,8 @@ use util::paths::PathMatcher;
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub(crate) struct DiagnosticsSlashCommand;
impl DiagnosticsSlashCommand {
@@ -167,7 +171,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
@@ -176,7 +180,11 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
cx.spawn(move |_| async move { task.await?.ok_or_else(|| anyhow!("No diagnostics found")) })
cx.spawn(move |_| async move {
task.await?
.map(|output| output.to_event_stream())
.ok_or_else(|| anyhow!("No diagnostics found"))
})
}
}

View File

@@ -6,6 +6,7 @@ use std::time::Duration;
use anyhow::{anyhow, bail, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
use indexed_docs::{
@@ -274,7 +275,7 @@ impl SlashCommand for DocsSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
if arguments.is_empty() {
return Task::ready(Err(anyhow!("missing an argument")));
};
@@ -355,7 +356,8 @@ impl SlashCommand for DocsSlashCommand {
})
.collect(),
run_commands_in_text: false,
})
}
.to_event_stream())
})
}
}

View File

@@ -6,6 +6,7 @@ use std::sync::Arc;
use anyhow::{anyhow, bail, Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use futures::AsyncReadExt;
use gpui::{Task, WeakView};
@@ -133,7 +134,7 @@ impl SlashCommand for FetchSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let Some(argument) = arguments.first() else {
return Task::ready(Err(anyhow!("missing URL")));
};
@@ -166,7 +167,8 @@ impl SlashCommand for FetchSlashCommand {
metadata: None,
}],
run_commands_in_text: false,
})
}
.to_event_stream())
})
}
}

View File

@@ -1,11 +1,16 @@
use super::{diagnostics_command::collect_buffer_diagnostics, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
SlashCommandOutput, SlashCommandOutputSection, SlashCommandResult,
};
use futures::channel::mpsc;
use futures::Stream;
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
use smol::stream::StreamExt;
use std::{
fmt::Write,
ops::{Range, RangeInclusive},
@@ -16,6 +21,8 @@ use ui::prelude::*;
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::diagnostics_command::collect_buffer_diagnostics;
pub(crate) struct FileSlashCommand;
impl FileSlashCommand {
@@ -181,7 +188,7 @@ impl SlashCommand for FileSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
@@ -190,7 +197,12 @@ impl SlashCommand for FileSlashCommand {
return Task::ready(Err(anyhow!("missing path")));
};
collect_files(workspace.read(cx).project().clone(), arguments, cx)
Task::ready(Ok(collect_files(
workspace.read(cx).project().clone(),
arguments,
cx,
)
.boxed()))
}
}
@@ -198,7 +210,7 @@ fn collect_files(
project: Model<Project>,
glob_inputs: &[String],
cx: &mut AppContext,
) -> Task<Result<SlashCommandOutput>> {
) -> impl Stream<Item = Result<SlashCommandEvent>> {
let Ok(matchers) = glob_inputs
.into_iter()
.map(|glob_input| {
@@ -207,7 +219,7 @@ fn collect_files(
})
.collect::<anyhow::Result<Vec<custom_path_matcher::PathMatcher>>>()
else {
return Task::ready(Err(anyhow!("invalid path")));
return futures::stream::once(async { Err(anyhow!("invalid path")) }).boxed();
};
let project_handle = project.downgrade();
@@ -217,11 +229,11 @@ fn collect_files(
.map(|worktree| worktree.read(cx).snapshot())
.collect::<Vec<_>>();
let (events_tx, events_rx) = mpsc::unbounded();
cx.spawn(|mut cx| async move {
let mut output = SlashCommandOutput::default();
for snapshot in snapshots {
let worktree_id = snapshot.id();
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
let mut directory_stack: Vec<Arc<Path>> = Vec::new();
let mut folded_directory_names_stack = Vec::new();
let mut is_top_level_directory = true;
@@ -237,17 +249,19 @@ fn collect_files(
continue;
}
while let Some((dir, _, _)) = directory_stack.last() {
while let Some(dir) = directory_stack.last() {
if entry.path.starts_with(dir) {
break;
}
let (_, entry_name, start) = directory_stack.pop().unwrap();
output.sections.push(build_entry_output_section(
start..output.text.len().saturating_sub(1),
Some(&PathBuf::from(entry_name)),
true,
None,
));
directory_stack.pop().unwrap();
events_tx
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false,
},
)))?;
}
let filename = entry
@@ -279,23 +293,46 @@ fn collect_files(
continue;
}
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
let entry_start = output.text.len();
if prefix_paths.is_empty() {
if is_top_level_directory {
output
.text
.push_str(&path_including_worktree_name.to_string_lossy());
let label = if is_top_level_directory {
is_top_level_directory = false;
path_including_worktree_name.to_string_lossy().to_string()
} else {
output.text.push_str(&filename);
}
directory_stack.push((entry.path.clone(), filename, entry_start));
filename
};
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
icon: IconName::Folder,
label: label.clone().into(),
metadata: None,
}))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: label,
run_commands_in_text: false,
},
)))?;
directory_stack.push(entry.path.clone());
} else {
let entry_name = format!("{}/{}", prefix_paths, &filename);
output.text.push_str(&entry_name);
directory_stack.push((entry.path.clone(), entry_name, entry_start));
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
icon: IconName::Folder,
label: entry_name.clone().into(),
metadata: None,
}))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: entry_name,
run_commands_in_text: false,
},
)))?;
directory_stack.push(entry.path.clone());
}
output.text.push('\n');
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false,
},
)))?;
} else if entry.is_file() {
let Some(open_buffer_task) = project_handle
.update(&mut cx, |project, cx| {
@@ -306,6 +343,7 @@ fn collect_files(
continue;
};
if let Some(buffer) = open_buffer_task.await.log_err() {
let mut output = SlashCommandOutput::default();
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
append_buffer_to_output(
&snapshot,
@@ -313,33 +351,24 @@ fn collect_files(
&mut output,
)
.log_err();
let mut buffer_events = output.to_event_stream();
while let Some(event) = buffer_events.next().await {
events_tx.unbounded_send(event)?;
}
}
}
}
while let Some((dir, entry, start)) = directory_stack.pop() {
if directory_stack.is_empty() {
let mut root_path = PathBuf::new();
root_path.push(snapshot.root_name());
root_path.push(&dir);
output.sections.push(build_entry_output_section(
start..output.text.len(),
Some(&root_path),
true,
None,
));
} else {
output.sections.push(build_entry_output_section(
start..output.text.len(),
Some(&PathBuf::from(entry.as_str())),
true,
None,
));
}
while let Some(_) = directory_stack.pop() {
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
}
}
Ok(output)
anyhow::Ok(())
})
.detach_and_log_err(cx);
events_rx.boxed()
}
pub fn codeblock_fence_for_path(
@@ -524,11 +553,14 @@ pub fn append_buffer_to_output(
#[cfg(test)]
mod test {
use assistant_slash_command::SlashCommandOutput;
use fs::FakeFs;
use gpui::TestAppContext;
use pretty_assertions::assert_eq;
use project::Project;
use serde_json::json;
use settings::SettingsStore;
use smol::stream::StreamExt;
use crate::slash_command::file_command::collect_files;
@@ -569,8 +601,9 @@ mod test {
let project = Project::test(fs, ["/root".as_ref()], cx).await;
let result_1 = cx
.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx))
let result_1 =
cx.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx));
let result_1 = SlashCommandOutput::from_event_stream(result_1.boxed())
.await
.unwrap();
@@ -578,17 +611,17 @@ mod test {
// 4 files + 2 directories
assert_eq!(result_1.sections.len(), 6);
let result_2 = cx
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
let result_2 =
cx.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx));
let result_2 = SlashCommandOutput::from_event_stream(result_2.boxed())
.await
.unwrap();
assert_eq!(result_1, result_2);
let result = cx
.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx))
.await
.unwrap();
let result =
cx.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx).boxed());
let result = SlashCommandOutput::from_event_stream(result).await.unwrap();
assert!(result.text.starts_with("root/dir"));
// 5 files + 2 directories
@@ -631,8 +664,9 @@ mod test {
let project = Project::test(fs, ["/zed".as_ref()], cx).await;
let result = cx
.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx))
let result =
cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx));
let result = SlashCommandOutput::from_event_stream(result.boxed())
.await
.unwrap();
@@ -692,8 +726,9 @@ mod test {
let project = Project::test(fs, ["/zed".as_ref()], cx).await;
let result = cx
.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx))
let result =
cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx));
let result = SlashCommandOutput::from_event_stream(result.boxed())
.await
.unwrap();
@@ -716,6 +751,8 @@ mod test {
assert_eq!(result.sections[6].label, "summercamp");
assert_eq!(result.sections[7].label, "zed/assets/themes");
assert_eq!(result.text, "zed/assets/themes\n```zed/assets/themes/LICENSE\n1\n```\n\nsummercamp\n```zed/assets/themes/summercamp/LICENSE\n1\n```\n\nsubdir\n```zed/assets/themes/summercamp/subdir/LICENSE\n1\n```\n\nsubsubdir\n```zed/assets/themes/summercamp/subdir/subsubdir/LICENSE\n3\n```\n\n");
// Ensure that the project lasts until after the last await
drop(project);
}

View File

@@ -4,6 +4,7 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use chrono::Local;
use gpui::{Task, WeakView};
@@ -48,7 +49,7 @@ impl SlashCommand for NowSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let now = Local::now();
let text = format!("Today is {now}.", now = now.to_rfc2822());
let range = 0..text.len();
@@ -62,6 +63,7 @@ impl SlashCommand for NowSlashCommand {
metadata: None,
}],
run_commands_in_text: false,
}))
}
.to_event_stream()))
}
}

View File

@@ -4,7 +4,7 @@ use super::{
};
use crate::PromptBuilder;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{Anchor, CodeLabel, LspAdapterDelegate};
@@ -76,7 +76,7 @@ impl SlashCommand for ProjectSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let model_registry = LanguageModelRegistry::read_global(cx);
let current_model = model_registry.active_model();
let prompt_builder = self.prompt_builder.clone();
@@ -162,7 +162,8 @@ impl SlashCommand for ProjectSlashCommand {
text: output,
sections,
run_commands_in_text: true,
})
}
.to_event_stream())
})
.await
})

View File

@@ -1,7 +1,9 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
@@ -61,7 +63,7 @@ impl SlashCommand for PromptSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let title = arguments.to_owned().join(" ");
if title.trim().is_empty() {
return Task::ready(Err(anyhow!("missing prompt name")));
@@ -100,7 +102,8 @@ impl SlashCommand for PromptSlashCommand {
metadata: None,
}],
run_commands_in_text: true,
})
}
.to_event_stream())
})
}
}

View File

@@ -1,10 +1,8 @@
use super::{
create_label_for_command,
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use anyhow::Result;
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
@@ -16,6 +14,9 @@ use std::{
use ui::{prelude::*, IconName};
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
use crate::slash_command::file_command::{build_entry_output_section, codeblock_fence_for_path};
pub(crate) struct SearchSlashCommandFeatureFlag;
impl FeatureFlag for SearchSlashCommandFeatureFlag {
@@ -63,7 +64,7 @@ impl SlashCommand for SearchSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};
@@ -129,6 +130,7 @@ impl SlashCommand for SearchSlashCommand {
sections,
run_commands_in_text: false,
}
.to_event_stream()
})
.await;

View File

@@ -1,6 +1,8 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use editor::Editor;
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -46,7 +48,7 @@ impl SlashCommand for OutlineSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else {
return Task::ready(Err(anyhow!("no active tab")));
@@ -83,7 +85,8 @@ impl SlashCommand for OutlineSlashCommand {
}],
text: outline_text,
run_commands_in_text: false,
})
}
.to_event_stream())
})
});

View File

@@ -1,6 +1,8 @@
use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
use anyhow::{Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
@@ -14,6 +16,8 @@ use ui::{ActiveTheme, WindowContext};
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::file_command::append_buffer_to_output;
pub(crate) struct TabSlashCommand;
const ALL_TABS_COMPLETION_ITEM: &str = "all";
@@ -132,7 +136,7 @@ impl SlashCommand for TabSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let tab_items_search = tab_items_for_queries(
Some(workspace),
arguments,
@@ -146,7 +150,7 @@ impl SlashCommand for TabSlashCommand {
for (full_path, buffer, _) in tab_items_search.await? {
append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
}
Ok(output)
Ok(output.to_event_stream())
})
}
}

View File

@@ -4,6 +4,7 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
@@ -62,7 +63,7 @@ impl SlashCommand for TerminalSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};
@@ -96,7 +97,8 @@ impl SlashCommand for TerminalSlashCommand {
metadata: None,
}],
run_commands_in_text: false,
}))
}
.to_event_stream()))
}
}

View File

@@ -1,79 +0,0 @@
use crate::prompts::PromptBuilder;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
pub(crate) struct WorkflowSlashCommand {
prompt_builder: Arc<PromptBuilder>,
}
impl WorkflowSlashCommand {
pub fn new(prompt_builder: Arc<PromptBuilder>) -> Self {
Self { prompt_builder }
}
}
impl SlashCommand for WorkflowSlashCommand {
fn name(&self) -> String {
"workflow".into()
}
fn description(&self) -> String {
"Insert prompt to opt into the edit workflow".into()
}
fn menu_text(&self) -> String {
self.description()
}
fn requires_argument(&self) -> bool {
false
}
fn complete_argument(
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let prompt_builder = self.prompt_builder.clone();
cx.spawn(|_cx| async move {
let text = prompt_builder.generate_workflow_prompt()?;
let range = 0..text.len();
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::Route,
label: "Workflow".into(),
metadata: None,
}],
run_commands_in_text: false,
})
})
}
}

View File

@@ -178,7 +178,7 @@ impl PickerDelegate for SlashCommandDelegate {
SlashCommandEntry::Info(info) => Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.spacing(ListItemSpacing::Dense)
.selected(selected)
.child(
h_flex()
@@ -224,7 +224,7 @@ impl PickerDelegate for SlashCommandDelegate {
SlashCommandEntry::Advert { renderer, .. } => Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.spacing(ListItemSpacing::Dense)
.selected(selected)
.child(renderer(cx)),
),

View File

@@ -1,6 +1,6 @@
use crate::{
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent,
ModelSelector, DEFAULT_CONTEXT_LINES,
ModelSelector, RequestType, DEFAULT_CONTEXT_LINES,
};
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
@@ -251,7 +251,7 @@ impl TerminalInlineAssistant {
.read(cx)
.active_context(cx)?
.read(cx)
.to_completion_request(cx),
.to_completion_request(RequestType::Chat, cx),
)
})
} else {

View File

@@ -1 +1,2 @@
pub mod context_server_tool;
pub mod now_tool;

View File

@@ -0,0 +1,82 @@
use anyhow::{anyhow, bail};
use assistant_tool::Tool;
use context_servers::manager::ContextServerManager;
use context_servers::types;
use gpui::Task;
pub struct ContextServerTool {
server_id: String,
tool: types::Tool,
}
impl ContextServerTool {
pub fn new(server_id: impl Into<String>, tool: types::Tool) -> Self {
Self {
server_id: server_id.into(),
tool,
}
}
}
impl Tool for ContextServerTool {
fn name(&self) -> String {
self.tool.name.clone()
}
fn description(&self) -> String {
self.tool.description.clone().unwrap_or_default()
}
fn input_schema(&self) -> serde_json::Value {
match &self.tool.input_schema {
serde_json::Value::Null => {
serde_json::json!({ "type": "object", "properties": [] })
}
serde_json::Value::Object(map) if map.is_empty() => {
serde_json::json!({ "type": "object", "properties": [] })
}
_ => self.tool.input_schema.clone(),
}
}
fn run(
self: std::sync::Arc<Self>,
input: serde_json::Value,
_workspace: gpui::WeakView<workspace::Workspace>,
cx: &mut ui::WindowContext,
) -> gpui::Task<gpui::Result<String>> {
let manager = ContextServerManager::global(cx);
let manager = manager.read(cx);
if let Some(server) = manager.get_server(&self.server_id) {
cx.foreground_executor().spawn({
let tool_name = self.tool.name.clone();
async move {
let Some(protocol) = server.client.read().clone() else {
bail!("Context server not initialized");
};
let arguments = if let serde_json::Value::Object(map) = input {
Some(map.into_iter().collect())
} else {
None
};
log::trace!(
"Running tool: {} with arguments: {:?}",
tool_name,
arguments
);
let response = protocol.run_tool(tool_name, arguments).await?;
let tool_result = match response.tool_result {
serde_json::Value::String(s) => s,
_ => serde_json::to_string(&response.tool_result)?,
};
Ok(tool_result)
}
})
} else {
Task::ready(Err(anyhow!("Context server not found")))
}
}
}

View File

@@ -1,507 +0,0 @@
use crate::{AssistantPanel, InlineAssistId, InlineAssistant};
use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
use editor::Editor;
use gpui::AsyncAppContext;
use gpui::{Model, Task, UpdateGlobal as _, View, WeakView, WindowContext};
use language::{Buffer, BufferSnapshot};
use project::{Project, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{ops::Range, path::Path, sync::Arc};
use text::Bias;
use workspace::Workspace;
#[derive(Debug)]
pub(crate) struct WorkflowStep {
pub range: Range<language::Anchor>,
pub leading_tags_end: text::Anchor,
pub trailing_tag_start: Option<text::Anchor>,
pub edits: Arc<[Result<WorkflowStepEdit>]>,
pub resolution_task: Option<Task<()>>,
pub resolution: Option<Arc<Result<WorkflowStepResolution>>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct WorkflowStepEdit {
pub path: String,
pub kind: WorkflowStepEditKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct WorkflowStepResolution {
pub title: String,
pub suggestion_groups: HashMap<Model<Buffer>, Vec<WorkflowSuggestionGroup>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WorkflowSuggestionGroup {
pub context_range: Range<language::Anchor>,
pub suggestions: Vec<WorkflowSuggestion>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum WorkflowSuggestion {
Update {
range: Range<language::Anchor>,
description: String,
},
CreateFile {
description: String,
},
InsertBefore {
position: language::Anchor,
description: String,
},
InsertAfter {
position: language::Anchor,
description: String,
},
Delete {
range: Range<language::Anchor>,
},
}
impl WorkflowSuggestion {
pub fn range(&self) -> Range<language::Anchor> {
match self {
Self::Update { range, .. } => range.clone(),
Self::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
Self::InsertBefore { position, .. } | Self::InsertAfter { position, .. } => {
*position..*position
}
Self::Delete { range, .. } => range.clone(),
}
}
pub fn description(&self) -> Option<&str> {
match self {
Self::Update { description, .. }
| Self::CreateFile { description }
| Self::InsertBefore { description, .. }
| Self::InsertAfter { description, .. } => Some(description),
Self::Delete { .. } => None,
}
}
fn description_mut(&mut self) -> Option<&mut String> {
match self {
Self::Update { description, .. }
| Self::CreateFile { description }
| Self::InsertBefore { description, .. }
| Self::InsertAfter { description, .. } => Some(description),
Self::Delete { .. } => None,
}
}
pub fn try_merge(&mut self, other: &Self, buffer: &BufferSnapshot) -> bool {
let range = self.range();
let other_range = other.range();
// Don't merge if we don't contain the other suggestion.
if range.start.cmp(&other_range.start, buffer).is_gt()
|| range.end.cmp(&other_range.end, buffer).is_lt()
{
return false;
}
if let Some(description) = self.description_mut() {
if let Some(other_description) = other.description() {
description.push('\n');
description.push_str(other_description);
}
}
true
}
pub fn show(
&self,
editor: &View<Editor>,
excerpt_id: editor::ExcerptId,
workspace: &WeakView<Workspace>,
assistant_panel: &View<AssistantPanel>,
cx: &mut WindowContext,
) -> Option<InlineAssistId> {
let mut initial_transaction_id = None;
let initial_prompt;
let suggestion_range;
let buffer = editor.read(cx).buffer().clone();
let snapshot = buffer.read(cx).snapshot(cx);
match self {
Self::Update {
range, description, ..
} => {
initial_prompt = description.clone();
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
}
Self::CreateFile { description } => {
initial_prompt = description.clone();
suggestion_range = editor::Anchor::min()..editor::Anchor::min();
}
Self::InsertBefore {
position,
description,
..
} => {
let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
initial_prompt = description.clone();
suggestion_range = buffer.update(cx, |buffer, cx| {
buffer.start_transaction(cx);
let line_start = buffer.insert_empty_line(position, true, true, cx);
initial_transaction_id = buffer.end_transaction(cx);
buffer.refresh_preview(cx);
let line_start = buffer.read(cx).anchor_before(line_start);
line_start..line_start
});
}
Self::InsertAfter {
position,
description,
..
} => {
let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
initial_prompt = description.clone();
suggestion_range = buffer.update(cx, |buffer, cx| {
buffer.start_transaction(cx);
let line_start = buffer.insert_empty_line(position, true, true, cx);
initial_transaction_id = buffer.end_transaction(cx);
buffer.refresh_preview(cx);
let line_start = buffer.read(cx).anchor_before(line_start);
line_start..line_start
});
}
Self::Delete { range, .. } => {
initial_prompt = "Delete".to_string();
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
}
}
InlineAssistant::update_global(cx, |inline_assistant, cx| {
Some(inline_assistant.suggest_assist(
editor,
suggestion_range,
initial_prompt,
initial_transaction_id,
false,
Some(workspace.clone()),
Some(assistant_panel),
cx,
))
})
}
}
impl WorkflowStepEdit {
pub fn new(
path: Option<String>,
operation: Option<String>,
search: Option<String>,
description: Option<String>,
) -> Result<Self> {
let path = path.ok_or_else(|| anyhow!("missing path"))?;
let operation = operation.ok_or_else(|| anyhow!("missing operation"))?;
let kind = match operation.as_str() {
"update" => WorkflowStepEditKind::Update {
search: search.ok_or_else(|| anyhow!("missing search"))?,
description: description.ok_or_else(|| anyhow!("missing description"))?,
},
"insert_before" => WorkflowStepEditKind::InsertBefore {
search: search.ok_or_else(|| anyhow!("missing search"))?,
description: description.ok_or_else(|| anyhow!("missing description"))?,
},
"insert_after" => WorkflowStepEditKind::InsertAfter {
search: search.ok_or_else(|| anyhow!("missing search"))?,
description: description.ok_or_else(|| anyhow!("missing description"))?,
},
"delete" => WorkflowStepEditKind::Delete {
search: search.ok_or_else(|| anyhow!("missing search"))?,
},
"create" => WorkflowStepEditKind::Create {
description: description.ok_or_else(|| anyhow!("missing description"))?,
},
_ => Err(anyhow!("unknown operation {operation:?}"))?,
};
Ok(Self { path, kind })
}
pub async fn resolve(
&self,
project: Model<Project>,
mut cx: AsyncAppContext,
) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> {
let path = self.path.clone();
let kind = self.kind.clone();
let buffer = project
.update(&mut cx, |project, cx| {
let project_path = project
.find_project_path(Path::new(&path), cx)
.or_else(|| {
// If we couldn't find a project path for it, put it in the active worktree
// so that when we create the buffer, it can be saved.
let worktree = project
.active_entry()
.and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
.or_else(|| project.worktrees(cx).next())?;
let worktree = worktree.read(cx);
Some(ProjectPath {
worktree_id: worktree.id(),
path: Arc::from(Path::new(&path)),
})
})
.with_context(|| format!("worktree not found for {:?}", path))?;
anyhow::Ok(project.open_buffer(project_path, cx))
})??
.await?;
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
let suggestion = cx
.background_executor()
.spawn(async move {
match kind {
WorkflowStepEditKind::Update {
search,
description,
} => {
let range = Self::resolve_location(&snapshot, &search);
WorkflowSuggestion::Update { range, description }
}
WorkflowStepEditKind::Create { description } => {
WorkflowSuggestion::CreateFile { description }
}
WorkflowStepEditKind::InsertBefore {
search,
description,
} => {
let range = Self::resolve_location(&snapshot, &search);
WorkflowSuggestion::InsertBefore {
position: range.start,
description,
}
}
WorkflowStepEditKind::InsertAfter {
search,
description,
} => {
let range = Self::resolve_location(&snapshot, &search);
WorkflowSuggestion::InsertAfter {
position: range.end,
description,
}
}
WorkflowStepEditKind::Delete { search } => {
let range = Self::resolve_location(&snapshot, &search);
WorkflowSuggestion::Delete { range }
}
}
})
.await;
Ok((buffer, suggestion))
}
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
const INSERTION_SCORE: f64 = -1.0;
const DELETION_SCORE: f64 = -1.0;
const REPLACEMENT_SCORE: f64 = -1.0;
const EQUALITY_SCORE: f64 = 5.0;
struct Matrix {
cols: usize,
data: Vec<f64>,
}
impl Matrix {
fn new(rows: usize, cols: usize) -> Self {
Matrix {
cols,
data: vec![0.0; rows * cols],
}
}
fn get(&self, row: usize, col: usize) -> f64 {
self.data[row * self.cols + col]
}
fn set(&mut self, row: usize, col: usize, value: f64) {
self.data[row * self.cols + col] = value;
}
}
let buffer_len = buffer.len();
let query_len = search_query.len();
let mut matrix = Matrix::new(query_len + 1, buffer_len + 1);
for (i, query_byte) in search_query.bytes().enumerate() {
for (j, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
let match_score = if query_byte == *buffer_byte {
EQUALITY_SCORE
} else {
REPLACEMENT_SCORE
};
let up = matrix.get(i + 1, j) + DELETION_SCORE;
let left = matrix.get(i, j + 1) + INSERTION_SCORE;
let diagonal = matrix.get(i, j) + match_score;
let score = up.max(left.max(diagonal)).max(0.);
matrix.set(i + 1, j + 1, score);
}
}
// Traceback to find the best match
let mut best_buffer_end = buffer_len;
let mut best_score = 0.0;
for col in 1..=buffer_len {
let score = matrix.get(query_len, col);
if score > best_score {
best_score = score;
best_buffer_end = col;
}
}
let mut query_ix = query_len;
let mut buffer_ix = best_buffer_end;
while query_ix > 0 && buffer_ix > 0 {
let current = matrix.get(query_ix, buffer_ix);
let up = matrix.get(query_ix - 1, buffer_ix);
let left = matrix.get(query_ix, buffer_ix - 1);
if current == left + INSERTION_SCORE {
buffer_ix -= 1;
} else if current == up + DELETION_SCORE {
query_ix -= 1;
} else {
query_ix -= 1;
buffer_ix -= 1;
}
}
let mut start = buffer.offset_to_point(buffer.clip_offset(buffer_ix, Bias::Left));
start.column = 0;
let mut end = buffer.offset_to_point(buffer.clip_offset(best_buffer_end, Bias::Right));
end.column = buffer.line_len(end.row);
buffer.anchor_after(start)..buffer.anchor_before(end)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "operation")]
pub enum WorkflowStepEditKind {
/// Rewrites the specified text entirely based on the given description.
/// This operation completely replaces the given text.
Update {
/// A string in the source text to apply the update to.
search: String,
/// A brief description of the transformation to apply to the symbol.
description: String,
},
/// Creates a new file with the given path based on the provided description.
/// This operation adds a new file to the codebase.
Create {
/// A brief description of the file to be created.
description: String,
},
/// Inserts text before the specified text in the source file.
InsertBefore {
/// A string in the source text to insert text before.
search: String,
/// A brief description of how the new text should be generated.
description: String,
},
/// Inserts text after the specified text in the source file.
InsertAfter {
/// A string in the source text to insert text after.
search: String,
/// A brief description of how the new text should be generated.
description: String,
},
/// Deletes the specified symbol from the containing file.
Delete {
/// A string in the source text to delete.
search: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{AppContext, Context};
use text::{OffsetRangeExt, Point};
#[gpui::test]
fn test_resolve_location(cx: &mut AppContext) {
{
let buffer = cx.new_model(|cx| {
Buffer::local(
concat!(
" Lorem\n",
" ipsum\n",
" dolor sit amet\n",
" consecteur",
),
cx,
)
});
let snapshot = buffer.read(cx).snapshot();
assert_eq!(
WorkflowStepEdit::resolve_location(&snapshot, "ipsum\ndolor").to_point(&snapshot),
Point::new(1, 0)..Point::new(2, 18)
);
}
{
let buffer = cx.new_model(|cx| {
Buffer::local(
concat!(
"fn foo1(a: usize) -> usize {\n",
" 42\n",
"}\n",
"\n",
"fn foo2(b: usize) -> usize {\n",
" 42\n",
"}\n",
),
cx,
)
});
let snapshot = buffer.read(cx).snapshot();
assert_eq!(
WorkflowStepEdit::resolve_location(&snapshot, "fn foo1(b: usize) {\n42\n}")
.to_point(&snapshot),
Point::new(0, 0)..Point::new(2, 1)
);
}
{
let buffer = cx.new_model(|cx| {
Buffer::local(
concat!(
"fn main() {\n",
" Foo\n",
" .bar()\n",
" .baz()\n",
" .qux()\n",
"}\n",
"\n",
"fn foo2(b: usize) -> usize {\n",
" 42\n",
"}\n",
),
cx,
)
});
let snapshot = buffer.read(cx).snapshot();
assert_eq!(
WorkflowStepEdit::resolve_location(&snapshot, "Foo.bar.baz.qux()")
.to_point(&snapshot),
Point::new(1, 0)..Point::new(4, 14)
);
}
}
}

View File

@@ -15,9 +15,15 @@ path = "src/assistant_slash_command.rs"
anyhow.workspace = true
collections.workspace = true
derive_more.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
parking_lot.workspace = true
serde.workspace = true
serde_json.workspace = true
workspace.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -1,6 +1,8 @@
mod slash_command_registry;
use anyhow::Result;
use futures::stream::{self, BoxStream};
use futures::StreamExt;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
use serde::{Deserialize, Serialize};
@@ -56,6 +58,8 @@ pub struct ArgumentCompletion {
pub replace_previous_arguments: bool,
}
pub type SlashCommandResult = Result<BoxStream<'static, Result<SlashCommandEvent>>>;
pub trait SlashCommand: 'static + Send + Sync {
fn name(&self) -> String;
fn label(&self, _cx: &AppContext) -> CodeLabel {
@@ -87,7 +91,7 @@ pub trait SlashCommand: 'static + Send + Sync {
// perhaps another kind of delegate is needed here.
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>>;
) -> Task<SlashCommandResult>;
}
pub type RenderFoldPlaceholder = Arc<
@@ -96,13 +100,146 @@ pub type RenderFoldPlaceholder = Arc<
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
>;
#[derive(Debug, Default, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
pub enum SlashCommandContent {
Text {
text: String,
run_commands_in_text: bool,
},
}
#[derive(Debug, PartialEq, Eq)]
pub enum SlashCommandEvent {
StartSection {
icon: IconName,
label: SharedString,
metadata: Option<serde_json::Value>,
},
Content(SlashCommandContent),
EndSection {
metadata: Option<serde_json::Value>,
},
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct SlashCommandOutput {
pub text: String,
pub sections: Vec<SlashCommandOutputSection<usize>>,
pub run_commands_in_text: bool,
}
impl SlashCommandOutput {
pub fn ensure_valid_section_ranges(&mut self) {
for section in &mut self.sections {
section.range.start = section.range.start.min(self.text.len());
section.range.end = section.range.end.min(self.text.len());
while !self.text.is_char_boundary(section.range.start) {
section.range.start -= 1;
}
while !self.text.is_char_boundary(section.range.end) {
section.range.end += 1;
}
}
}
/// Returns this [`SlashCommandOutput`] as a stream of [`SlashCommandEvent`]s.
pub fn to_event_stream(mut self) -> BoxStream<'static, Result<SlashCommandEvent>> {
self.ensure_valid_section_ranges();
let mut events = Vec::new();
let mut last_section_end = 0;
for section in self.sections {
if last_section_end < section.range.start {
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: self
.text
.get(last_section_end..section.range.start)
.unwrap_or_default()
.to_string(),
run_commands_in_text: self.run_commands_in_text,
})));
}
events.push(Ok(SlashCommandEvent::StartSection {
icon: section.icon,
label: section.label,
metadata: section.metadata.clone(),
}));
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: self
.text
.get(section.range.start..section.range.end)
.unwrap_or_default()
.to_string(),
run_commands_in_text: self.run_commands_in_text,
})));
events.push(Ok(SlashCommandEvent::EndSection {
metadata: section.metadata,
}));
last_section_end = section.range.end;
}
if last_section_end < self.text.len() {
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: self.text[last_section_end..].to_string(),
run_commands_in_text: self.run_commands_in_text,
})));
}
stream::iter(events).boxed()
}
pub async fn from_event_stream(
mut events: BoxStream<'static, Result<SlashCommandEvent>>,
) -> Result<SlashCommandOutput> {
let mut output = SlashCommandOutput::default();
let mut section_stack = Vec::new();
while let Some(event) = events.next().await {
match event? {
SlashCommandEvent::StartSection {
icon,
label,
metadata,
} => {
let start = output.text.len();
section_stack.push(SlashCommandOutputSection {
range: start..start,
icon,
label,
metadata,
});
}
SlashCommandEvent::Content(SlashCommandContent::Text {
text,
run_commands_in_text,
}) => {
output.text.push_str(&text);
output.run_commands_in_text = run_commands_in_text;
if let Some(section) = section_stack.last_mut() {
section.range.end = output.text.len();
}
}
SlashCommandEvent::EndSection { metadata } => {
if let Some(mut section) = section_stack.pop() {
section.metadata = metadata;
output.sections.push(section);
}
}
}
}
while let Some(section) = section_stack.pop() {
output.sections.push(section);
}
Ok(output)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SlashCommandOutputSection<T> {
pub range: Range<T>,
@@ -116,3 +253,243 @@ impl SlashCommandOutputSection<language::Anchor> {
self.range.start.is_valid(buffer) && !self.range.to_offset(buffer).is_empty()
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use super::*;
#[gpui::test]
async fn test_slash_command_output_to_events_round_trip() {
// Test basic output consisting of a single section.
{
let text = "Hello, world!".to_string();
let range = 0..text.len();
let output = SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::Code,
label: "Section 1".into(),
metadata: None,
}],
run_commands_in_text: false,
};
let events = output.clone().to_event_stream().collect::<Vec<_>>().await;
let events = events
.into_iter()
.filter_map(|event| event.ok())
.collect::<Vec<_>>();
assert_eq!(
events,
vec![
SlashCommandEvent::StartSection {
icon: IconName::Code,
label: "Section 1".into(),
metadata: None
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Hello, world!".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection { metadata: None }
]
);
let new_output =
SlashCommandOutput::from_event_stream(output.clone().to_event_stream())
.await
.unwrap();
assert_eq!(new_output, output);
}
// Test output where the sections do not comprise all of the text.
{
let text = "Apple\nCucumber\nBanana\n".to_string();
let output = SlashCommandOutput {
text,
sections: vec![
SlashCommandOutputSection {
range: 0..6,
icon: IconName::Check,
label: "Fruit".into(),
metadata: None,
},
SlashCommandOutputSection {
range: 15..22,
icon: IconName::Check,
label: "Fruit".into(),
metadata: None,
},
],
run_commands_in_text: false,
};
let events = output.clone().to_event_stream().collect::<Vec<_>>().await;
let events = events
.into_iter()
.filter_map(|event| event.ok())
.collect::<Vec<_>>();
assert_eq!(
events,
vec![
SlashCommandEvent::StartSection {
icon: IconName::Check,
label: "Fruit".into(),
metadata: None
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Apple\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection { metadata: None },
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Cucumber\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::StartSection {
icon: IconName::Check,
label: "Fruit".into(),
metadata: None
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Banana\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection { metadata: None }
]
);
let new_output =
SlashCommandOutput::from_event_stream(output.clone().to_event_stream())
.await
.unwrap();
assert_eq!(new_output, output);
}
// Test output consisting of multiple sections.
{
let text = "Line 1\nLine 2\nLine 3\nLine 4\n".to_string();
let output = SlashCommandOutput {
text,
sections: vec![
SlashCommandOutputSection {
range: 0..6,
icon: IconName::FileCode,
label: "Section 1".into(),
metadata: Some(json!({ "a": true })),
},
SlashCommandOutputSection {
range: 7..13,
icon: IconName::FileDoc,
label: "Section 2".into(),
metadata: Some(json!({ "b": true })),
},
SlashCommandOutputSection {
range: 14..20,
icon: IconName::FileGit,
label: "Section 3".into(),
metadata: Some(json!({ "c": true })),
},
SlashCommandOutputSection {
range: 21..27,
icon: IconName::FileToml,
label: "Section 4".into(),
metadata: Some(json!({ "d": true })),
},
],
run_commands_in_text: false,
};
let events = output.clone().to_event_stream().collect::<Vec<_>>().await;
let events = events
.into_iter()
.filter_map(|event| event.ok())
.collect::<Vec<_>>();
assert_eq!(
events,
vec![
SlashCommandEvent::StartSection {
icon: IconName::FileCode,
label: "Section 1".into(),
metadata: Some(json!({ "a": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Line 1".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "a": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::StartSection {
icon: IconName::FileDoc,
label: "Section 2".into(),
metadata: Some(json!({ "b": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Line 2".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "b": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::StartSection {
icon: IconName::FileGit,
label: "Section 3".into(),
metadata: Some(json!({ "c": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Line 3".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "c": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::StartSection {
icon: IconName::FileToml,
label: "Section 4".into(),
metadata: Some(json!({ "d": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Line 4".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "d": true }))
},
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
}),
]
);
let new_output =
SlashCommandOutput::from_event_stream(output.clone().to_event_stream())
.await
.unwrap();
assert_eq!(new_output, output);
}
}
}

View File

@@ -32,4 +32,5 @@ settings.workspace = true
smol.workspace = true
tempfile.workspace = true
util.workspace = true
which.workspace = true
workspace.workspace = true

View File

@@ -11,6 +11,7 @@ use gpui::{
};
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use paths::remote_servers_dir;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
@@ -33,6 +34,7 @@ use std::{
};
use update_notification::UpdateNotification;
use util::ResultExt;
use which::which;
use workspace::notifications::NotificationId;
use workspace::Workspace;
@@ -82,9 +84,9 @@ pub struct AutoUpdater {
}
#[derive(Deserialize)]
struct JsonRelease {
version: String,
url: String,
pub struct JsonRelease {
pub version: String,
pub url: String,
}
struct MacOsUnmounter {
@@ -430,10 +432,11 @@ impl AutoUpdater {
cx.notify();
}
pub async fn get_latest_remote_server_release(
pub async fn download_remote_server_release(
os: &str,
arch: &str,
mut release_channel: ReleaseChannel,
release_channel: ReleaseChannel,
version: Option<SemanticVersion>,
cx: &mut AsyncAppContext,
) -> Result<PathBuf> {
let this = cx.update(|cx| {
@@ -443,15 +446,12 @@ impl AutoUpdater {
.ok_or_else(|| anyhow!("auto-update not initialized"))
})??;
if release_channel == ReleaseChannel::Dev {
release_channel = ReleaseChannel::Nightly;
}
let release = Self::get_latest_release(
let release = Self::get_release(
&this,
"zed-remote-server",
os,
arch,
version,
Some(release_channel),
cx,
)
@@ -466,13 +466,97 @@ impl AutoUpdater {
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
if smol::fs::metadata(&version_path).await.is_err() {
log::info!("downloading zed-remote-server {os} {arch}");
log::info!(
"downloading zed-remote-server {os} {arch} version {}",
release.version
);
download_remote_server_binary(&version_path, release, client, cx).await?;
}
Ok(version_path)
}
pub async fn get_remote_server_release_url(
os: &str,
arch: &str,
release_channel: ReleaseChannel,
version: Option<SemanticVersion>,
cx: &mut AsyncAppContext,
) -> Result<(JsonRelease, String)> {
let this = cx.update(|cx| {
cx.default_global::<GlobalAutoUpdate>()
.0
.clone()
.ok_or_else(|| anyhow!("auto-update not initialized"))
})??;
let release = Self::get_release(
&this,
"zed-remote-server",
os,
arch,
version,
Some(release_channel),
cx,
)
.await?;
let update_request_body = build_remote_server_update_request_body(cx)?;
let body = serde_json::to_string(&update_request_body)?;
Ok((release, body))
}
async fn get_release(
this: &Model<Self>,
asset: &str,
os: &str,
arch: &str,
version: Option<SemanticVersion>,
release_channel: Option<ReleaseChannel>,
cx: &mut AsyncAppContext,
) -> Result<JsonRelease> {
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
if let Some(version) = version {
let channel = release_channel.map(|c| c.dev_name()).unwrap_or("stable");
let url = format!("/api/releases/{channel}/{version}/{asset}-{os}-{arch}.gz?update=1",);
Ok(JsonRelease {
version: version.to_string(),
url: client.build_url(&url),
})
} else {
let mut url_string = client.build_url(&format!(
"/api/releases/latest?asset={}&os={}&arch={}",
asset, os, arch
));
if let Some(param) = release_channel.and_then(|c| c.release_query_param()) {
url_string += "&";
url_string += param;
}
let mut response = client.get(&url_string, Default::default(), true).await?;
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
if !response.status().is_success() {
return Err(anyhow!(
"failed to fetch release: {:?}",
String::from_utf8_lossy(&body),
));
}
serde_json::from_slice(body.as_slice()).with_context(|| {
format!(
"error deserializing release {:?}",
String::from_utf8_lossy(&body),
)
})
}
}
async fn get_latest_release(
this: &Model<Self>,
asset: &str,
@@ -481,38 +565,7 @@ impl AutoUpdater {
release_channel: Option<ReleaseChannel>,
cx: &mut AsyncAppContext,
) -> Result<JsonRelease> {
let client = this.read_with(cx, |this, _| this.http_client.clone())?;
let mut url_string = client.build_url(&format!(
"/api/releases/latest?asset={}&os={}&arch={}",
asset, os, arch
));
if let Some(param) = release_channel.and_then(|c| c.release_query_param()) {
url_string += "&";
url_string += param;
}
let mut response = client.get(&url_string, Default::default(), true).await?;
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading release")?;
if !response.status().is_success() {
Err(anyhow!(
"failed to fetch release: {:?}",
String::from_utf8_lossy(&body),
))?;
}
serde_json::from_slice(body.as_slice()).with_context(|| {
format!(
"error deserializing release {:?}",
String::from_utf8_lossy(&body),
)
})
Self::get_release(this, asset, os, arch, None, release_channel, cx).await
}
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
@@ -560,6 +613,12 @@ impl AutoUpdater {
"linux" => Ok("zed.tar.gz"),
_ => Err(anyhow!("not supported: {:?}", OS)),
}?;
anyhow::ensure!(
which("rsync").is_ok(),
"Aborting. Could not find rsync which is required for auto-updates."
);
let downloaded_asset = temp_dir.path().join(filename);
download_release(&downloaded_asset, release, client, &cx).await?;
@@ -621,7 +680,19 @@ async fn download_remote_server_binary(
client: Arc<HttpClientWithUrl>,
cx: &AsyncAppContext,
) -> Result<()> {
let mut target_file = File::create(&target_path).await?;
let temp = tempfile::Builder::new().tempfile_in(remote_servers_dir())?;
let mut temp_file = File::create(&temp).await?;
let update_request_body = build_remote_server_update_request_body(cx)?;
let request_body = AsyncBody::from(serde_json::to_string(&update_request_body)?);
let mut response = client.get(&release.url, request_body, true).await?;
smol::io::copy(response.body_mut(), &mut temp_file).await?;
smol::fs::rename(&temp, &target_path).await?;
Ok(())
}
fn build_remote_server_update_request_body(cx: &AsyncAppContext) -> Result<UpdateRequestBody> {
let (installation_id, release_channel, telemetry_enabled, is_staff) = cx.update(|cx| {
let telemetry = Client::global(cx).telemetry().clone();
let is_staff = telemetry.is_staff();
@@ -637,17 +708,14 @@ async fn download_remote_server_binary(
is_staff,
)
})?;
let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
Ok(UpdateRequestBody {
installation_id,
release_channel,
telemetry: telemetry_enabled,
is_staff,
destination: "remote",
})?);
let mut response = client.get(&release.url, request_body, true).await?;
smol::io::copy(response.body_mut(), &mut target_file).await?;
Ok(())
})
}
async fn download_release(

View File

@@ -1194,26 +1194,15 @@ impl Room {
project: Model<Project>,
cx: &mut ModelContext<Self>,
) -> Task<Result<u64>> {
let request = if let Some(dev_server_project_id) = project.read(cx).dev_server_project_id()
{
self.client.request(proto::ShareProject {
room_id: self.id(),
worktrees: vec![],
dev_server_project_id: Some(dev_server_project_id.0),
is_ssh_project: false,
})
} else {
if let Some(project_id) = project.read(cx).remote_id() {
return Task::ready(Ok(project_id));
}
if let Some(project_id) = project.read(cx).remote_id() {
return Task::ready(Ok(project_id));
}
self.client.request(proto::ShareProject {
room_id: self.id(),
worktrees: project.read(cx).worktree_metadata_protos(cx),
dev_server_project_id: None,
is_ssh_project: project.read(cx).is_via_ssh(),
})
};
let request = self.client.request(proto::ShareProject {
room_id: self.id(),
worktrees: project.read(cx).worktree_metadata_protos(cx),
is_ssh_project: project.read(cx).is_via_ssh(),
});
cx.spawn(|this, mut cx| async move {
let response = request.await?;

View File

@@ -3,7 +3,7 @@ mod channel_index;
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
use anyhow::{anyhow, Result};
use channel_index::ChannelIndex;
use client::{ChannelId, Client, ClientSettings, ProjectId, Subscription, User, UserId, UserStore};
use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore};
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
@@ -33,30 +33,11 @@ struct NotesVersion {
version: clock::Global,
}
#[derive(Debug, Clone)]
pub struct HostedProject {
project_id: ProjectId,
channel_id: ChannelId,
name: SharedString,
_visibility: proto::ChannelVisibility,
}
impl From<proto::HostedProject> for HostedProject {
fn from(project: proto::HostedProject) -> Self {
Self {
project_id: ProjectId(project.project_id),
channel_id: ChannelId(project.channel_id),
_visibility: project.visibility(),
name: project.name.into(),
}
}
}
pub struct ChannelStore {
pub channel_index: ChannelIndex,
channel_invitations: Vec<Arc<Channel>>,
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
channel_states: HashMap<ChannelId, ChannelState>,
hosted_projects: HashMap<ProjectId, HostedProject>,
outgoing_invites: HashSet<(ChannelId, UserId)>,
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
@@ -85,7 +66,6 @@ pub struct ChannelState {
observed_notes_version: NotesVersion,
observed_chat_message: Option<u64>,
role: Option<ChannelRole>,
projects: HashSet<ProjectId>,
}
impl Channel {
@@ -216,7 +196,6 @@ impl ChannelStore {
channel_invitations: Vec::default(),
channel_index: ChannelIndex::default(),
channel_participants: Default::default(),
hosted_projects: Default::default(),
outgoing_invites: Default::default(),
opened_buffers: Default::default(),
opened_chats: Default::default(),
@@ -316,19 +295,6 @@ impl ChannelStore {
self.channel_index.by_id().get(&channel_id)
}
pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, ProjectId)> {
let mut projects: Vec<(SharedString, ProjectId)> = self
.channel_states
.get(&channel_id)
.map(|state| state.projects.clone())
.unwrap_or_default()
.into_iter()
.flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
.collect();
projects.sort();
projects
}
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
if let OpenedModelHandle::Open(buffer) = buffer {
@@ -1102,9 +1068,7 @@ impl ChannelStore {
let channels_changed = !payload.channels.is_empty()
|| !payload.delete_channels.is_empty()
|| !payload.latest_channel_message_ids.is_empty()
|| !payload.latest_channel_buffer_versions.is_empty()
|| !payload.hosted_projects.is_empty()
|| !payload.deleted_hosted_projects.is_empty();
|| !payload.latest_channel_buffer_versions.is_empty();
if channels_changed {
if !payload.delete_channels.is_empty() {
@@ -1161,34 +1125,6 @@ impl ChannelStore {
.or_default()
.update_latest_message_id(latest_channel_message.message_id);
}
for hosted_project in payload.hosted_projects {
let hosted_project: HostedProject = hosted_project.into();
if let Some(old_project) = self
.hosted_projects
.insert(hosted_project.project_id, hosted_project.clone())
{
self.channel_states
.entry(old_project.channel_id)
.or_default()
.remove_hosted_project(old_project.project_id);
}
self.channel_states
.entry(hosted_project.channel_id)
.or_default()
.add_hosted_project(hosted_project.project_id);
}
for hosted_project_id in payload.deleted_hosted_projects {
let hosted_project_id = ProjectId(hosted_project_id);
if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
self.channel_states
.entry(old_project.channel_id)
.or_default()
.remove_hosted_project(old_project.project_id);
}
}
}
cx.notify();
@@ -1295,12 +1231,4 @@ impl ChannelState {
};
}
}
fn add_hosted_project(&mut self, project_id: ProjectId) {
self.projects.insert(project_id);
}
fn remove_hosted_project(&mut self, project_id: ProjectId) {
self.projects.remove(&project_id);
}
}

View File

@@ -15,7 +15,6 @@ pub enum CliRequest {
urls: Vec<String>,
wait: bool,
open_new_workspace: Option<bool>,
dev_server_token: Option<String>,
env: Option<HashMap<String, String>>,
},
}

View File

@@ -151,6 +151,12 @@ fn main() -> Result<()> {
}
}
if let Some(_) = args.dev_server_token {
return Err(anyhow::anyhow!(
"Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development"
))?;
}
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
let exit_status = exit_status.clone();
move || {
@@ -162,7 +168,6 @@ fn main() -> Result<()> {
urls,
wait: args.wait,
open_new_workspace,
dev_server_token: args.dev_server_token,
env,
})?;

View File

@@ -4,6 +4,7 @@ pub mod test;
mod socks;
pub mod telemetry;
pub mod user;
pub mod zed_urls;
use anyhow::{anyhow, bail, Context as _, Result};
use async_recursion::async_recursion;
@@ -29,7 +30,6 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use socks::connect_socks_proxy_stream;
use std::fmt;
use std::pin::Pin;
use std::{
any::TypeId,
@@ -53,15 +53,6 @@ pub use rpc::*;
pub use telemetry_events::Event;
pub use user::*;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DevServerToken(pub String);
impl fmt::Display for DevServerToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
static ZED_SERVER_URL: LazyLock<Option<String>> =
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
@@ -303,20 +294,14 @@ struct ClientState {
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Credentials {
DevServer { token: DevServerToken },
User { user_id: u64, access_token: String },
pub struct Credentials {
pub user_id: u64,
pub access_token: String,
}
impl Credentials {
pub fn authorization_header(&self) -> String {
match self {
Credentials::DevServer { token } => format!("dev-server-token {}", token),
Credentials::User {
user_id,
access_token,
} => format!("{} {}", user_id, access_token),
}
format!("{} {}", self.user_id, self.access_token)
}
}
@@ -599,11 +584,11 @@ impl Client {
}
pub fn user_id(&self) -> Option<u64> {
if let Some(Credentials::User { user_id, .. }) = self.state.read().credentials.as_ref() {
Some(*user_id)
} else {
None
}
self.state
.read()
.credentials
.as_ref()
.map(|credentials| credentials.user_id)
}
pub fn peer_id(&self) -> Option<PeerId> {
@@ -792,11 +777,6 @@ impl Client {
.is_some()
}
pub fn set_dev_server_token(&self, token: DevServerToken) -> &Self {
self.state.write().credentials = Some(Credentials::DevServer { token });
self
}
#[async_recursion(?Send)]
pub async fn authenticate_and_connect(
self: &Arc<Self>,
@@ -847,9 +827,7 @@ impl Client {
}
}
let credentials = credentials.unwrap();
if let Credentials::User { user_id, .. } = &credentials {
self.set_id(*user_id);
}
self.set_id(credentials.user_id);
if was_disconnected {
self.set_status(Status::Connecting, cx);
@@ -865,9 +843,8 @@ impl Client {
Ok(conn) => {
self.state.write().credentials = Some(credentials.clone());
if !read_from_provider && IMPERSONATE_LOGIN.is_none() {
if let Credentials::User{user_id, access_token} = credentials {
self.credentials_provider.write_credentials(user_id, access_token, cx).await.log_err();
}
self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err();
}
futures::select_biased! {
@@ -1300,7 +1277,7 @@ impl Client {
.decrypt_string(&access_token)
.context("failed to decrypt access token")?;
Ok(Credentials::User {
Ok(Credentials {
user_id: user_id.parse()?,
access_token,
})
@@ -1421,7 +1398,7 @@ impl Client {
// Use the admin API token to authenticate as the impersonated user.
api_token.insert_str(0, "ADMIN_TOKEN:");
Ok(Credentials::User {
Ok(Credentials {
user_id: response.user.id,
access_token: api_token,
})
@@ -1666,7 +1643,7 @@ impl CredentialsProvider for DevelopmentCredentialsProvider {
let credentials: DevelopmentCredentials = serde_json::from_slice(&json).log_err()?;
Some(Credentials::User {
Some(Credentials {
user_id: credentials.user_id,
access_token: credentials.access_token,
})
@@ -1720,7 +1697,7 @@ impl CredentialsProvider for KeychainCredentialsProvider {
.await
.log_err()??;
Some(Credentials::User {
Some(Credentials {
user_id: user_id.parse().ok()?,
access_token: String::from_utf8(access_token).ok()?,
})
@@ -1854,7 +1831,7 @@ mod tests {
// Time out when client tries to connect.
client.override_authenticate(move |cx| {
cx.background_executor().spawn(async move {
Ok(Credentials::User {
Ok(Credentials {
user_id,
access_token: "token".into(),
})

View File

@@ -49,7 +49,7 @@ impl FakeServer {
let mut state = state.lock();
state.auth_count += 1;
let access_token = state.access_token.to_string();
Ok(Credentials::User {
Ok(Credentials {
user_id: client_user_id,
access_token,
})
@@ -73,7 +73,7 @@ impl FakeServer {
}
if credentials
!= (Credentials::User {
!= (Credentials {
user_id: client_user_id,
access_token: state.lock().access_token.to_string(),
})

View File

@@ -28,9 +28,6 @@ impl std::fmt::Display for ChannelId {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ProjectId(pub u64);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct DevServerId(pub u64);
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
)]
@@ -51,6 +48,7 @@ pub struct Collaborator {
pub peer_id: proto::PeerId,
pub replica_id: ReplicaId,
pub user_id: UserId,
pub is_host: bool,
}
impl PartialOrd for User {
@@ -827,6 +825,7 @@ impl Collaborator {
peer_id: message.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?,
replica_id: message.replica_id as ReplicaId,
user_id: message.user_id as UserId,
is_host: message.is_host,
})
}
}

View File

@@ -0,0 +1,19 @@
//! Contains helper functions for constructing URLs to various Zed-related pages.
//!
//! These URLs will adapt to the configured server URL in order to construct
//! links appropriate for the environment (e.g., by linking to a local copy of
//! zed.dev in development).
use gpui::AppContext;
use settings::Settings;
use crate::ClientSettings;
fn server_url(cx: &AppContext) -> &str {
&ClientSettings::get_global(cx).server_url
}
/// Returns the URL to the account page on zed.dev.
pub fn account_url(cx: &AppContext) -> String {
format!("{server_url}/account", server_url = server_url(cx))
}

View File

@@ -86,7 +86,6 @@ client = { workspace = true, features = ["test-support"] }
collab_ui = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true
dev_server_projects.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
file_finder.workspace = true
@@ -94,7 +93,6 @@ fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] }
git_hosting_providers.workspace = true
gpui = { workspace = true, features = ["test-support"] }
headless.workspace = true
hyper.workspace = true
indoc.workspace = true
language = { workspace = true, features = ["test-support"] }

View File

@@ -199,6 +199,12 @@ spec:
secretKeyRef:
name: slack
key: panics_webhook
- name: STRIPE_API_KEY
valueFrom:
secretKeyRef:
name: stripe
key: api_key
optional: true
- name: COMPLETE_WITH_LANGUAGE_MODEL_RATE_LIMIT_PER_HOUR
value: "1000"
- name: SUPERMAVEN_ADMIN_API_KEY

View File

@@ -11,7 +11,8 @@ CREATE TABLE "users" (
"metrics_id" TEXT,
"github_user_id" INTEGER NOT NULL,
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE,
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE,
"custom_llm_monthly_allowance_in_cents" INTEGER
);
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
@@ -51,9 +52,7 @@ CREATE TABLE "projects" (
"host_user_id" INTEGER REFERENCES users (id),
"host_connection_id" INTEGER,
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
"hosted_project_id" INTEGER REFERENCES hosted_projects (id),
"dev_server_project_id" INTEGER REFERENCES dev_server_projects(id)
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
@@ -78,10 +77,10 @@ CREATE TABLE "worktree_entries" (
"id" INTEGER NOT NULL,
"is_dir" BOOL NOT NULL,
"path" VARCHAR NOT NULL,
"canonical_path" TEXT,
"inode" INTEGER NOT NULL,
"mtime_seconds" INTEGER NOT NULL,
"mtime_nanos" INTEGER NOT NULL,
"is_symlink" BOOL NOT NULL,
"is_external" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL,
@@ -398,30 +397,6 @@ CREATE TABLE rate_buckets (
);
CREATE INDEX idx_user_id_rate_limit ON rate_buckets (user_id, rate_limit_name);
CREATE TABLE hosted_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
visibility TEXT NOT NULL,
deleted_at TIMESTAMP NULL
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
CREATE TABLE dev_servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
ssh_connection_string TEXT,
hashed_token TEXT NOT NULL
);
CREATE TABLE dev_server_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dev_server_id INTEGER NOT NULL REFERENCES dev_servers(id),
paths TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS billing_preferences (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@@ -0,0 +1,2 @@
ALTER TABLE worktree_entries ADD COLUMN canonical_path text;
ALTER TABLE worktree_entries ALTER COLUMN is_symlink SET DEFAULT false;

View File

@@ -0,0 +1 @@
alter table users add column custom_llm_monthly_allowance_in_cents integer;

View File

@@ -0,0 +1,6 @@
ALTER TABLE projects DROP COLUMN dev_server_project_id;
ALTER TABLE projects DROP COLUMN hosted_project_id;
DROP TABLE hosted_projects;
DROP TABLE dev_server_projects;
DROP TABLE dev_servers;

View File

@@ -19,8 +19,8 @@ use stripe::{
};
use util::ResultExt;
use crate::llm::DEFAULT_MAX_MONTHLY_SPEND;
use crate::rpc::ResultExt as _;
use crate::llm::{DEFAULT_MAX_MONTHLY_SPEND, FREE_TIER_MONTHLY_SPENDING_LIMIT};
use crate::rpc::{ResultExt as _, Server};
use crate::{
db::{
billing_customer, BillingSubscriptionId, CreateBillingCustomerParams,
@@ -34,7 +34,7 @@ use crate::{
db::{billing_subscription::StripeSubscriptionStatus, UserId},
llm::db::LlmDatabase,
};
use crate::{AppState, Error, Result};
use crate::{AppState, Cents, Error, Result};
pub fn router() -> Router {
Router::new()
@@ -50,6 +50,7 @@ pub fn router() -> Router {
"/billing/subscriptions/manage",
post(manage_billing_subscription),
)
.route("/billing/monthly_spend", get(get_monthly_spend))
}
#[derive(Debug, Deserialize)]
@@ -225,6 +226,13 @@ async fn create_billing_subscription(
))?
};
if app.db.has_active_billing_subscription(user.id).await? {
return Err(Error::http(
StatusCode::CONFLICT,
"user already has an active subscription".into(),
));
}
let customer_id =
if let Some(existing_customer) = app.db.get_billing_customer_by_user_id(user.id).await? {
CustomerId::from_str(&existing_customer.stripe_customer_id)
@@ -244,7 +252,10 @@ async fn create_billing_subscription(
let default_model = llm_db.model(rpc::LanguageModelProvider::Anthropic, "claude-3-5-sonnet")?;
let stripe_model = stripe_billing.register_model(default_model).await?;
let success_url = format!("{}/account", app.config.zed_dot_dev_url());
let success_url = format!(
"{}/account?checkout_complete=1",
app.config.zed_dot_dev_url()
);
let checkout_session_url = stripe_billing
.checkout(customer_id, &user.github_login, &stripe_model, &success_url)
.await?;
@@ -404,7 +415,7 @@ const NUMBER_OF_ALREADY_PROCESSED_PAGES_BEFORE_WE_STOP: usize = 4;
/// Polls the Stripe events API periodically to reconcile the records in our
/// database with the data in Stripe.
pub fn poll_stripe_events_periodically(app: Arc<AppState>) {
pub fn poll_stripe_events_periodically(app: Arc<AppState>, rpc_server: Arc<Server>) {
let Some(stripe_client) = app.stripe_client.clone() else {
log::warn!("failed to retrieve Stripe client");
return;
@@ -415,7 +426,9 @@ pub fn poll_stripe_events_periodically(app: Arc<AppState>) {
let executor = executor.clone();
async move {
loop {
poll_stripe_events(&app, &stripe_client).await.log_err();
poll_stripe_events(&app, &rpc_server, &stripe_client)
.await
.log_err();
executor.sleep(POLL_EVENTS_INTERVAL).await;
}
@@ -425,6 +438,7 @@ pub fn poll_stripe_events_periodically(app: Arc<AppState>) {
async fn poll_stripe_events(
app: &Arc<AppState>,
rpc_server: &Arc<Server>,
stripe_client: &stripe::Client,
) -> anyhow::Result<()> {
fn event_type_to_string(event_type: EventType) -> String {
@@ -449,29 +463,28 @@ async fn poll_stripe_events(
let mut pages_of_already_processed_events = 0;
let mut unprocessed_events = Vec::new();
log::info!(
"Stripe events: starting retrieval for {}",
event_types.join(", ")
);
let mut params = ListEvents::new();
params.types = Some(event_types.clone());
params.limit = Some(EVENTS_LIMIT_PER_PAGE);
let mut event_pages = stripe::Event::list(&stripe_client, &params)
.await?
.paginate(params);
loop {
if pages_of_already_processed_events >= NUMBER_OF_ALREADY_PROCESSED_PAGES_BEFORE_WE_STOP {
log::info!("saw {pages_of_already_processed_events} pages of already-processed events: stopping event retrieval");
break;
}
log::info!("retrieving events from Stripe: {}", event_types.join(", "));
let mut params = ListEvents::new();
params.types = Some(event_types.clone());
params.limit = Some(EVENTS_LIMIT_PER_PAGE);
let events = stripe::Event::list(stripe_client, &params).await?;
let processed_event_ids = {
let event_ids = &events
let event_ids = event_pages
.page
.data
.iter()
.map(|event| event.id.as_str())
.collect::<Vec<_>>();
app.db
.get_processed_stripe_events_by_event_ids(event_ids)
.get_processed_stripe_events_by_event_ids(&event_ids)
.await?
.into_iter()
.map(|event| event.stripe_event_id)
@@ -479,13 +492,13 @@ async fn poll_stripe_events(
};
let mut processed_events_in_page = 0;
let events_in_page = events.data.len();
for event in events.data {
let events_in_page = event_pages.page.data.len();
for event in &event_pages.page.data {
if processed_event_ids.contains(&event.id.to_string()) {
processed_events_in_page += 1;
log::debug!("Stripe event {} already processed: skipping", event.id);
log::debug!("Stripe events: already processed '{}', skipping", event.id);
} else {
unprocessed_events.push(event);
unprocessed_events.push(event.clone());
}
}
@@ -493,15 +506,21 @@ async fn poll_stripe_events(
pages_of_already_processed_events += 1;
}
if !events.has_more {
if event_pages.page.has_more {
if pages_of_already_processed_events >= NUMBER_OF_ALREADY_PROCESSED_PAGES_BEFORE_WE_STOP
{
log::info!("Stripe events: stopping, saw {pages_of_already_processed_events} pages of already-processed events");
break;
} else {
log::info!("Stripe events: retrieving next page");
event_pages = event_pages.next(&stripe_client).await?;
}
} else {
break;
}
}
log::info!(
"unprocessed events from Stripe: {}",
unprocessed_events.len()
);
log::info!("Stripe events: unprocessed {}", unprocessed_events.len());
// Sort all of the unprocessed events in ascending order, so we can handle them in the order they occurred.
unprocessed_events.sort_by(|a, b| a.created.cmp(&b.created).then_with(|| a.id.cmp(&b.id)));
@@ -517,12 +536,12 @@ async fn poll_stripe_events(
// If the event has happened too far in the past, we don't want to
// process it and risk overwriting other more-recent updates.
//
// 1 hour was chosen arbitrarily. This could be made longer or shorter.
let one_hour = Duration::from_secs(60 * 60);
let an_hour_ago = Utc::now() - one_hour;
if an_hour_ago.timestamp() > event.created {
// 1 day was chosen arbitrarily. This could be made longer or shorter.
let one_day = Duration::from_secs(24 * 60 * 60);
let a_day_ago = Utc::now() - one_day;
if a_day_ago.timestamp() > event.created {
log::info!(
"Stripe event {} is more than {one_hour:?} old, marking as processed",
"Stripe events: event '{}' is more than {one_day:?} old, marking as processed",
event_id
);
app.db
@@ -541,7 +560,7 @@ async fn poll_stripe_events(
| EventType::CustomerSubscriptionPaused
| EventType::CustomerSubscriptionResumed
| EventType::CustomerSubscriptionDeleted => {
handle_customer_subscription_event(app, stripe_client, event).await
handle_customer_subscription_event(app, rpc_server, stripe_client, event).await
}
_ => Ok(()),
};
@@ -609,6 +628,7 @@ async fn handle_customer_event(
async fn handle_customer_subscription_event(
app: &Arc<AppState>,
rpc_server: &Arc<Server>,
stripe_client: &stripe::Client,
event: stripe::Event,
) -> anyhow::Result<()> {
@@ -645,6 +665,33 @@ async fn handle_customer_subscription_event(
)
.await?;
} else {
// If the user already has an active billing subscription, ignore the
// event and return an `Ok` to signal that it was processed
// successfully.
//
// There is the possibility that this could cause us to not create a
// subscription in the following scenario:
//
// 1. User has an active subscription A
// 2. User cancels subscription A
// 3. User creates a new subscription B
// 4. We process the new subscription B before the cancellation of subscription A
// 5. User ends up with no subscriptions
//
// In theory this situation shouldn't arise as we try to process the events in the order they occur.
if app
.db
.has_active_billing_subscription(billing_customer.user_id)
.await?
{
log::info!(
"user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}",
user_id = billing_customer.user_id,
subscription_id = subscription.id
);
return Ok(());
}
app.db
.create_billing_subscription(&CreateBillingSubscriptionParams {
billing_customer_id: billing_customer.id,
@@ -654,9 +701,63 @@ async fn handle_customer_subscription_event(
.await?;
}
// When the user's subscription changes, we want to refresh their LLM tokens
// to either grant/revoke access.
rpc_server
.refresh_llm_tokens_for_user(billing_customer.user_id)
.await;
Ok(())
}
#[derive(Debug, Deserialize)]
struct GetMonthlySpendParams {
github_user_id: i32,
}
#[derive(Debug, Serialize)]
struct GetMonthlySpendResponse {
monthly_free_tier_spend_in_cents: u32,
monthly_free_tier_allowance_in_cents: u32,
monthly_spend_in_cents: u32,
}
async fn get_monthly_spend(
Extension(app): Extension<Arc<AppState>>,
Query(params): Query<GetMonthlySpendParams>,
) -> Result<Json<GetMonthlySpendResponse>> {
let user = app
.db
.get_user_by_github_user_id(params.github_user_id)
.await?
.ok_or_else(|| anyhow!("user not found"))?;
let Some(llm_db) = app.llm_db.clone() else {
return Err(Error::http(
StatusCode::NOT_IMPLEMENTED,
"LLM database not available".into(),
));
};
let free_tier = user
.custom_llm_monthly_allowance_in_cents
.map(|allowance| Cents(allowance as u32))
.unwrap_or(FREE_TIER_MONTHLY_SPENDING_LIMIT);
let spending_for_month = llm_db
.get_user_spending_for_month(user.id, Utc::now())
.await?;
let free_tier_spend = Cents::min(spending_for_month, free_tier);
let monthly_spend = spending_for_month.saturating_sub(free_tier);
Ok(Json(GetMonthlySpendResponse {
monthly_free_tier_spend_in_cents: free_tier_spend.0,
monthly_free_tier_allowance_in_cents: free_tier.0,
monthly_spend_in_cents: monthly_spend.0,
}))
}
impl From<SubscriptionStatus> for StripeSubscriptionStatus {
fn from(value: SubscriptionStatus) -> Self {
match value {
@@ -738,6 +839,7 @@ pub fn sync_llm_usage_with_stripe_periodically(app: Arc<AppState>) {
loop {
sync_with_stripe(&app, &llm_db, &stripe_billing)
.await
.context("failed to sync LLM usage to Stripe")
.trace_err();
executor.sleep(SYNC_LLM_USAGE_WITH_STRIPE_INTERVAL).await;
}

View File

@@ -1,5 +1,5 @@
use crate::{
db::{self, dev_server, AccessTokenId, Database, DevServerId, UserId},
db::{self, AccessTokenId, Database, UserId},
rpc::Principal,
AppState, Error, Result,
};
@@ -44,19 +44,10 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
let first = auth_header.next().unwrap_or("");
if first == "dev-server-token" {
let dev_server_token = auth_header.next().ok_or_else(|| {
Error::http(
StatusCode::BAD_REQUEST,
"missing dev-server-token token in authorization header".to_string(),
)
})?;
let dev_server = verify_dev_server_token(dev_server_token, &state.db)
.await
.map_err(|e| Error::http(StatusCode::UNAUTHORIZED, format!("{}", e)))?;
req.extensions_mut()
.insert(Principal::DevServer(dev_server));
return Ok::<_, Error>(next.run(req).await);
Err(Error::http(
StatusCode::UNAUTHORIZED,
"Dev servers were removed in Zed 0.157 please upgrade to SSH remoting".to_string(),
))?;
}
let user_id = UserId(first.parse().map_err(|_| {
@@ -240,41 +231,6 @@ pub async fn verify_access_token(
})
}
pub fn generate_dev_server_token(id: usize, access_token: String) -> String {
format!("{}.{}", id, access_token)
}
pub async fn verify_dev_server_token(
dev_server_token: &str,
db: &Arc<Database>,
) -> anyhow::Result<dev_server::Model> {
let (id, token) = split_dev_server_token(dev_server_token)?;
let token_hash = hash_access_token(token);
let server = db.get_dev_server(id).await?;
if server
.hashed_token
.as_bytes()
.ct_eq(token_hash.as_ref())
.into()
{
Ok(server)
} else {
Err(anyhow!("wrong token for dev server"))
}
}
// a dev_server_token has the format <id>.<base64>. This is to make them
// relatively easy to copy/paste around.
pub fn split_dev_server_token(dev_server_token: &str) -> anyhow::Result<(DevServerId, &str)> {
let mut parts = dev_server_token.splitn(2, '.');
let id = DevServerId(parts.next().unwrap_or_default().parse()?);
let token = parts
.next()
.ok_or_else(|| anyhow!("invalid dev server token format"))?;
Ok((id, token))
}
#[cfg(test)]
mod test {
use rand::thread_rng;

View File

@@ -10,6 +10,8 @@
Copy,
derive_more::Add,
derive_more::AddAssign,
derive_more::Sub,
derive_more::SubAssign,
)]
pub struct Cents(pub u32);

View File

@@ -617,7 +617,6 @@ pub struct ChannelsForUser {
pub channels: Vec<Channel>,
pub channel_memberships: Vec<channel_member::Model>,
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
pub hosted_projects: Vec<proto::HostedProject>,
pub invited_channels: Vec<Channel>,
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
@@ -726,7 +725,6 @@ pub struct Project {
pub collaborators: Vec<ProjectCollaborator>,
pub worktrees: BTreeMap<u64, Worktree>,
pub language_servers: Vec<proto::LanguageServer>,
pub dev_server_project_id: Option<DevServerProjectId>,
}
pub struct ProjectCollaborator {
@@ -742,6 +740,7 @@ impl ProjectCollaborator {
peer_id: Some(self.connection_id.into()),
replica_id: self.replica_id.0 as u32,
user_id: self.user_id.to_proto(),
is_host: self.is_host,
}
}
}

View File

@@ -79,7 +79,6 @@ id_type!(ChannelChatParticipantId);
id_type!(ChannelId);
id_type!(ChannelMemberId);
id_type!(ContactId);
id_type!(DevServerId);
id_type!(ExtensionId);
id_type!(FlagId);
id_type!(FollowerId);
@@ -89,7 +88,6 @@ id_type!(NotificationId);
id_type!(NotificationKindId);
id_type!(ProjectCollaboratorId);
id_type!(ProjectId);
id_type!(DevServerProjectId);
id_type!(ReplicaId);
id_type!(RoomId);
id_type!(RoomParticipantId);
@@ -277,12 +275,6 @@ impl From<ChannelVisibility> for i32 {
}
}
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
pub enum PrincipalId {
UserId(UserId),
DevServerId(DevServerId),
}
/// Indicate whether a [Buffer] has permissions to edit.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Capability {

View File

@@ -8,11 +8,8 @@ pub mod buffers;
pub mod channels;
pub mod contacts;
pub mod contributors;
pub mod dev_server_projects;
pub mod dev_servers;
pub mod embeddings;
pub mod extensions;
pub mod hosted_projects;
pub mod messages;
pub mod notifications;
pub mod processed_stripe_events;

View File

@@ -116,6 +116,7 @@ impl Database {
peer_id: Some(collaborator.connection().into()),
user_id: collaborator.user_id.to_proto(),
replica_id: collaborator.replica_id.0 as u32,
is_host: false,
})
.collect(),
})
@@ -222,6 +223,7 @@ impl Database {
peer_id: Some(collaborator.connection().into()),
user_id: collaborator.user_id.to_proto(),
replica_id: collaborator.replica_id.0 as u32,
is_host: false,
})
.collect(),
},
@@ -257,6 +259,7 @@ impl Database {
peer_id: Some(db_collaborator.connection().into()),
replica_id: db_collaborator.replica_id.0 as u32,
user_id: db_collaborator.user_id.to_proto(),
is_host: false,
})
} else {
collaborator_ids_to_remove.push(db_collaborator.id);
@@ -385,6 +388,7 @@ impl Database {
peer_id: Some(connection.into()),
replica_id: row.replica_id.0 as u32,
user_id: row.user_id.to_proto(),
is_host: false,
});
}

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