Compare commits

..

160 Commits

Author SHA1 Message Date
Richard Feldman
a6d45ab41d Update docs to say "edit predictions" 2025-01-31 10:40:05 -05:00
Marshall Bowers
af6548c745 docs: Remove lingering docs for default_dock_anchor (#24029)
This PR removes some lingering docs leftover after `default_dock_anchor`
was removed.

These were missed in https://github.com/zed-industries/zed/pull/18210.

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

Release Notes:

- N/A
2025-01-31 14:34:13 +00:00
João Marcos
6f467281e0 Fix data collection permission asked multiple times for same worktree (#24016)
After the user confirmation, only the current instance of the
completions provider had the answer stored.

In this PR, the changes are propagated by having each provider have an
`Entity<choice>`, and having a lookup map with one `Entity<choice>` for
each worktree that `Zeta` has seen.

Release Notes:

- N/A
2025-01-31 10:22:31 +00:00
Piotr Osiewicz
be4c3cfbd2 workspace: Make "New Window" bring app to foreground (#24015)
Closes #ISSUE

Release Notes:

- "New Window" action will now bring App to foreground.
2025-01-31 11:18:56 +01:00
Conrad Irwin
0ad2aeb2e9 Enable word wrap in feedback modal (#23893)
https://zed-industries.slack.com/archives/C04S7CZPF4M/p1738151539115169

Release Notes:

- Enable word wrap in the feedback modal
2025-01-31 00:13:53 -07:00
Conrad Irwin
f2b3f3a9ab Allow buffer search in project search (#23819)
Closes #13437
Closes #19993

Release Notes:

- Allow searching within the results of a project search
- vim: Fix `/`/`?`, `n`/`N`, `gn`/`gN`,`*`/`#` in project search results

---------

Co-authored-by: Nico <nico.lehmann@gmail.com>
2025-01-31 00:13:46 -07:00
Jason Lee
e1af35aa15 gpui: Add closest_index_for_position method (#23668)
Closes #ISSUE

Release Notes:

- N/A

------------

I just make a little change to improve `index_for_position` to support
return closest index for position.

I need this method to measure for position cursor in multi-line mode
TextInput.

https://github.com/longbridge/gpui-component/pull/583


https://github.com/user-attachments/assets/c69d098e-d2cb-4053-b739-6c7dd666e769

Before this change, GPUI have `LineLayout::closest_index_for_x` method
for unwrapped line case.


d1be419fff/crates/gpui/src/text_system/line_layout.rs (L58-L94)

This change is equivalent to making `index_for_position` have a
corresponding method to get the closest index like `index_for_x`.
2025-01-31 00:03:56 -07:00
Conrad Irwin
cb15753694 Fix clipping at end of line in vim mode with inlay hints (#23975)
Closes #23877

Co-Authored-By: Ben <ben@zed.dev>
Co-Authored-By: Michael <michael@zed.dev>

Release Notes:

- vim: Fix navigating to end of line with inlay hints

---------

Co-authored-by: Ben <ben@zed.dev>
Co-authored-by: Michael <michael@zed.dev>
2025-01-31 00:00:47 -07:00
Conrad Irwin
5914ccdc51 Deflake fs::test_event_stream_simple (#24013)
Should reduce test flakiness

Release Notes:

- N/A
2025-01-30 23:53:36 -07:00
Marshall Bowers
8be73bf187 collab: Remove unused POST /predict_edits endpoint from LLM service (#23997)
This PR removes the `POST /predict_edits` endpoint from the LLM service,
as it has been superseded by the corresponding endpoint running in
Cloudflare Workers.

All traffic is already being routed to the Cloudflare Workers via the
Workers route, so nothing is hitting this endpoint running in the LLM
service anymore.

You can see the drop off in requests to this endpoint on this graph when
the Workers route was added:

<img width="472" alt="Screenshot 2025-01-30 at 9 18 04 PM"
src="https://github.com/user-attachments/assets/fa60f7c8-2737-4329-88a3-17093bdb5a29"
/>

We also don't use the `fireworks` crate anymore in this repo, so it has
been removed.

Release Notes:

- N/A
2025-01-31 03:21:40 +00:00
Marshall Bowers
35fbe1ef3d zeta: Send staff edit predictions through llm.zed.dev again (#23996)
This PR changes the edit predictions URL for Zed Staff back to
`llm.zed.dev/predict_edits`.

This endpoint is now being routed to the Cloudflare Workers instead of
the LLM service.

Release Notes:

- N/A
2025-01-30 21:07:38 -05:00
Mikayla Maki
517e519bdc Make the gpui_tokio crate generic over the context it spawns (#23995)
Part of  #21092

Makes `Tokio::spawn` generic over any `AppContext`.

Also removes a stray `model_context` I missed

Release Notes:

- N/A
2025-01-31 02:00:55 +00:00
Cameron Radmore
ff43b6875b Add icon association for ESLint flat config (#23994)
Release Notes:

- Added file type associations for ESLint flat config files.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-01-31 01:53:30 +00:00
Joseph T. Lyons
4c8b5ea4f7 Unify selection directions when performing editor: select all selections (#23993)
Closes https://github.com/zed-industries/zed/issues/19569

Current behavior:


https://github.com/user-attachments/assets/1de764c9-7c62-49ad-b24b-6e85760857db

After PR:


https://github.com/user-attachments/assets/651d8e50-95e2-4513-852b-9557d00d2b62

Release Notes:

- Unified selection directions when performing `editor: select all
selections`.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-01-31 01:48:37 +00:00
Marshall Bowers
f29b33ec85 extensions_ui: Show the filtered icon theme selector when installing an icon theme (#23992)
This PR makes it so when you install an extension with icon themes it
will deploy the icon theme selector filtered down to the newly-installed
icon themes.

This is similar to what we do when installing an extension with themes.

Because we can only have one picker open at a time, when installing an
extension that has _both_ themes and icon themes, the theme selector
will take precedence.

Release Notes:

- N/A
2025-01-31 01:32:13 +00:00
Marshall Bowers
e5bc0486b5 Add schema_generator for generating JSON schemas (#23991)
This PR adds a `schema_generator` crate that can be used to generate our
various JSON schemas for publishing elsewhere.

Currently it does the simplest thing possible and just prints the JSON
schema to stdout. We can make this a but more robust later.

I also removed the schema-printing facilities from the `theme_importer`,
as they don't really make sense there.

Release Notes:

- N/A
2025-01-31 01:22:10 +00:00
Max Brunsfeld
b6e54ae2f1 Fix two bugs in new diff hunk handling (#23990)
Closes https://github.com/zed-industries/zed/issues/23981

Release Notes:

- Fixed a crash that could happen when expanding certain diff hunks
- Fixed a bug where diff hunks were not syntax highlighted when
reopening a project with previously-opened buffers.
2025-01-31 01:03:53 +00:00
Mike Qin
9c3482083b Map window after set_app_id() under X11 (#23046)
GPUI applications can set the window class by the `app_id` window
option. However, GPUI will map the window first and then change the
window class after the window is displayed. This doesn't work on some
X11 window managers. FVWM, for example, does not track window class
after a window is mapped. Because in practice, a window shouldn't change
its application group on the fly.

This PR fixed this by adding a `map_window()` function `PlatformWindow`.
On X11, it will `set_app_id()` first and then map the window.

Release Notes:

- N/A
2025-01-30 16:47:16 -08:00
Shane Friedman
c28a4204ee Use click event to determine modifier keys (#22988)
Previously, editor elements had to listen for mouse_up events to
determine when a click had completed. This meant that they only had
access to modifier keys that were pressed during the mouse_up event.

This led to some incorrect user experiences, such as executing a
ctrl+click if the user pressed ctrl after pressing the mouse button, but
before releasing it.

This change adds a click event handler to EditorElement, and adds a
modifier() method to the ClickEvent, which only includes the modifier
keys that were pressed during both mouse down and mouse up. The code for
handling link clicks has been moved into the click event handler, so
that it's only triggered when the non-multi-cursor modifier was held for
both the mouse down and mouse up events.

Closes #12752, #16074, #17892 (the latter two seem to be duplicates of
the former!)

Release Notes:

- Fixed a bug where pressing ctrl/cmd (or other modifiers) after mouse
down but before mouse up still triggered ctrl/cmd+click behavior (e.g.
"go to definition")
2025-01-30 16:40:20 -08:00
Marshall Bowers
4892286465 theme_importer: Fix theme JSON schema URL (#23988)
This PR fixes the URL for the theme JSON schema, as it had an extra path
segment.

Release Notes:

- N/A
2025-01-31 00:35:20 +00:00
Marshall Bowers
419780d702 Add support for icon themes (#23987)
This PR adds support for icon themes.

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

Here is Zed with Material Icons:

<img width="1136" alt="Screenshot 2025-01-30 at 7 02 06 PM"
src="https://github.com/user-attachments/assets/57d8a0e0-ff38-44d9-8628-af58a60a7c9a"
/>

### Extensions

Extensions can provide icon themes as well as the icons used in those
themes.

Icon themes are defined as JSON files in the `icon_themes` directory,
and icons included in the `icons` directory will be packaged up with the
extension.

All icon paths within an icon theme are interpreted relative to the root
of the extension.

See the [Material Icon
Theme](https://github.com/zed-extensions/material-icon-theme) extension
for an example.

Release Notes:

- Added support for icon themes.
  - Extensions can now provide icon themes.
- Use the `icon theme selector: toggle` action to switch between
installed icon themes.
2025-01-30 19:08:31 -05:00
Marshall Bowers
7bf4fd6c46 gpui: Move generic bounds to a where clause for better readability (#23985)
This PR moves some generic bounds to a `where` clause to improve the
formatting/readability of the associated `impl` block.

Release Notes:

- N/A
2025-01-30 23:10:05 +00:00
Marshall Bowers
2c950cf7f5 theme: Properly resolve directory and chevron icons from icon themes (#23984)
This PR fixes an issue where we weren't properly resolving directory and
chevron icons from icon themes the way we were for file icons.

We need to interpret the icon paths as relative to the extension
directory.

Release Notes:

- N/A
2025-01-30 22:34:29 +00:00
Michael Sloan
87b0f62041 Implement simpler logic for edit predictions prompt byte limits (#23983)
Realized that the logic in #23814 was more than needed, and harder to
maintain. Something like that could make sense if using the tokenizer
and wanting to precisely hit a token limit. However in the case of edit
predictions it's more of a latency+expense vs capability tradeoff, and
so such precision is unnecessary.

Happily this change didn't require much extra work, just copy-modifying
parts of that change was sufficient.

Release Notes:

- N/A
2025-01-30 15:27:42 -07:00
Marshall Bowers
9d6c0e57a0 extension_cli: Add support for packaging icon themes (#23978)
This PR updates the Zed extension CLI with support for packaging
extensions containing icon themes.

The `icons` directory in the extension will be copied into the packaged
extension to facilitate distributing icon files.

Release Notes:

- N/A
2025-01-30 21:45:04 +00:00
Max Brunsfeld
399e2c1ed3 Revert "project: Fine-grained language server management" (#23977)
Reverts zed-industries/zed#23805
2025-01-30 13:42:56 -08:00
Marshall Bowers
7adf9cb1a0 Add icon theme selector (#23976)
This PR adds an icon theme selector for switching between icon themes:


https://github.com/user-attachments/assets/2cdc7ab7-d9f4-4968-a2e9-724e8ad4ef4d

Release Notes:

- N/A
2025-01-30 16:11:42 -05:00
Agus Zubiaga
e23e03592b zeta: Onboarding and title bar banner (#23797)
Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Danilo <danilo@zed.dev>
Co-authored-by: João Marcos <joao@zed.dev>
2025-01-30 16:55:32 -03:00
Marshall Bowers
4ab372d6b5 assistant: Unship tool use (#23969)
This PR unships tool use from Assistant1.

This was only ever partially implemented, and was never released to end
users.

Assistant2 will support tool use.

Release Notes:

- N/A
2025-01-30 19:46:15 +00:00
Szymon Piechaczek
d2828e8722 gpui: Handle Swipe events to support navigation buttons on some mice (#23332)
Closes #14170

To fix this, Zed needs to handle swipe events on its NSView. Logitech
mice don't send the usual Mouse4 and Mouse5 buttons but emulate swipe
gestures according to these websites:
- https://superuser.com/a/1216049
- https://sensible-side-buttons.archagon.net/

Of course, the user can map these buttons to something else in the
device's driver. Most IDEs (VSCode, IntelliJ) handle that correctly by
default so it would be good to follow that pattern.

Since it's my first contribution here, please let me know if I need to
enhance this PR to make it good enough for the main branch.

Release Notes:
 - Fixed mouse navigation buttons on some devices (Logitech, Mac OS)
2025-01-30 11:27:50 -08:00
Marshall Bowers
d1b8fedc9c prisma: Extract to zed-extensions/prisma repository (#23961)
This PR extracts the Prisma extension to the
[zed-extensions/prisma](https://github.com/zed-extensions/prisma)
repository.

Release Notes:

- N/A
2025-01-30 18:39:34 +00:00
Joe Sweeney
429dbf7129 Pass extra CA certs to node process if env var exists (#23662)
Closes #8650

According to this comment:
https://github.com/zed-industries/zed/issues/8650#issuecomment-2125877549
it fixes the issue as described.

Happy to make adjustments!

Release Notes:
- Added passthrough of `NODE_EXTRA_CA_CERTS` if populated to node
commands
2025-01-30 08:56:02 -08:00
Kirill Bulatov
9e4555797d Use more LSP data when falling back to regular completions label (#23909)
Closes https://github.com/zed-industries/zed/issues/23590
Closes https://x.com/steeve/status/1865129235536568555

Before:

<img width="773" alt="before"
src="https://github.com/user-attachments/assets/129a8d12-9298-4bf5-8f2d-b3292c2562bf"
/>


After:

<img width="768" alt="after"
src="https://github.com/user-attachments/assets/e0516fb3-b02a-48be-8923-63bba05fdb69"
/>


The list obviously needs some solution for the cut-off part of the
completion label, but this is the reality for all extensions'
completions too, so one step at a time.


Release Notes:

- Improved default completion label fallback
2025-01-30 15:05:34 +00:00
Bennet Bo Fenner
48dba9a9d9 edit prediction: Do not request a completion if edits can be interpolated (#23908)
This ensures that we do not fetch a new completion when the edits of the
user can be interpolated.
E.g. (suggestions in `[]`):
```rust
s[truct Person {}]
```
Then if i type out `truct` we will not fetch a new completion

Release Notes:

- N/A
2025-01-30 14:03:01 +00:00
peanut996
51f07e3382 docs: Update Java extension config example (#23885) 2025-01-30 13:40:51 +00:00
Bennet Bo Fenner
5e210c083f edit prediction: Fix popover positioning when placed above edit (#23902)
Fixes an off-by-one error when the popover was placed above the edit:
<img width="688" alt="Screenshot 2025-01-30 at 11 59 14"
src="https://github.com/user-attachments/assets/938a6626-3f4d-4566-b68c-89b14d48b68d"
/>


Release Notes:

- N/A
2025-01-30 11:16:56 +00:00
Bennet Bo Fenner
5e449c84fe edit prediction: Add syntax highlighting for diff popover (#23899)
Co-Authored-by: Antonio <antonio@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2025-01-30 11:53:51 +01:00
Kirill Bulatov
41de83fe1f Implement collaborative git manipulations (#23869)
Now commit, stage and unstage can be done both via remote ssh and via
collab (by guests with write access).



https://github.com/user-attachments/assets/a0f5e4e8-01a3-402b-a1f7-f3fc1236cffd


Release Notes:

- N/A
2025-01-30 11:23:38 +02:00
Justin Su
e721dac367 Fix counting in default settings (#23898)
Release Notes:

- N/A
2025-01-30 09:08:05 +00:00
Kirill Bulatov
1bc54c2c20 Disable git panel elements for readonly participants (#23897)
Release Notes:

- N/A
2025-01-30 09:07:11 +00:00
Piotr Osiewicz
e662e819fe project: Fine-grained language server management (#23805)
Closes #ISSUE
https://github.com/zed-industries/zed/pull/23804
Release Notes:

- Improved detection of project roots for use by language servers.
2025-01-30 08:35:36 +00:00
renovate[bot]
b62812c49e Update Rust crate tree-sitter-regex to 0.24 (#23871)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[tree-sitter-regex](https://redirect.github.com/tree-sitter/tree-sitter-regex)
| workspace.dependencies | minor | `0.23` -> `0.24` |

---

### Release Notes

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

###
[`v0.24.3`](https://redirect.github.com/tree-sitter/tree-sitter-regex/releases/tag/v0.24.3)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-regex/compare/v0.24.2...v0.24.3)

**NOTE:** Download `tree-sitter-regex.tar.xz` for the *complete* source
code.

###
[`v0.24.2`](https://redirect.github.com/tree-sitter/tree-sitter-regex/releases/tag/v0.24.2)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-regex/compare/v0.24.1...v0.24.2)

**NOTE:** Download `tree-sitter-regex.tar.xz` for the *complete* source
code.

###
[`v0.24.1`](https://redirect.github.com/tree-sitter/tree-sitter-regex/compare/v0.24.0...v0.24.1)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-regex/compare/v0.24.0...v0.24.1)

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

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

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 09:29:36 +02:00
renovate[bot]
154cffb9d5 Update Rust crate tempfile to v3.16.0 (#23864)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tempfile](https://stebalien.com/projects/tempfile-rs/)
([source](https://redirect.github.com/Stebalien/tempfile)) |
workspace.dependencies | minor | `3.15.0` -> `3.16.0` |

---

### Release Notes

<details>
<summary>Stebalien/tempfile (tempfile)</summary>

###
[`v3.16.0`](https://redirect.github.com/Stebalien/tempfile/blob/HEAD/CHANGELOG.md#3160)

[Compare
Source](https://redirect.github.com/Stebalien/tempfile/compare/v3.15.0...v3.16.0)

- Update `getrandom` to `0.3.0` (thanks to
[@&#8203;paolobarbolini](https://redirect.github.com/paolobarbolini)).
- Allow `windows-sys` versions `0.59.x` in addition to `0.59.0` (thanks
[@&#8203;ErichDonGubler](https://redirect.github.com/ErichDonGubler)).
- Improved security documentation (thanks to
[@&#8203;n0toose](https://redirect.github.com/n0toose) for collaborating
with me on this).

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 09:28:45 +02:00
renovate[bot]
a6c0388ce9 Update aws-sdk-rust monorepo (#23870)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aws-sdk-kinesis](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.58.0` -> `1.59.0` |
| [aws-sdk-s3](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.71.0` -> `1.72.0` |

---

### 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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

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

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 09:28:23 +02:00
renovate[bot]
0434b4b9ae Update Rust crate unindent to 0.2.0 (#23881)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [unindent](https://redirect.github.com/dtolnay/indoc) |
workspace.dependencies | minor | `0.1.7` -> `0.2.0` |

---

### Release Notes

<details>
<summary>dtolnay/indoc (unindent)</summary>

###
[`v0.2.3`](https://redirect.github.com/dtolnay/indoc/releases/tag/0.2.3)

[Compare
Source](https://redirect.github.com/dtolnay/indoc/compare/0.2.2...0.2.3)

-   Update to proc-macro-hack 0.4

###
[`v0.2.2`](https://redirect.github.com/dtolnay/indoc/releases/tag/0.2.2)

[Compare
Source](https://redirect.github.com/dtolnay/indoc/compare/0.2.1...0.2.2)

- Fix a shared library error when using indoc from a binary and running
outside of Cargo
([#&#8203;15](https://redirect.github.com/dtolnay/indoc/issues/15))

###
[`v0.2.1`](https://redirect.github.com/dtolnay/indoc/releases/tag/0.2.1)

[Compare
Source](https://redirect.github.com/dtolnay/indoc/compare/0.2.0...0.2.1)

- Add an `unstable` feature that changes the implementation to be a
Macros 2.0 macro. This is required in call sites that need a string
literal rather than just a &'static str, such as in a format string.
([#&#8203;12](https://redirect.github.com/dtolnay/indoc/issues/12))

    ```toml
    [dependencies]
    indoc = { version = "0.2.1", features = ["unstable"] }
    ```

    ```rust
    #![feature(proc_macro)]

    extern crate indoc;
    use indoc::indoc;

    fn main() {
        let username = "Boscop";
        let body = "Check this out";

        let message = format!(
            indoc!("
                Hello {username}
                ======{underline}

                {body}
            "),
            username = username,
            underline = "=".repeat(username.len()),
            body = body,
        );
        print!("{}", message);
    }
    ```

###
[`v0.2.0`](https://redirect.github.com/dtolnay/indoc/releases/tag/0.2.0)

[Compare
Source](https://redirect.github.com/dtolnay/indoc/compare/0.1.11...0.2.0)

- Rewrite to use
[`proc-macro-hack`](https://redirect.github.com/dtolnay/proc-macro-hack)
and work on stable Rust

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 09:28:15 +02:00
renovate[bot]
53f4ad8ad4 Update Rust crate uuid to v1.12.1 (#23882)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [uuid](https://redirect.github.com/uuid-rs/uuid) |
workspace.dependencies | minor | `1.11.0` -> `1.12.1` |

---

### Release Notes

<details>
<summary>uuid-rs/uuid (uuid)</summary>

###
[`v1.12.1`](https://redirect.github.com/uuid-rs/uuid/releases/tag/1.12.1)

[Compare
Source](https://redirect.github.com/uuid-rs/uuid/compare/1.12.0...1.12.1)

##### What's Changed

- Fix links to namespaces in documentation by
[@&#8203;cstyles](https://redirect.github.com/cstyles) in
[https://github.com/uuid-rs/uuid/pull/789](https://redirect.github.com/uuid-rs/uuid/pull/789)
- use inherent to_be_bytes and to_le_bytes methods by
[@&#8203;Vrtgs](https://redirect.github.com/Vrtgs) in
[https://github.com/uuid-rs/uuid/pull/788](https://redirect.github.com/uuid-rs/uuid/pull/788)
- Reduce bitshifts in from_u64\_pair by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/790](https://redirect.github.com/uuid-rs/uuid/pull/790)
- prepare for 1.12.1 release by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/791](https://redirect.github.com/uuid-rs/uuid/pull/791)

##### New Contributors

- [@&#8203;cstyles](https://redirect.github.com/cstyles) made their
first contribution in
[https://github.com/uuid-rs/uuid/pull/789](https://redirect.github.com/uuid-rs/uuid/pull/789)
- [@&#8203;Vrtgs](https://redirect.github.com/Vrtgs) made their first
contribution in
[https://github.com/uuid-rs/uuid/pull/788](https://redirect.github.com/uuid-rs/uuid/pull/788)

**Full Changelog**:
https://github.com/uuid-rs/uuid/compare/1.12.0...1.12.1

###
[`v1.12.0`](https://redirect.github.com/uuid-rs/uuid/releases/tag/1.12.0)

[Compare
Source](https://redirect.github.com/uuid-rs/uuid/compare/1.11.1...1.12.0)

##### ⚠️ Possible Breakage

This release includes additional `PartialEq` implementations on `Uuid`,
which can break inference in some cases.

##### What's Changed

- feat: Add `NonZeroUuid` type for optimized `Option<Uuid>`
representation by
[@&#8203;ab22593k](https://redirect.github.com/ab22593k) in
[https://github.com/uuid-rs/uuid/pull/779](https://redirect.github.com/uuid-rs/uuid/pull/779)
- Finalize `NonNilUuid` by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/783](https://redirect.github.com/uuid-rs/uuid/pull/783)
- Prepare for 1.12.0 release by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/784](https://redirect.github.com/uuid-rs/uuid/pull/784)

##### New Contributors

- [@&#8203;ab22593k](https://redirect.github.com/ab22593k) made their
first contribution in
[https://github.com/uuid-rs/uuid/pull/779](https://redirect.github.com/uuid-rs/uuid/pull/779)

**Full Changelog**:
https://github.com/uuid-rs/uuid/compare/1.11.1...1.12.0

###
[`v1.11.1`](https://redirect.github.com/uuid-rs/uuid/releases/tag/1.11.1)

[Compare
Source](https://redirect.github.com/uuid-rs/uuid/compare/1.11.0...1.11.1)

##### What's Changed

- Finish cut off docs by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/777](https://redirect.github.com/uuid-rs/uuid/pull/777)
- Fix links in CONTRIBUTING.md by
[@&#8203;jacobggman](https://redirect.github.com/jacobggman) in
[https://github.com/uuid-rs/uuid/pull/778](https://redirect.github.com/uuid-rs/uuid/pull/778)
- Update rust toolchain before building by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/781](https://redirect.github.com/uuid-rs/uuid/pull/781)
- Prepare for 1.11.1 release by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/782](https://redirect.github.com/uuid-rs/uuid/pull/782)

##### New Contributors

- [@&#8203;jacobggman](https://redirect.github.com/jacobggman) made
their first contribution in
[https://github.com/uuid-rs/uuid/pull/778](https://redirect.github.com/uuid-rs/uuid/pull/778)

**Full Changelog**:
https://github.com/uuid-rs/uuid/compare/1.11.0...1.11.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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 21:16:16 -05:00
Ben Kunkle
303cce0cbc Improve GitHub issue previews (#23853)
Modify the new issue templates so that the summary of the issue is
hopefully at the top and visible in previews/references to the issue

Release Notes:

- N/A
2025-01-29 19:02:28 -06:00
Marshall Bowers
19383036d5 anthropic: Fix license (#23867)
This PR fixes the license for the `anthropic` crate.

It was mistakenly licensed as AGPL, despite being used outside of
collab. It should be licensed as GPL.

Release Notes:

- N/A
2025-01-29 23:03:20 +00:00
William Blazer
ff72c6358e Fix project_panel::NewSearchInDirectory to work on files (#23696)
Closes #23383

This PR changes `project_panel::NewSearchInDirectory` to open project
search filtered by the parent directory when triggered on a file, rather
than doing nothing.

Release Notes:

- Improved `project_panel::NewSearchInDirectory` to search the parent
directory when triggered on a file
2025-01-29 22:50:12 +00:00
Michael Sloan
508c08bb86 Layout edit predictions popover within viewport instead of text bounds (#23865)
This makes the popover more likely to appear to the right of the longest
line.

Release Notes:

- N/A
2025-01-29 22:46:29 +00:00
Mikayla Maki
e970690cfa Add a shader compilation step to GPUI's build process (#23862)
This PR prevents situations like
https://github.com/zed-industries/zed/pull/23850, which caused our linux
nightly build to fail to open at all.

This PR also sorts the GPUI build and dev dependencies out from the sea
of platform specific dependencies.

Release Notes:

- N/A
2025-01-29 22:09:27 +00:00
curiouslad
e584586cb0 terminal: Fix alt-f and alt-b behavior (#23741)
Fixes alt+f and alt+b (word forward and word backward) behavior in
terminal

Release Notes:

- N/A
2025-01-29 14:01:25 -08:00
renovate[bot]
73c7f8aa8f Update aws-sdk-rust monorepo (#23859)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aws-config](https://redirect.github.com/smithy-lang/smithy-rs) |
dependencies | patch | `1.5.14` -> `1.5.15` |
| [aws-sdk-kinesis](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.56.0` -> `1.58.0` |
| [aws-sdk-s3](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.69.0` -> `1.71.0` |

---

### 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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

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

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 16:58:30 -05:00
Michael Sloan
ade3e45a36 Add character limits to edit prediction prompt generation (#23814)
Limits the size of the buffer excerpt and the size of change history.

Release Notes:

- N/A

---------

Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Joao <joao@zed.dev>
2025-01-29 21:56:29 +00:00
renovate[bot]
974b9eec85 Update Rust crate serde_json to v1.0.138 (#23858)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [serde_json](https://redirect.github.com/serde-rs/json) | dependencies
| patch | `1.0.137` -> `1.0.138` |
| [serde_json](https://redirect.github.com/serde-rs/json) |
workspace.dependencies | patch | `1.0.137` -> `1.0.138` |

---

### Release Notes

<details>
<summary>serde-rs/json (serde_json)</summary>

###
[`v1.0.138`](https://redirect.github.com/serde-rs/json/releases/tag/v1.0.138)

[Compare
Source](https://redirect.github.com/serde-rs/json/compare/v1.0.137...v1.0.138)

-   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 these
updates again.

---

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

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 16:48:54 -05:00
Michael Sloan
943d46c465 Make the edit predictions popover avoid overlapping the cursor (#23860)
Release Notes:

- N/A
2025-01-29 21:33:06 +00:00
Mikayla Maki
bd21334013 Add a crate for spawning tokio tasks in Zed (#23857)
Part of https://github.com/zed-industries/zed/pull/21092

As we're already depending on and using `tokio` to run `reqwest`, I've
added a crate to make running tokio futures more convenient. This should
unblock the Bedrock Cloud Model provider PR.

Note that since the `gpui_tokio` code is nearly trivial glue and I
expect that it will be useful for the nascent GPUI ecosystem, I've
elected to license it under Apache 2, like GPUI itself, instead of our
normal GPL license for Zed code.

Release Notes:

- N/A
2025-01-29 20:53:16 +00:00
Richard Feldman
ee0d2a8d94 Revise "Hide/Show Inline Completions" menu (#23808)
> **Note:** https://github.com/zed-industries/zed/pull/23813 should be
merged first!

@nathansobo and I paired on revising this menu, including adding the
"Predict Edits at Cursor" menu item (to make the keyboard shortcut more
discoverable; clicking it makes the inline edits show up, as shown in
the second screenshot) and switching from "Hide/Show" language to
checkboxes.

## Before
<img width="282" alt="Screenshot 2025-01-28 at 4 51 37 PM"
src="https://github.com/user-attachments/assets/309c82c1-8fb5-44db-950e-1a8789a63993"
/>

## After
<img width="1138" alt="Screenshot 2025-01-28 at 4 50 05 PM"
src="https://github.com/user-attachments/assets/302a126c-9389-42a4-bb7d-2896bce859e7"
/>

We also switched to use `SharedString` in more places, where it made
more sense.

@danilo-leal This isn't necessarily *exactly* what we want, but we were
pairing and decided to get it in a state where we can actually try it
out and tweak from here.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-01-29 17:45:28 -03:00
renovate[bot]
4cef772364 Update Rust crate mdbook to v0.4.44 (#23856)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [mdbook](https://redirect.github.com/rust-lang/mdBook) | dependencies
| patch | `0.4.43` -> `0.4.44` |

---

### Release Notes

<details>
<summary>rust-lang/mdBook (mdbook)</summary>

###
[`v0.4.44`](https://redirect.github.com/rust-lang/mdBook/blob/HEAD/CHANGELOG.md#mdBook-0444)

[Compare
Source](https://redirect.github.com/rust-lang/mdBook/compare/v0.4.43...v0.4.44)


[v0.4.43...v0.4.44](https://redirect.github.com/rust-lang/mdBook/compare/v0.4.43...v0.4.44)

##### Added

-   Added pre-built aarch64-apple-darwin binaries to the releases.
[#&#8203;2500](https://redirect.github.com/rust-lang/mdBook/pull/2500)
-   `mdbook clean` now shows a summary of what it did.
[#&#8203;2458](https://redirect.github.com/rust-lang/mdBook/pull/2458)
- Added the `output.html.search.chapter` config setting to disable
search indexing of individual chapters.
[#&#8203;2533](https://redirect.github.com/rust-lang/mdBook/pull/2533)

##### Fixed

- Fixed auto-scrolling the side-bar when loading a page with a `#`
fragment URL.
[#&#8203;2517](https://redirect.github.com/rust-lang/mdBook/pull/2517)
-   Fixed display of sidebar when javascript is disabled.
[#&#8203;2529](https://redirect.github.com/rust-lang/mdBook/pull/2529)
-   Fixed the sidebar visibility getting out of sync with the button.
[#&#8203;2532](https://redirect.github.com/rust-lang/mdBook/pull/2532)

##### Changed

-  Rust code block hidden lines now follow the same logic as rustdoc.
This requires a space after the `#` symbol.
[#&#8203;2530](https://redirect.github.com/rust-lang/mdBook/pull/2530)
-  Updated the Linux pre-built binaries which requires a newer version
of glibc (2.34).
[#&#8203;2523](https://redirect.github.com/rust-lang/mdBook/pull/2523)
-   Updated dependencies
[#&#8203;2538](https://redirect.github.com/rust-lang/mdBook/pull/2538)
[#&#8203;2539](https://redirect.github.com/rust-lang/mdBook/pull/2539)

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 20:25:27 +00:00
renovate[bot]
a62ce45126 Update actions/setup-node digest to 1d0ff46 (#23854)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-node](https://redirect.github.com/actions/setup-node) |
action | digest | `39370e3` -> `1d0ff46` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjM5LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 20:18:19 +00:00
Jason Lee
b9e0aae49f gpui: Enable MSAA to Path render for Anti-Aliasing (#22812)
Closes #20762

Release Notes:

- N/A

---

Enable MSAA for Anti-Aliasing to Path (`cx.paint_path`) for drawing a
better vector graphics.

```bash
cargo run -p gpui --example gradient --features macos-blade
cargo run -p gpui --example gradient

cargo run -p gpui --example painting --features macos-blade
cargo run -p gpui --example painting
```

**Before**

<img width="1089" alt="image"
src="https://github.com/user-attachments/assets/0ae7240f-4ba9-4ef5-896c-e436c1282770"
/>

**After**

<img width="944" alt="image"
src="https://github.com/user-attachments/assets/71a07ae8-be54-452c-aacc-b8cec1f810c0"
/>

## TODO

- [x] Support Metal and Blade.
- [x] Detect system support to set up sample count.
- [x] Fix extra lines between Path vertices wait #22808 to merge.

Ref https://github.com/kvark/blade/pull/213

Ask @kvark to review.

I am not sure if there is anything I missed. I modified it according to
the
[particle](https://github.com/kvark/blade/tree/main/examples/particle)
example of Blade project. But the difference is that after the first
MSAA render, I did not do it a second time, I tested it and found it was
not necessary.
2025-01-29 22:14:40 +02:00
Jason Lee
31fa414422 gpui: Add PathBuilder based on lyon to build Path (#22808)
Release Notes:

- N/A

---

Continue https://github.com/zed-industries/zed/pull/20499

We to draw more complex Path. Before this change, we only have
`line_to`, but it is not enough.

Add a new `PathBuilder` to use [lyon](https://github.com/nical/lyon) to
build more complex path.

And then with PR #22812 to enable anti-aliasing, all thing will be
perfect.
## Show case

```bash
cargo run -p gpui --example painting
```

Before:

<img width="1136" alt="image"
src="https://github.com/user-attachments/assets/0c15833a-ec95-404c-a469-24cf172cfd86"
/>

After:

<img width="1136" alt="image"
src="https://github.com/user-attachments/assets/42cfa35e-7e8f-4ef3-bb2d-b98defc62ad6"
/>
2025-01-29 22:14:33 +02:00
Jason Lee
706f7be5e7 gpui: Add line_clamp to truncate text after a specified number of lines (#23058)
Release Notes:

- N/A

Add this feature for some case we need keep 2 or 3 lines, but truncate.
For example the blog post summary.

- Added `line_clamp` method.
    Ref: https://tailwindcss.com/docs/line-clamp


## Break changes:

- Renamed `gpui::Truncate` to `gpui::TextOverflow` to match
[CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow).
- Update `truncate` style method to match [Tailwind
CSS](https://tailwindcss.com/docs/text-overflow) behavior:

    ```css
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    ```
<img width="538" alt="image"
src="https://github.com/user-attachments/assets/c69c4213-eac9-4087-9daa-ce7afe18c758"
/>


## Show case

<img width="816" alt="image"
src="https://github.com/user-attachments/assets/e0660290-8042-4954-b93c-c729d609484a"
/>

![CleanShot 2025-01-13 at 17 22
05](https://github.com/user-attachments/assets/38644892-79fe-4254-af9e-88c1349561bd)

## Describe changes

The [second
commit](6b41c2772f)
for make sure text layout to match with the line clamp. Before this
change, they may wrap many lines in sometimes. And I also make
line_clamp default to 1 if we used `truncate` to ensure no wrap.

> TODO: There is still a tiny detail that is not easy to fix. This
problem only occurs in the case of certain long words. I will think
about how to improve it later. At present, this has some flaws but does
not affect the use.
2025-01-29 22:14:24 +02:00
Joseph T. Lyons
baac01cea4 Revert "Attempt to suppress embeds in Discord webhook (#23807)" (#23855)
Didn't work.

Release Notes:

- N/A
2025-01-29 15:08:30 -05:00
Danilo Leal
f8dddf0a5c assistant2: Tweak the settings UI (#23845)
This PR does some somewhat light UI adjustment to the Assistant 2
settings view. The Prompt Library section should feature the default
prompts in the future, so that's why it's been separated that way.

<img width="800" alt="Screenshot 2025-01-29 at 2 59 59 PM"
src="https://github.com/user-attachments/assets/7b033bde-51ab-44d5-9e53-3f72b8ff5f51"
/>

Release Notes:

- N/A
2025-01-29 16:20:09 -03:00
Nate Butler
a03b7624f1 Revert "gpui & ui: Use shader for dashed dividers" (#23850)
Reverts zed-industries/zed#23839

getting some reports of linux crashes – will investigate later today

Release Notes:

- N/A
2025-01-29 19:19:20 +00:00
Marshall Bowers
8603a908c1 zeta: Send staff edit predictions through Cloudflare Workers (#23847)
This PR makes it so staff edit predictions now go through Cloudflare
Workers instead of going to the LLM service.

This will allow us to dogfood the new LLM worker to make sure it is
working as expected.

Release Notes:

- N/A
2025-01-29 13:22:16 -05:00
Nate Butler
e5943975f9 gpui & ui: Use shader for dashed dividers (#23839)
TODO:
- [x] BackgroundOrientation
- [x] PatternDash
- [x] `pattern_horizontal_dash` & `pattern_vertical_dash`
- [x] Metal dash shader
- [x] Blade dash shader
- [x] Update ui::Divider to use new pattern

---

This PR introduces proper dashed dividers using the new `PatternDash`
background shader.

![CleanShot 2025-01-29 at 09 33
06@2x](https://github.com/user-attachments/assets/2db5af58-1aa9-4ad7-aa52-b9046fbf8584)

Before this we were using 128 elements to create a dashed divider, which
is both expensive, and would not scale beyond a certain size. This
allows us to simplify the divider element as well.

Changes:

- Adds `BackgroundOrientation` to `gpui::color::Background` to allow
specifying a direction for a pattern
- Adds the PatternDash pattern variant
- Updates `ui::Divider`'s dashed variants to be more efficient

Misc:
- Documents the `ui::Divider` component
- Treat `.metal` files as `C` in the Zed project until we get some metal
syntax highlighting.

Release Notes:

- N/A
2025-01-29 12:18:34 -05:00
Joseph T. Lyons
8442e2b9d8 Bump Zed to v0.173 (#23843)
Release Notes:

-N/A
2025-01-29 11:55:51 -05:00
Marshall Bowers
5ecff157aa collab: Add internal POST /snowflake/events endpoint (#23842)
This PR adds a new internal `POST /snowflake/events` endpoint to collab.

This endpoint is protected with the admin token like our other internal
endpoints.

This endpoint accepts a `SnowflakeRow` in the body and writes it to the
AWS Kinesis stream.

Release Notes:

- N/A
2025-01-29 16:33:48 +00:00
Bennet Bo Fenner
fb9b4ee842 edit prediction: Remove zeta codename from action (#23835)
Release Notes:

- N/A
2025-01-29 13:00:10 +00:00
Kirill Bulatov
07161d65d0 Bind editor::OpenSelectionsInMultibuffer in full editors only (#23832)
Follow-up of https://github.com/zed-industries/zed/pull/23648

Binding `alt-enter` to all editors breaks
46f45464be/assets/keymaps/default-macos.json (L281)
key binding for buffer search, and it's impossible to select all search
matches anymore.

Release Notes:

- N/A
2025-01-29 12:06:27 +00:00
Bennet Bo Fenner
9bf5e55233 Revert "inline completion: Add syntax highlighting for edit prediction (#23361)" (#23829)
This reverts commit 3dee32c43d.

Release Notes:

- N/A
2025-01-29 11:32:18 +01:00
Kirill Bulatov
46f45464be Fix terminal drag and drop (#23827)
Closes https://github.com/zed-industries/zed/discussions/23823

* Fixes terminal drag and drop not working after
https://github.com/zed-industries/zed/pull/23256
* Fixes project panel items drag and drop not working after selection
overhaul even earlier: now, all marked items are added to terminal on
drag and drop

Release Notes:

- Fixed terminal drag and drop, including project panel items
2025-01-29 09:44:51 +00:00
Bennet Bo Fenner
d2d9f492b9 edit prediction: Do not log error when prediction cannot be interpolated (#23826)
Previously we returned an error when the interpolation failed in
`process_completion_response`.
However, it is not an error when interpolation returns `None`. That just
means that the predicted edits can be discarded, because the user typed
something that is not a subset of what the model predicted OR if the
model responds with a no-op.
```
2025-01-29T09:44:30.221135+01:00 [ERROR] zeta prediction failed

Caused by:
    Interpolated edits are empty
```

Release Notes:

- N/A
2025-01-29 10:15:46 +01:00
Jason Lee
6d4ccb0eb1 Fix project_panel::NewDirectory in TextMate keymap (#23825)
Release Notes:

- Fixed incorrect action names in TextMate keymap.
2025-01-29 08:37:45 +00:00
Michael Sloan
dbdf140ca1 Show settings file errors on startup (#23817)
Required using a global `LazyLock<Mutex<AppNotifications>>` instead of a
context global because settings errors first occur before initialization
of the notifications global.

Release Notes:

- Errors in settings file are now reported in UI on startup.
2025-01-29 07:05:33 +00:00
Joseph T. Lyons
06936c69f6 Prompt users to use Discussions for feature requests (#23821)
I'm moving forward on this - we can revert if it ends up being a bad
move.

Release Notes:

- N/A
2025-01-29 06:48:07 +00:00
Michael Sloan
43f3491d50 Add comment explaining why AddSurrounds target is not deserializable (#23820)
See #23088

Release Notes:

- N/A
2025-01-29 06:18:56 +00:00
João Marcos
16004d4c6a Fix deprecated alias for toggling hunks (#23818)
Release Notes:

- N/A
2025-01-28 23:02:03 -07:00
Osvaldo
9e31b1019e vim: Add any brackets to support motions like ab and ib to work with any type of brackets (#23679)
# Add AnyBrackets text object for Vim mode

## Overview
This PR introduces a new text object `AnyBrackets` that allows
operations on the closest matching pair of brackets, regardless of the
bracket type. This enhances the editing experience by reducing the need
to identify specific bracket types before performing text operations.

By default, this feature is NOT mapped to any key in vim.json. However,
it can be enabled manually, and the recommended key for mapping is b:

If you want to add it to your zed keymap config you need to add the
following config:
```json
{
	"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
	"bindings": {
		"b": "vim::AnyBrackets"
	}
}
```

## Features
- New text object that works with parentheses `()`, square brackets
`[]`, curly braces `{}`, they are also know as round brackets, square
brackets and curly brackets in english.
- Automatically finds the closest matching pair of any bracket type
- Works with all standard Vim operators (delete, change, yank)
- Supports both "inside" and "around" variants (`i` and `a`)

## Usage Examples
```vim
# Delete inside the closest brackets
di(  # Works on (), [] or {} depending on which is closest

# Change around the closest brackets
ca[  # Works on (), [] or {}  depending on which is closest

# Visual select inside the closest brackets
vi{  # Works on (), [] or {}  depending on which is closest
```

# References:
- Based on the popular plugin https://github.com/echasnovski/mini.ai

# Important Notes
This PR also fixes a bug with nested quotes on AnyQuotes, now it works
fine with any type of quotes or brackets.
Please take a look at the new tests to understand the expected behavior.

Release Notes:

- vim: Add `ab`/`ib` "AnyBrackets" text objects that are the smallest of
`a(`, `a[` or `a{` or `i(`, `i[` or `i{`
- vim: Fix aq/iq "AnyQuotes" text objects when they are nested
2025-01-28 20:23:17 -07:00
Max Brunsfeld
442ea508c4 Ensure hunk controls have unique element ids (#23815)
This fixes an edge case when two hunk controls button groups were
visible (due to having text cursor on one hunk, and mouse cursor on the
other). In that situation, the mouse states for the two button groups
would mirror.

Release Notes:

- N/A
2025-01-29 01:08:51 +00:00
Richard Feldman
33d1145c3f Refactor to use SharedString in more places (#23813)
Splitting this off from
https://github.com/zed-industries/zed/pull/23808, per @maxdeviant's
suggestion!

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
2025-01-28 19:04:21 -05:00
Conrad Irwin
92a1cb893f Restore go to type definition et.al (#23810)
Accidentally dropped by the GPUI3 refactr

Release Notes:

- N/A
2025-01-28 16:02:03 -07:00
Marshall Bowers
3b6e1be169 collab: Fix error message when missing Kinesis region (#23811)
This PR fixes a typo in the error that occurs when trying to construct
an AWS Kinesis client and the `kinesis_region` value is missing.

Release Notes:

- N/A
2025-01-28 22:52:50 +00:00
Victor Quiroz
353ae316c9 prisma: Update grammar and syntax highlighting (#23596)
- Updates the bindings
([tree-sitter-prisma](https://github.com/victorhqc/tree-sitter-prisma))
to its latest update (recently updated to use latest tree-sitter)
- Improves syntax highlighting
- Adds the `view` keyword

**After**

<img width="1174" alt="Screenshot 2025-01-24 at 12 44 57"
src="https://github.com/user-attachments/assets/84e6afe0-5340-4cdf-ad85-9a800a757323"
/>

**Before**

<img width="1174" alt="Screenshot 2025-01-24 at 12 44 45"
src="https://github.com/user-attachments/assets/11296998-fdfe-4fe8-8e5b-feeb41c24385"
/>

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-01-28 22:02:16 +00:00
Joseph T. Lyons
e1646e6ff4 Attempt to suppress embeds in Discord webhook (#23807)
Closes: https://github.com/zed-industries/zed/issues/6884

(hopefully 🤞)

Release Notes:

- N/A
2025-01-28 21:48:35 +00:00
Conrad Irwin
1973bf5268 Allow buffer search to search deleted hunks (#23632)
Closes #ISSUE

Release Notes:

- N/A
2025-01-28 21:48:16 +00:00
Piotr Osiewicz
22afec32cf Revert "project: Fine-grained language server management" (#23804)
Reverts zed-industries/zed#23708
2025-01-28 21:38:06 +00:00
Marshall Bowers
bda269059b ci: Restrict more jobs to only run in the zed-industries organization (#23803)
This PR updates the GitHub Action definitions to restrict more CI jobs
to only run in the `zed-industries` organization (and thus, not on
forks).

Release Notes:

- N/A
2025-01-28 21:30:42 +00:00
Piotr Osiewicz
c4e6c619ba project: Fine-grained language server management (#23708)
This reverts commit d8c9fdd014.

Closes #ISSUE

Release Notes:

- N/A
2025-01-28 22:14:55 +01:00
Conrad Irwin
2b677736bf Don't re-wrap unneccessarily on expanding hunks (#23796)
Co-Authored-By: Max <max@zed.dev>

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2025-01-28 11:15:47 -08:00
Max Brunsfeld
7b901caf8f Fix rendering of gutter diff hunks that extend to EOF, w/o newline (#23790)
Release Notes:

- N/A
2025-01-28 18:27:19 +00:00
Marshall Bowers
47dcbdfe51 gpui: Fix pattern example (#23786)
This PR fixes the `pattern` example, which was merged in
https://github.com/zed-industries/zed/pull/23576 without being updated
with the new GPUI changes.

Release Notes:

- N/A
2025-01-28 12:02:57 -05:00
Nate Butler
23672987ff gpui: Add support for slash pattern fills (///) (#23576)
TODO:
- [x] Add BackgroundTag::PatternSlash
- [x] Support metal slash pattern fills
- [x] Support blade slash pattern fills
---

Adds support for a new background type in gpui, `pattern_slash`.

Usage:

```rust
div().size(px(56.0)).bg(pattern_slash(gpui::red()))
```
This will create a 56px square with a red slash pattern fill.

You can run the pattern example with `cargo run -p gpui --example
pattern`:

![CleanShot 2025-01-23 at 16 22
09@2x](https://github.com/user-attachments/assets/39d9f8c8-816c-4d3b-bc75-fcc122747e17)

---

After talking with @as-cii at length about how we want to support
patterns in gpui, we decided for now we'll simply add a new
BackgroundTag specific to this pattern.

It isn't the best long term plan however – we'll likely want to
introduce the concept of a `Fill` at some point so we can have
`Fill::Solid`, `Fill::Gradient(LinearGradient)`, etc in the future.

The pattern is designed to seamlessly tile vertically for elements of
the same height. For example, for use in editor line backgrounds:

![CleanShot 2025-01-23 at 16 27
41@2x](https://github.com/user-attachments/assets/d51b94bc-cfc2-4aff-89e3-289a04ea8841)

---


Release Notes:

(do we do gpui release notes?)
- Adds support for slash pattern fills in `gpui`.

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-01-28 11:33:34 -05:00
Marshall Bowers
070890d361 anthropic: Don't bail out on unknown model ID (#23782)
This PR fixes an issue introduced in
https://github.com/zed-industries/zed/pull/20551/ that would prevent
models with unknown IDs from working in the LLM service.

We only need to look up a model from its ID for the beta headers, and if
we can't find that particular model we should fall back to the default
beta headers instead of bailing out completely,

Release Notes:

- N/A
2025-01-28 10:56:05 -05:00
João Marcos
2b160f4f3c Omit gitignored files from context file picker (#23777)
In both `thread` and `prompt editor` the context file picker, gitignored
files are hidden (as expected) when searching files by path, but they
are still shown initially as you create the file picker.

Plus, selecting gitignored files in the `prompt editor` is bugged and
collapses everything.

This PR settles on not showing gitignored files to solve these
inconsistencies.

Release Notes:

- Fix gitignored files filter occasionally not working in context file
picker.
2025-01-28 11:40:42 -03:00
Kirill Bulatov
a5957bfaeb Sanitize another pair of brackets when hovering over a path in the terminal (#23776)
Closes https://github.com/zed-industries/zed/issues/23774

Release Notes:

- Improved terminal hover word matching
2025-01-28 16:03:48 +02:00
Piotr Osiewicz
b74a273934 project search: Do not bail on search when a binary file is encountered (#23775)
Closes #ISSUE

Release Notes:

- N/A
2025-01-28 14:54:00 +01:00
Kirill Bulatov
7105f9c68c Show entries in remote git panels (#23773)
Now both remote collab and ssh remote get entries shown and updated in
the git panel.
This seems to be quite a step towards remote git support, hence
submitting a PR.

Further steps: remove `get_local_repo` and allow getting the repo from
`Worktree`, not its local counterpart + have another, remote impl of the
`GitRepository` trait.

Release Notes:

- N/A
2025-01-28 15:25:59 +02:00
Bennet Bo Fenner
fc5461adf4 Revert "edit prediction: Fix crash in highlight_text (#23766)" (#23771)
This reverts commit dfed43ab24.

Closes #ISSUE

Release Notes:

- N/A
2025-01-28 13:56:57 +01:00
Bennet Bo Fenner
57a3d8c491 edit prediction: Hide rate completions modal behind feature flag (#23597)
This hides the ability to rate completions behind the
`predict-edits-rate-completions` feature flag

Release Notes:

- N/A
2025-01-28 12:27:09 +01:00
Bennet Bo Fenner
dfed43ab24 edit prediction: Fix crash in highlight_text (#23766)
This fixes the panics we we're seeing in `EditPreview::highlight_edits`.
The reason for this was that we were interpolating edits incorrectly.

Here's an example:

```rust
let a = 0; // existing code
let c = 2; // suggested by edit prediction
```
The edits would look like this: `[(Point(1, 0)..Point(1, 0), "let c =
2;"]`

Now i type:
```rust
let a = 0; // existing code
let b = 1; // added this line
let c = 2; // suggested by edit prediction
```

Before this change, the `interpolate` function would allow insertions
before the edit prediction edits, the anchors will move to the next
line.
The edits would look now like this: `[(Point(2, 0)..Point(2, 0), "let c
= 2;"]`

However, now we end up with a call to `EditPreview::highlight_edits`,
with the following parameters:
- current_snapshot: 
  ```rust
  let a = 0;
  let b = 1;
  ```
- edits: `[(Point(2, 0)..Point(2, 0), "let c = 2;"]`
- applied_edits_snapshot:
  ```rust
  let a = 0;
  let c = 2;
  ```

And here you can see the issue, applying the `edits` to the
`current_snapshot` should always end up re-creating the text that is
present in the `applied_edits_snapshot`. That is not the case here
though, meaning that the offsets in the new buffer are not correct,
which can either lead to a confusing popup or a crash if the suggestion
is at the end of the file.

Here's a real world example (edit prediction is ONLY suggesting to
delete a new line):

<img width="487" alt="Screenshot 2025-01-27 at 13 05 26"
src="https://github.com/user-attachments/assets/a0a8064e-8cfa-48b2-9f1c-efc2d0d9d7d4"
/>

We fixed this by only allowing interpolation if the user is editing
after all the edit predictions OR if the user edit is a subset of the
model suggestion.



Co-Authored-by: Antonio <antonio@zed.dev>

Release Notes:

- N/A

Co-authored-by: Antonio <antonio@zed.dev>
2025-01-28 12:08:04 +01:00
loczek
b99159c59b snippets: Fix snippets not updating while containing comments (#23755)
Closes #23699

Release Notes:

- Fixed issue where snippets would not update when a snippets file
contained comments.
2025-01-28 10:37:48 +01:00
jfmontanaro
bb59e7f217 Refine syntax highlighting for Python docstrings (#20898)
Following up on #20763, this PR adds support for module- and class-level
docstrings, adds "additional docstrings" as described in [PEP
257](https://peps.python.org/pep-0257/), and fixes function-level
docstrings so that only the first string literal in a function gets
treated as a docstring.

One question that occurs to me is: Would it be good to capture attribute
and additional docstrings differently from regular docstrings? E.g.
`@string.doc.attribute`, `@string.doc.additional`? PEP 257 mentions that
unlike regular docstrings, these docstrings are ignored by the
interpreter (regular docstrings get added as the `__doc__` property of
the object they document), so I can see someone potentially wanting to
style them a little differently.

Release notes:

* Added Python syntax highlighting for class- and module-level
docstrings, additional docstrings, and improved recognition of
function-level docstrings.

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-01-28 10:23:43 +01:00
Kirill Bulatov
b643080117 Use proper names for actions' async context (#23763)
Post-PR merge fixes.

Release Notes:

- N/A
2025-01-28 11:18:00 +02:00
tims
02af8dde16 menus: Add "Open File" action for Linux and Windows (#23707)
This PR adds menu item for `workspace::OpenFiles` in app menu on Linux
and Windows.

Context:
When opening a file or folder on Linux and Windows via the native file
picker, the picker can be either in file-only mode or folder-only mode.
This means you have to open it already knowing whether you want to open
a file or a folder, unlike macOS, which lets you choose either in the
same picker.

For this reason, a new action, `workspace::OpenFiles`, was recently
added for Linux and Windows. This is basically file-only mode, alongside
the existing `workspace::Open` action, which is folder-only. In macOS,
the `workspace::Open` action is sufficient to open both file and folder.

Before:  
<img
src="https://github.com/user-attachments/assets/67dc95d6-e98d-438a-9568-570e87617f85"
alt="Before" width="200" />

After:  
<img
src="https://github.com/user-attachments/assets/d0ffd02c-0f48-4edc-b426-4d430f2e0c86"
alt="After" width="200" />

Release Notes:

- Added "Open File" action in file menu for Linux and Windows.
2025-01-28 11:10:00 +02:00
tims
34d0b57945 editor: Fix inline Git blame not visible on long lines due to overflow (#23374)
Closes #18702

This is take 2 of [my previous
PR](https://github.com/zed-industries/zed/pull/19555), which was closed
due to inactivity and merge conflicts.

**Cause**: 

The editor's horizontal scroll width only considers the longest line in
the buffer, using `layout_line` for `longest_row`. The inline blame
width isn’t included in it because it is just a decoration on top of the
line (think of like CSS absolute) and not part of its actual content.
This causes blame to overflow.

**Solution**:

Along with `longest_row` width we also add that line's inline blame
width for scroll width calculation. We also have to add some padding
that is between inline blame and line's content.

**Alternate Solution**:

In my previous PR, instead of adding the inline blame width of the
longest line for scroll width calculation, I used the inline blame of
the current line the cursor is on (since we only see the blame for the
current line). I added that to the current line's width, giving us the
full width of that row. Then, we compare that row's width with the
longest row width and use the max of the two for the scroll width
calculation.

While this solution seems clever, it's overly complicated and could
cause issues, like the scroll width changing every time you move the
cursor up or down. I don't think we should go with this, but I'm open to
suggestions.

**Preview**:

Before:


https://github.com/user-attachments/assets/01ef90cf-06e7-4ebb-8bd1-637a53e0654e

After:


https://github.com/user-attachments/assets/b13616de-bdea-4da4-b32d-9c4104448166


Release Notes:

- Fixed inline Git blame not visible on long lines due to overflow.
2025-01-28 10:47:11 +02:00
tims
f314662048 project_panel: Add precise drag-and-drop for files onto folded directories (#22983)
Closes #19192

1. Changed the drag overlay of entries for better visibility of where to
drop.
2. Folded directories (except for the last folded one) will be
highlighted as drop targets.
3. The delimiter between folded directories prevents the directory
highlight from losing focus and acts as part of the directory to avoid
flickering.

This works just like VS Code does.


[fold-drop.webm](https://github.com/user-attachments/assets/853f7c5e-3492-4f56-9736-6d0e3ef09325)

Release Notes:

- Added precise drag-and-drop for files onto folded directories in the
Project Panel.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-28 10:42:10 +02:00
tims
5c650cdcb2 project_panel: Add Alt/Opt+Click to expand/collapse a directory and all its contents (#22896)
Closes #15966 

This PR adds `Alt/Opt+Click` to expand or collapse a directory and all
its contents.

Context:

The current `expand_entry` scans immediate child subdirectories if they
aren’t loaded, while `expand_all_for_entry` scans the entire subtree.
The latter takes longer, so we wait for it to complete to ensure
accurate results.

For full directory scan, instead of using
`refresh_entries_for_paths(vec![path])`, which requires specifying all
explicit paths to refresh, we use `add_path_prefix_to_scan`, which
eliminates the need to list every path. Both methods internally call
`reload_entries_for_paths`, which invokes `should_scan_directory`. This
determines whether to scan deeper based on a path prefix match between
the given directory and its subdirectories, returning `true` for
`add_path_prefix_to_scan`.

The existing code handles scanning, removing path prefixes after scans
complete, and managing ignored directories.

How it works (Expand):
1. Alt clicking on non-ignored closed directory, expands it and all its
subdirectories, except ignored subdirectories. This helps while working
on mono repos, where you might not want to expand dirs like
`node_modules`, `dist`, etc or git submodules, when you expand any root
dir.

In example, `draft` and `posts` dir are ignored dir.


[expand-1.webm](https://github.com/user-attachments/assets/07d3f724-0757-408f-b349-5beb4ee8440e)

2. Alt clicking on ignored closed directory, expands it and all its
subdirectories. This is when you explicitly want to do it, on dirs like
`node_modules`, `dist`, etc.

In example, `dist` dir is ignored dir.


[expand-2.webm](https://github.com/user-attachments/assets/99e55883-ab1a-4a9c-a0f0-48026991a922)

3. In case of auto folded subdirectories, expand all action will take
precedence over it. That is, it will unfold all the subdirectories
inside clicked dir. This is intentional, as user explicitly wants to
reveal as much content as possible. (This is my personal opinion on how
it should work).


[expand-3.webm](https://github.com/user-attachments/assets/f20b0311-e92a-4e34-b640-1469b0d6fa16)

How it works (Collapse):
1. Alt clicking any opened directory will collapse it and all its
children, whether ignored or not. This is when you want to start from a
fresh state.

2. When auto fold is enabled in settings, collapse action will also fold
all subdirectories that it can fold. This is to bring it back to its
fresh state as mentioned above.


[collapse-1-2.webm](https://github.com/user-attachments/assets/74db6cee-0afa-406b-a9a2-7421083a2c2a)


Future:
- Using keybinding to expand/collapse all for selected entry
- Handle expand/collapse all for folded entry

Todos:
- [x] Expand entries logic
- [x] Handle remote worktree for expand
- [x] Figure out scan complete status
- [x] Move expansion logic to status update event
- [x] Collapse entries logic
- [x] Handle fold/unfold subdirs interaction
- [x] Do not expand git ignored sub-dirs
- [x] Tests
- [x] Test Remote

Release Notes:

- Added Alt/Opt+Click functionality to expand or collapse a directory
and all its contents.
2025-01-28 10:37:56 +02:00
renovate[bot]
793873bdc9 Update Rust crate sqlx to v0.8.3 (#22867)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sqlx](https://redirect.github.com/launchbadge/sqlx) |
dev-dependencies | patch | `0.8.2` -> `0.8.3` |
| [sqlx](https://redirect.github.com/launchbadge/sqlx) | dependencies |
patch | `0.8.2` -> `0.8.3` |

---

### Release Notes

<details>
<summary>launchbadge/sqlx (sqlx)</summary>

###
[`v0.8.3`](https://redirect.github.com/launchbadge/sqlx/blob/HEAD/CHANGELOG.md#083---2025-01-03)

[Compare
Source](https://redirect.github.com/launchbadge/sqlx/compare/v0.8.2...v0.8.3)

41 pull requests were merged this release cycle.

##### Added

- \[[#&#8203;3418]]: parse timezone parameter in mysql connection url
\[\[[@&#8203;dojiong](https://redirect.github.com/dojiong)]]
- \[[#&#8203;3491]]: chore: Update async-std v1.13
\[\[[@&#8203;jayvdb](https://redirect.github.com/jayvdb)]]
- \[[#&#8203;3492]]: expose relation_id and relation_attribution_no on
PgColumn
\[\[[@&#8203;kurtbuilds](https://redirect.github.com/kurtbuilds)]]
- \[[#&#8203;3493]]: doc(sqlite): document behavior for zoned date-time
types \[\[[@&#8203;abonander](https://redirect.github.com/abonander)]]
- \[[#&#8203;3500]]: Add sqlite commit and rollback hooks
\[\[[@&#8203;gridbox](https://redirect.github.com/gridbox)]]
- \[[#&#8203;3505]]: chore(mysql): create test for passwordless auth
([#&#8203;3484](https://redirect.github.com/launchbadge/sqlx/issues/3484))
\[\[[@&#8203;abonander](https://redirect.github.com/abonander)]]
- \[[#&#8203;3507]]: Add a "sqlite-unbundled" feature that dynamically
links to system libsqlite3.so library
\[\[[@&#8203;lilydjwg](https://redirect.github.com/lilydjwg)]]
- \[[#&#8203;3508]]: doc(sqlite): show how to turn options into a pool
\[\[[@&#8203;M3t0r](https://redirect.github.com/M3t0r)]]
- \[[#&#8203;3514]]: Support PgHstore by default in macros
\[\[[@&#8203;joeydewaal](https://redirect.github.com/joeydewaal)]]
- \[[#&#8203;3550]]: Implement Acquire for PgListener
\[\[[@&#8203;sandhose](https://redirect.github.com/sandhose)]]
- \[[#&#8203;3551]]: Support building with rustls but native
certificates
\[\[[@&#8203;IlyaBizyaev](https://redirect.github.com/IlyaBizyaev)]]
- \[[#&#8203;3553]]: Add support for Postgres lquery arrays
\[\[[@&#8203;philipcristiano](https://redirect.github.com/philipcristiano)]]
- \[[#&#8203;3560]]: Add PgListener::next_buffered(), to support batch
processing of notifications
\[\[[@&#8203;chanks](https://redirect.github.com/chanks)]]
- \[[#&#8203;3577]]: Derive Copy where possible for database-specific
types \[\[[@&#8203;veigaribo](https://redirect.github.com/veigaribo)]]
- \[[#&#8203;3579]]: Reexport AnyTypeInfoKind
\[\[[@&#8203;Norlock](https://redirect.github.com/Norlock)]]
- \[[#&#8203;3580]]: doc(mysql): document difference between `Uuid` and
`uuid::fmt::Hyphenated`
\[\[[@&#8203;abonander](https://redirect.github.com/abonander)]]
- \[[#&#8203;3583]]: feat: point
\[\[[@&#8203;jayy-lmao](https://redirect.github.com/jayy-lmao)]]
- \[[#&#8203;3608]]: Implement AnyQueryResult for Sqlite and MySQL
\[\[[@&#8203;pxp9](https://redirect.github.com/pxp9)]]
- \[[#&#8203;3623]]: feat: add geometry line
\[\[[@&#8203;jayy-lmao](https://redirect.github.com/jayy-lmao)]]
- \[[#&#8203;3658]]: feat: add Transaction type aliases
\[\[[@&#8203;joeydewaal](https://redirect.github.com/joeydewaal)]]

##### Changed

- \[[#&#8203;3519]]: Remove unused dependencies from sqlx-core, sqlx-cli
and sqlx-postgres
\[\[[@&#8203;vsuryamurthy](https://redirect.github.com/vsuryamurthy)]]
- \[[#&#8203;3529]]: Box Pgconnection fields
\[\[[@&#8203;joeydewaal](https://redirect.github.com/joeydewaal)]]
- \[[#&#8203;3548]]: Demote `.pgpass` file warning to a debug message.
\[\[[@&#8203;denschub](https://redirect.github.com/denschub)]]
- \[[#&#8203;3585]]: Eagerly reconnect in `PgListener::try_recv`
\[\[[@&#8203;swlynch99](https://redirect.github.com/swlynch99)]]
- \[[#&#8203;3596]]: Bump thiserror to v2.0.0
\[\[[@&#8203;paolobarbolini](https://redirect.github.com/paolobarbolini)]]
- \[[#&#8203;3605]]: Use `UNION ALL` instead of `UNION` in nullable
check \[\[[@&#8203;Suficio](https://redirect.github.com/Suficio)]]
- \[[#&#8203;3629]]: chore: remove BoxFuture's (non-breaking)
\[\[[@&#8203;joeydewaal](https://redirect.github.com/joeydewaal)]]
- \[[#&#8203;3632]]: Bump hashlink to v0.10
\[\[[@&#8203;paolobarbolini](https://redirect.github.com/paolobarbolini)]]
- \[[#&#8203;3643]]: Roll PostgreSQL 11..=15 tests to 13..=17
\[\[[@&#8203;paolobarbolini](https://redirect.github.com/paolobarbolini)]]
- \[[#&#8203;3648]]: close listener connection on TimedOut and
BrokenPipe errors
\[\[[@&#8203;DXist](https://redirect.github.com/DXist)]]
- \[[#&#8203;3649]]: Bump hashbrown to v0.15
\[\[[@&#8203;paolobarbolini](https://redirect.github.com/paolobarbolini)]]

##### Fixed

- \[[#&#8203;3528]]: fix: obey `no-transaction` flag in down migrations
\[\[[@&#8203;manifest](https://redirect.github.com/manifest)]]
- \[[#&#8203;3536]]: fix: using sqlx::test macro inside macro's
\[\[[@&#8203;joeydewaal](https://redirect.github.com/joeydewaal)]]
- \[[#&#8203;3545]]: fix: remove `sqlformat`
\[\[[@&#8203;tbar4](https://redirect.github.com/tbar4)]]
- \[[#&#8203;3558]]: fix: fix example code of `query_as`
\[\[[@&#8203;xuehaonan27](https://redirect.github.com/xuehaonan27)]]
- \[[#&#8203;3566]]: Fix: Cannot query Postgres `INTERVAL[]`
\[\[[@&#8203;Ddystopia](https://redirect.github.com/Ddystopia)]]
- \[[#&#8203;3593]]: fix: URL decode database name when parsing
connection url
\[\[[@&#8203;BenoitRanque](https://redirect.github.com/BenoitRanque)]]
- \[[#&#8203;3601]]: Remove default-features = false from url
\[\[[@&#8203;hsivonen](https://redirect.github.com/hsivonen)]]
- \[[#&#8203;3604]]: Fix mistake in sqlx::test fixtures docs
\[\[[@&#8203;andreweggleston](https://redirect.github.com/andreweggleston)]]
- \[[#&#8203;3612]]: fix(mysql): percent-decode database name
\[\[[@&#8203;abonander](https://redirect.github.com/abonander)]]
- \[[#&#8203;3640]]: Dont use `EXPLAIN` in nullability check for QuestDB
\[\[[@&#8203;Suficio](https://redirect.github.com/Suficio)]]

[#&#8203;3418]: https://redirect.github.com/launchbadge/sqlx/pull/3418

[#&#8203;3478]: https://redirect.github.com/launchbadge/sqlx/pull/3478

[#&#8203;3491]: https://redirect.github.com/launchbadge/sqlx/pull/3491

[#&#8203;3492]: https://redirect.github.com/launchbadge/sqlx/pull/3492

[#&#8203;3493]: https://redirect.github.com/launchbadge/sqlx/pull/3493

[#&#8203;3500]: https://redirect.github.com/launchbadge/sqlx/pull/3500

[#&#8203;3505]: https://redirect.github.com/launchbadge/sqlx/pull/3505

[#&#8203;3507]: https://redirect.github.com/launchbadge/sqlx/pull/3507

[#&#8203;3508]: https://redirect.github.com/launchbadge/sqlx/pull/3508

[#&#8203;3514]: https://redirect.github.com/launchbadge/sqlx/pull/3514

[#&#8203;3519]: https://redirect.github.com/launchbadge/sqlx/pull/3519

[#&#8203;3528]: https://redirect.github.com/launchbadge/sqlx/pull/3528

[#&#8203;3529]: https://redirect.github.com/launchbadge/sqlx/pull/3529

[#&#8203;3536]: https://redirect.github.com/launchbadge/sqlx/pull/3536

[#&#8203;3545]: https://redirect.github.com/launchbadge/sqlx/pull/3545

[#&#8203;3548]: https://redirect.github.com/launchbadge/sqlx/pull/3548

[#&#8203;3550]: https://redirect.github.com/launchbadge/sqlx/pull/3550

[#&#8203;3551]: https://redirect.github.com/launchbadge/sqlx/pull/3551

[#&#8203;3553]: https://redirect.github.com/launchbadge/sqlx/pull/3553

[#&#8203;3558]: https://redirect.github.com/launchbadge/sqlx/pull/3558

[#&#8203;3560]: https://redirect.github.com/launchbadge/sqlx/pull/3560

[#&#8203;3566]: https://redirect.github.com/launchbadge/sqlx/pull/3566

[#&#8203;3577]: https://redirect.github.com/launchbadge/sqlx/pull/3577

[#&#8203;3579]: https://redirect.github.com/launchbadge/sqlx/pull/3579

[#&#8203;3580]: https://redirect.github.com/launchbadge/sqlx/pull/3580

[#&#8203;3583]: https://redirect.github.com/launchbadge/sqlx/pull/3583

[#&#8203;3585]: https://redirect.github.com/launchbadge/sqlx/pull/3585

[#&#8203;3593]: https://redirect.github.com/launchbadge/sqlx/pull/3593

[#&#8203;3596]: https://redirect.github.com/launchbadge/sqlx/pull/3596

[#&#8203;3601]: https://redirect.github.com/launchbadge/sqlx/pull/3601

[#&#8203;3604]: https://redirect.github.com/launchbadge/sqlx/pull/3604

[#&#8203;3605]: https://redirect.github.com/launchbadge/sqlx/pull/3605

[#&#8203;3608]: https://redirect.github.com/launchbadge/sqlx/pull/3608

[#&#8203;3612]: https://redirect.github.com/launchbadge/sqlx/pull/3612

[#&#8203;3623]: https://redirect.github.com/launchbadge/sqlx/pull/3623

[#&#8203;3629]: https://redirect.github.com/launchbadge/sqlx/pull/3629

[#&#8203;3632]: https://redirect.github.com/launchbadge/sqlx/pull/3632

[#&#8203;3640]: https://redirect.github.com/launchbadge/sqlx/pull/3640

[#&#8203;3643]: https://redirect.github.com/launchbadge/sqlx/pull/3643

[#&#8203;3648]: https://redirect.github.com/launchbadge/sqlx/pull/3648

[#&#8203;3649]: https://redirect.github.com/launchbadge/sqlx/pull/3649

[#&#8203;3658]: https://redirect.github.com/launchbadge/sqlx/pull/3658

</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 these
updates again.

---

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

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45Mi4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 10:22:41 +02:00
张小白
79991650af windows: Refactor mouse events related code (#23729)
Release Notes:

- N/A
2025-01-28 15:48:43 +08:00
张小白
e083679e0d windows: Prefer WM_SETTINGCHANGE when handing theme changed events (#23727)
I recently noticed that on my Windows 11 machine, Zed no longer receive
the `WM_DWMCOLORIZATIONCOLORCHANGED` message when the system theme
changes. This functionality was present in the past. While this change
might be unexpected, it's understandable given Microsoft's history of
system updates.

This pull request proposes an alternative approach using the
`WM_SETTINGCHANGE` message to handle theme changes.

Release Notes:

- N/A
2025-01-28 15:47:55 +08:00
Joseph T. Lyons
1b88734c6c Flag issues as stale more aggressively (#23761)
We used to wait 6 months to close stale issues. Jono suggested 1 month.
I'm sort of splitting the difference and adding a bit of buffer. We can
adjust again later on, if we want.

Release Notes:

- N/A
2025-01-28 01:38:15 -05:00
Jason Lee
3eba831de8 Fix closest_index_for_x to get correct offset when only 1 char (#23603)
Release Notes:

- N/A

----------

This bug can easy to replay by `input` example, just enter 1 char and
click on the middle of the char, we can't move cursor to 0, it is always
be 1.

```bash
cargo run -p gpui --example input
```

## Before


https://github.com/user-attachments/assets/3239dd47-278e-4311-9757-5165d1ccd796

## After


https://github.com/user-attachments/assets/4e2c1500-0142-4e28-bf34-7ef1f4929925
2025-01-27 23:18:18 -07:00
CharlesChen0823
02503cf3be vim: Fix NextSubwordEnd crash (#23604)
Closes #23550 

Release Notes:

- N/A
2025-01-27 23:13:13 -07:00
tims
d090caccd7 workspace: Prefer active window over other local non-collab windows for opening file (#23726)
Closes #21625 #17401 #16426

This is how the opening of a file works currently:

1. We first check if the file is part of any existing worktree. If it
is, we focus on that file in the worktree, and the window is activated.
2. If the file is not part of any worktree, we open it in the first
local non-collab workspace we find and activate that window. This is the
bug that the issues are based on.

This PR fixes it by modifying the second part of the above step, where
the file is not part of any worktree. Now, we will first open the file
in the active window, but only if the active window is local and
non-collab. This resolves the issue. If the file can't be opened in the
active window due to the local non-collab check, we will carry out the
existing logic of opening the file in whichever window is local and
non-collab.

I have tested this using the "workspace::OpenFiles" action, and
"workspace::Open" also uses the same method. That is, it will work on
all platforms.

Future: Some users also mentioned there should be a setting for whether
we should open a non-workspace file in the existing window or in a
separate new window. However, this seems out of scope, and a new issue
should be created for this.

#9370 is related, but this likely doesn't fix it, as it deals with how
macOS Finder's "Open with" handles files. I don't have macOS to test,
but this PR won't resolve it if Finder always opens a new window.

Release Notes:

- Fixed the issue where a file outside of the workspace was opening in a
random window instead of the last active window.
2025-01-27 23:11:35 -07:00
Conrad Irwin
7deafdafae Fix run indicators with expanded diff hunks (#23758)
Fix runnable positioning when diff hunks are altered.

Release Notes:

- N/A
2025-01-27 23:05:46 -07:00
Max Brunsfeld
ee5f270f3f Fix unnecessarily large edits emitted from multi buffer on diff recalculation (#23753)
This fixes an issue introduced in #22994 where soft wrap would
recalculate for the entire buffer when editing.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
2025-01-27 18:11:15 -08:00
Danilo Leal
5331418f3a pane: Add settings to hide the tab bar buttons (#23752)
This PR adds the `show_tab_bar_buttons` under `tab_bar` that allows
hiding the "New", "Split Pane", and "Zoom" buttons to the left of the
pane tab bar.

Release Notes:

- Added a new `show_tab_bar_buttons` setting, under `tab_bar`, that
enables hiding the pane tab bar buttons.
2025-01-27 21:36:33 -03:00
Danilo Leal
7a6223e71b assistant2: Add tiny visual adjustments (#23748)
This PR adds really tiny visual adjustments to the assistant 2. I guess
the most note-worthy thing here is that I separated the `title` for
History views into two just because I wanted to render the `/` smaller
and lighter. 😬

Release Notes:

- N/A
2025-01-27 20:26:34 -03:00
Joseph T. Lyons
06424c9608 Update actions to open GitHub issue templates (#23747)
Release Notes:

- N/A
2025-01-27 18:17:43 -05:00
Peter Tripp
15933d478f New Github Issue Templates v2 (#23746) 2025-01-27 17:45:53 -05:00
Peter Tripp
82b81ed6d6 New GitHub Issue Templates (#23745)
Co-authored-by: Joseph T. Lyons <joseph@zed.dev>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
2025-01-27 17:38:29 -05:00
Joseph T. Lyons
23b92e3057 Remove issue automation (#23743)
Release Notes:

- N/A
2025-01-27 22:12:26 +00:00
Cole Miller
27d57ba3b6 git: First stab at adding Linux and Vim keybindings (#23738)
Release Notes:

- N/A
2025-01-27 16:58:09 -05:00
Mikayla Maki
a7c549b85b Fix window double borrows (#23739)
Fix bugs caused by the window context PR, where the window could be on
the stack and is then requested from the App.
This PR also adds derive macros for `AppContext` and `VisualContext` so
that it's easy to define further contexts in API code, such as
`editor::BlockContext`.

Release Notes:

- N/A
2025-01-27 21:56:29 +00:00
邻二氮杂菲
29bfb56739 Add DeepSeek support (#23551)
- Added support for DeepSeek as a new language model provider in Zed
Assistant
- Implemented streaming API support for real-time responses from
DeepSeek models.
- Added a configuration UI for DeepSeek API key management and settings.
- Updated documentation with detailed setup instructions for DeepSeek
integration.
- Added DeepSeek-specific icons and model definitions for seamless
integration into the Zed UI.
- Integrated DeepSeek into the language model registry, making it
available alongside other providers like OpenAI and Anthropic.

Release Notes:

- Added support for DeepSeek to the Assistant.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-01-27 13:40:59 -05:00
Marshall Bowers
f096a28a19 assistant2: Don't block ThreadStore initialization on reloading the threads (#23728)
This PR changes the `ThreadStore` constructor to not block on reloading
the threads before we finish initializing it.

This allows us to make the constructor synchronous instead of
asynchronous.

Release Notes:

- N/A
2025-01-27 12:59:28 -05:00
Marshall Bowers
9705764892 assistant2: Fix opening the configuration via the button (#23732)
This PR fixes an issues where clicking the "Open Configuration" button
wasn't opening the configuration.

We needed to change how the action was dispatched after #22632.

Release Notes:

- N/A
2025-01-27 17:47:18 +00:00
João Marcos
2256c21841 Silence Rust-Analyzer false-positive (#23724)
Release Notes:

- N/A
2025-01-27 16:38:03 +00:00
Marshall Bowers
ad49f71e6e assistant2: Add debug logging for initialization issues (#23722)
This PR adds some logging so we can debug the issues some folks have
been having with Assistant2 not getting initialized properly.

All the logs are prefixed with `[assistant2-debug]` so they're easier to
pick out of the logs, as well as find them later to clean up once we've
diagnosed the issue.

Release Notes:

- N/A
2025-01-27 16:27:59 +00:00
Danilo Leal
98ea0587df assistant: Preserve selection focus in the model selector (#23713)
This PR fixes an incorrect behavior in the model selector where we were
removing the focus from the selected item back to the very first item of
the list. Now, if you click/hit return on an item, focus is preserved
there. In other words, focus is always initially in the selected item.

### Before

https://github.com/user-attachments/assets/62b72b1f-4e32-4b4a-adff-dcf9a2c13a28

### After

https://github.com/user-attachments/assets/a8528933-da01-481a-96f3-0173a39a03c0

Release Notes:

- N/A
2025-01-27 12:29:08 -03:00
Danilo Leal
93b62e0ed4 assistant: Fix model selector label shift (#23717)
This PR caps the width of the model label in the selector trigger to a
certain size. This is fix the behavior of the popover dancing around,
given its popover position is anchored to a certain edge of the trigger,
and if the trigger size increases while you select different models with
different name lengths, the popover dances around.

### Before


https://github.com/user-attachments/assets/0854fa2b-9eb2-45fb-886d-bde1cd644dcf

### After

Note how even though the second item has the largest label, the popover
stays in place.


https://github.com/user-attachments/assets/06b60030-65dc-4f06-b486-3045042bbff0

Fixing that then means truncating the model name to keep it constrained
into a max-width.

<img width="500" alt="Screenshot 2025-01-27 at 11 38 14 AM"
src="https://github.com/user-attachments/assets/94ce9cc6-848c-4dac-86b8-321da75c3af3"
/>

Release Notes:

- N/A
2025-01-27 12:28:58 -03:00
Danilo Leal
91f44725d9 assistant2: Fix model selector position (#23721)
This PR makes the model selector not render on top of its trigger.

<img width="800" alt="Screenshot 2025-01-27 at 12 02 01 PM"
src="https://github.com/user-attachments/assets/76c5ac6f-ae27-4b3d-a80a-3027042c75f8"
/>

Release Notes:

- N/A
2025-01-27 12:28:45 -03:00
Peter Tripp
b58c994706 docs: Example OpenAI model config missing version key (#23720) 2025-01-27 15:17:51 +00:00
Andrew Borg (Kashin)
9f3dbd6fa3 Make ZED_WORKTREE_ROOT always point to a directory or is not set (#23150)
When the out-of-tree file like `tasks.json` is-focus, tasks spawn with `ZED_WORKTREE_ROOT` unset.
2025-01-27 10:10:39 -05:00
Peter Tripp
33921362cf docs: Fix error in OpenAI configuring custom models (#23716) 2025-01-27 14:47:09 +00:00
Peter Tripp
f6d286c7db openai: Add back O1-Preview (#23715)
Follow-up to: https://github.com/zed-industries/zed/pull/23425
2025-01-27 14:44:12 +00:00
Kirill Bulatov
5bde053b0d Avoid panics when normalizing completion label with invalid ranges (#23712)
Dev builds show panics related to completion label normalization

<details>
<summary>Panic</summary>

```
index out of bounds: the len is 103 but the index is 103
zed::reliability::init_panic_hook::{{closure}}::h78130eff43c84f6f+110375521
std::panicking::rust_panic_with_hook::hfe205f6954b2c97b+87457752
std::panicking::begin_panic_handler::{{closure}}::h6cb44b3a50f28c44+87456967
std::sys::backtrace::__rust_end_short_backtrace::hf1c1f2a92799bb0e+87449337
rust_begin_unwind+87456084
core::panicking::panic_fmt::h3d8fc78294164da7+7033011
core::panicking::panic_bounds_check::h9397cb495d89a72d+7033511
project::lsp_store::ensure_uniform_list_compatible_label::haf80316ce11edd67+72663592
project::lsp_store::populate_labels_for_completions::{{closure}}::hc93c3c540ef7d2d6+72642960
project::lsp_store::LspStore::completions::{{closure}}::{{closure}}::hb4b5432e24432ca8+72336627
async_task::raw::RawTask<F,T,S,M>::run::hf444c3dc07dd583b+68504803
<gpui::platform::linux::wayland::client::WaylandClient as gpui::platform::linux::platform::LinuxClient>::run::hbf5a316eb781a10d+50646579
gpui::platform::linux::platform::<impl gpui::platform::Platform for P>::run::hc85518d4552fc4cd+50496669
gpui::app::App::run::hca4e2eaf984ca6f6+109905269
zed::main::h849467ac1a6d32c9+110413414
std::sys::backtrace::__rust_begin_short_backtrace::h81b5ee155a7cf505+110835475
std::rt::lang_start::{{closure}}::h48a83f884cfb6865+110834761
std::rt::lang_start_internal::h5e7c81cecd7f0954+87382485
main+110425932
__libc_start_call_main+22789462491720
__libc_start_main_alias_1+22789462491915
_start+10436606
```
</details>

This can only happen when either `label.runs` or `label.filter_range`
has a range that's larger than the label text, which is an error.
Instead of panicking, log such errors and fall back to last index (which
is not really helpful, but still).

Release Notes:

- N/A
2025-01-27 14:18:39 +00:00
Matin Aniss
06d00b940d gpui: Support windows dark mode title bar (#23700)
This will allow Windows GPUI applications that utilise the native title
bar to support the dark mode variant when the system user has dark mode
enabled in their system settings. [Related Win32
information](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/ui/apply-windows-themes)

| Before  | After |
| ------------- | ------------- |
|
![image](https://github.com/user-attachments/assets/50f3d131-8f41-4b91-8012-f8120b150033)
|
![image](https://github.com/user-attachments/assets/a36a6c6b-a469-49ba-85a8-9b55db9ea20f)
|

Release Notes:

- N/A

---------

Co-authored-by: 张小白 <364772080@qq.com>
2025-01-27 20:07:28 +08:00
loczek
6293b20fd0 Add auto-completion support for snippet files (#23698)
Release Notes:

- Added auto-completion support for snippet files.


![image](https://github.com/user-attachments/assets/ad165fc7-a6e7-426c-8892-f7004515dfc7)
2025-01-27 12:32:22 +01:00
Michael Sloan
6e9ea47849 Fix completions menu scroll when y_flipped and edit prediction arrives (#23580)
Release Notes:

- N/A
2025-01-27 00:36:21 -07:00
Mikayla Maki
9cae96f82f Remove more references to 'model' in GPUI APIs (#23693)
Release Notes:

- N/A
2025-01-27 04:00:27 +00:00
Mikayla Maki
a6b1514246 Fix missed renames in #22632 (#23688)
Fix a bug where a GPUI macro still used `ModelContext`
Rename `AsyncAppContext` -> `AsyncApp`
Rename update_model, read_model, insert_model, and reserve_model to
update_entity, read_entity, insert_entity, and reserve_entity

Release Notes:

- N/A
2025-01-26 23:37:34 +00:00
Michael Sloan
83141d07e9 Fix escaping of alt-\ in docs for triggering inline completion (#23684)
Release Notes:

- N/A
2025-01-26 20:26:11 +00:00
Kirill Bulatov
7d30dda557 Restore the default release channel (#23671)
Release Notes:

- N/A
2025-01-26 09:19:24 +00:00
Joseph T. Lyons
0c8ee21e22 Clean up duplicate dock-retrieval code (#23670)
Release Notes:

- N/A
2025-01-26 09:11:38 +00:00
Maksim Bondarenkov
64a5153bb5 collab: Make unsupported for MinGW toolchain (#23518)
Closes #23451

reverts #23117 for MinGW. collab can't be compiled for MinGW because
webrtc itself doesn't support MinGW compilers

Release Notes:

- N/A
2025-01-26 11:01:20 +02:00
张小白
5d005a7621 Canonicalize paths when running tests (#23655)
In the Windows test environment, the paths generated by `temp_tree()`
are symlink paths, which causes certain tests to fail.

I later noticed that when opening a project, we seem to always use
`canonicalize` to normalize the paths, as shown here:
https://github.com/zed-industries/zed/pull/21039.

This PR adopts a similar approach for the test environment to address
the issue.

Release Notes:

- N/A
2025-01-26 14:56:07 +08:00
Michael Sloan
0f8e2e3811 Remove scripts used in merging the refactoring (#23669)
Release Notes:

- N/A
2025-01-26 06:33:11 +00:00
Michael Sloan
84b945e89d Revert making keybinding display in Mac menus use standard precedence (#23661)
Closes #23621

Change was in #23378. Also adds a comment to clarify why this is
inconsistent with all other uses of `bindings_for_action`.

Release Notes:

- N/A
2025-01-26 04:35:01 +00:00
Nathan Sobo
6fca1d2b0b Eliminate GPUI View, ViewContext, and WindowContext types (#22632)
There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
2025-01-26 03:02:45 +00:00
Joseph T. Lyons
21b4a0d50e Adjust editor: open excerpts split key binding (#23646)
The pattern in Zed and in other editors is to use `cmd` to modify some
file-opening action to open it in a split rather than in the current
pane.

- In the project pane, a `click` opens a file, and a `cmd-click` opens
it in a split
- In the file finder, `enter` opens the file, and a `cmd-enter` opens it
in a split

It makes sense to me that if `alt-enter` opens a file from the excerpt,
that `cmd-alt-enter` opens it in a split, following the pattern above.

Note: I'm not auto-merging this, as others might disagree.
Note: I didn't touch the Vim binding.

Release Notes:

- Breaking Change: Changed `editor: open excerpts split` key binding to
`cmd-alt-enter` on macOS and `ctrl-alt-enter` on Linux.
2025-01-25 17:06:53 -05:00
Kirill Bulatov
da2bd4b8e9 Rework go to line infrastructure (#23654)
Closes https://github.com/zed-industries/zed/issues/12024


https://github.com/user-attachments/assets/60ea3dbd-b594-4bf5-a44d-4bff925b815f

* Fixes incorrect line selection for certain corner cases

Before:

<img width="1728" alt="image"
src="https://github.com/user-attachments/assets/35aaee6c-c120-4bf1-9355-448a29d1b9b5"
/>

After:

<img width="1728" alt="image"
src="https://github.com/user-attachments/assets/abd97339-4594-4e8e-8605-50d74581ae86"
/>


* Reworks https://github.com/zed-industries/zed/pull/16420 to display
selection length with less performance overhead.
Improves the performance more, doing a single selections loop instead of
two.

* Fixes incorrect caret position display when text contains UTF-8 chars
with size > 1
Also fixes tooltop values for this case

* Fixes go to line to treat UTF-8 chars with size > 1 properly when
navigating

* Adds a way to fill go to line text editor with its tooltip on `Tab`

Release Notes:

- Fixed incorrect UTF-8 characters handling in `GoToLine` and caret
position
2025-01-25 19:24:19 +00:00
Joseph T. Lyons
7c0a39daa6 Add key binding for editor: open selections in multibuffer (#23648)
Follow-up to: https://github.com/zed-industries/zed/pull/23644

- Existing: `alt-enter` to open files from multi-buffer selections
- New: `alt-enter` to open multi-buffer from file selections

I updated the original PR changelog line.

Release Notes:

- N/A
2025-01-25 10:27:14 +00:00
Kirill Bulatov
392b95b179 Use proper path separator for multi buffer headers (#23635)
Before: 


![bad](https://github.com/user-attachments/assets/ab20836a-b3bb-4b33-8ce4-eecc79d5f02f)


After:


![good](https://github.com/user-attachments/assets/40c328d1-2ef1-4a9b-9de9-9be9bba26756)


Release Notes:

- N/A
2025-01-25 10:35:22 +02:00
Michael Sloan
5f417eda25 Fix repl "plain" terminals enqueing events without ever dequeuing (#23641)
Bringing back part of #23631 which was reverted in #23636. Different but
related memory leak to what's described in #23008

Release Notes:

- N/A
2025-01-25 01:08:34 -07:00
Joseph T. Lyons
75b507d38a Open selections in multi buffer (#23644)
Closes https://github.com/zed-industries/zed/issues/5126

Release Notes:

- Added an `editor: open selections in multibuffer` command.
2025-01-25 07:45:09 +00:00
Kirill Bulatov
f5102838f3 Revert terminal memory leak fixes (#23636)
New mechanism had introduced the following regressions:

* Windows tasks are not registering child exit properly, sometimes
getting stuck in dirty state (even with
https://github.com/zed-industries/zed/pull/23631):


https://github.com/user-attachments/assets/6d406f17-aa76-4012-9c3b-be72d6d5beae

* Overall, the terminal editing started to feel more sluggish, esp. in
regards to deletions (ctrl-w and backspace), tested on macOS:


https://github.com/user-attachments/assets/3a69fe2e-e394-45e8-8f51-0f5ac396cb24


Release Notes:

- N/A
2025-01-24 21:35:24 -07:00
799 changed files with 46115 additions and 33334 deletions

View File

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

View File

@@ -1,54 +1,34 @@
name: Bug Report
description: |
Use this template for **non-crash-related** bug reports.
Tip: open this issue template from within Zed with the `file bug report` command palette action.
labels: ["admin read", "triage", "bug"]
Something is broken in Zed (exclude crashing).
type: "Bug"
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `file bug report` and this field will be autopopulated -->
<!-- If Zed won't launch, include the equivalent with other relevant details (e.g. video card driver version for display bugs, etc) -->
<!-- Zed Version: 0.xxx.x; Channel: Stable, OS: macOS xx.xx, RAM: XXGB, Architecture: x86_64"
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, add screenshots or screencasts of the incorrect state / behavior
description: Drag images / videos into the text input below
validations:
required: false
- type: textarea
attributes:
label: If applicable, attach your Zed.log file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary>
<!-- Click below this line and paste or drag-and-drop your log-->
```
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

View File

@@ -1,26 +1,33 @@
name: Crash Report
description: |
Use this template for crash reports.
labels: ["admin read", "triage", "bug", "panic / crash"]
description: Zed is Crashing or Hanging
type: "Crash"
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true
- type: textarea

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

375
Cargo.lock generated
View File

@@ -506,14 +506,12 @@ dependencies = [
"assistant_settings",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_tool",
"chrono",
"client",
"clock",
"collections",
"context_server",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -560,6 +558,7 @@ version = "0.1.0"
dependencies = [
"anthropic",
"anyhow",
"deepseek",
"feature_flags",
"fs",
"gpui",
@@ -636,6 +635,7 @@ dependencies = [
"ui",
"util",
"workspace",
"worktree",
]
[[package]]
@@ -1198,9 +1198,9 @@ dependencies = [
[[package]]
name = "aws-config"
version = "1.5.14"
version = "1.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f40e82e858e02445402906e454a73e244c7f501fcae198977585946c48e8697"
checksum = "dc47e70fc35d054c8fcd296d47a61711f043ac80534a10b4f741904f81e73a90"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1266,9 +1266,9 @@ dependencies = [
[[package]]
name = "aws-sdk-kinesis"
version = "1.56.0"
version = "1.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d9c9144b6b00173d8f212a89d6bb252d48f88aeb2ae89c33c13b0a0fcd0ac9"
checksum = "7963cf7a0f49ba4f8351044751f4d42c003c4a5f31d9e084f0d0e68b6fb8b8cf"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1288,9 +1288,9 @@ dependencies = [
[[package]]
name = "aws-sdk-s3"
version = "1.69.0"
version = "1.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a88f1c30e4ffa2464f910297c24736ff68cca9e8d2b7d52596b54efd99b9c1e"
checksum = "1c7ce6d85596c4bcb3aba8ad5bb134b08e204c8a475c9999c1af9290f80aa8ad"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1322,9 +1322,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.54.0"
version = "1.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "921a13ed6aabe2d1258f65ef7804946255c799224440774c30e1a2c65cdf983a"
checksum = "c54bab121fe1881a74c338c5f723d1592bf3b53167f80268a1274f404e1acc38"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1344,9 +1344,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.55.0"
version = "1.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196c952738b05dfc917d82a3e9b5ba850822a6d6a86d677afda2a156cc172ceb"
checksum = "8c8234fd024f7ac61c4e44ea008029bde934250f371efe7d4a39708397b1080c"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1366,9 +1366,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.55.0"
version = "1.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ef5b73a927ed80b44096f8c20fb4abae65469af15198367e179ae267256e9d"
checksum = "ba60e1d519d6f23a9df712c04fdeadd7872ac911c84b2f62a8bda92e129b7962"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1837,7 +1837,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.6.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [
"ash",
"ash-window",
@@ -1869,7 +1869,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [
"proc-macro2",
"quote",
@@ -1879,7 +1879,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.2.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -2182,7 +2182,7 @@ dependencies = [
"cap-primitives",
"cap-std",
"io-lifetimes",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -2210,7 +2210,7 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
"winx",
]
@@ -2718,7 +2718,6 @@ dependencies = [
"envy",
"extension",
"file_finder",
"fireworks",
"fs",
"futures 0.3.31",
"git",
@@ -3685,6 +3684,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "deepseek"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.31",
"http_client",
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "deflate64"
version = "0.1.9"
@@ -4009,7 +4020,7 @@ dependencies = [
"util",
"uuid",
"workspace",
"zed_predict_tos",
"zed_predict_onboarding",
]
[[package]]
@@ -4209,7 +4220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -4645,17 +4656,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "fireworks"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.31",
"http_client",
"serde",
"serde_json",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@@ -4684,6 +4684,12 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
[[package]]
name = "float_next_after"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
[[package]]
name = "flume"
version = "0.11.1"
@@ -4852,7 +4858,7 @@ dependencies = [
"gpui",
"libc",
"log",
"notify",
"notify 6.1.1",
"objc",
"parking_lot",
"paths",
@@ -4876,7 +4882,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
dependencies = [
"io-lifetimes",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -5149,6 +5155,18 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
]
[[package]]
name = "gif"
version = "0.13.1"
@@ -5245,7 +5263,6 @@ dependencies = [
"futures 0.3.31",
"git",
"gpui",
"language",
"menu",
"picker",
"project",
@@ -5413,8 +5430,10 @@ dependencies = [
"itertools 0.14.0",
"linkme",
"log",
"lyon",
"media",
"metal",
"naga",
"num_cpus",
"objc",
"objc2",
@@ -5465,11 +5484,21 @@ dependencies = [
name = "gpui_macros"
version = "0.1.0"
dependencies = [
"gpui",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "gpui_tokio"
version = "0.1.0"
dependencies = [
"gpui",
"tokio",
"util",
]
[[package]]
name = "grid"
version = "0.13.0"
@@ -5615,11 +5644,11 @@ dependencies = [
[[package]]
name = "hashlink"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.14.5",
"hashbrown 0.15.2",
]
[[package]]
@@ -6374,7 +6403,7 @@ dependencies = [
"ui",
"workspace",
"zed_actions",
"zed_predict_tos",
"zed_predict_onboarding",
"zeta",
]
@@ -6389,6 +6418,17 @@ dependencies = [
"libc",
]
[[package]]
name = "inotify"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.8.0",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
@@ -6445,7 +6485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
dependencies = [
"io-lifetimes",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -6808,6 +6848,7 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"collections",
"deepseek",
"futures 0.3.31",
"google_ai",
"gpui",
@@ -6851,6 +6892,7 @@ dependencies = [
"client",
"collections",
"copilot",
"deepseek",
"editor",
"feature_flags",
"fs",
@@ -6956,6 +6998,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"snippet_provider",
"task",
"text",
"theme",
@@ -7412,6 +7455,69 @@ dependencies = [
"url",
]
[[package]]
name = "lyon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f"
dependencies = [
"lyon_algorithms",
"lyon_extra",
"lyon_tessellation",
]
[[package]]
name = "lyon_algorithms"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08"
dependencies = [
"lyon_path",
"num-traits",
]
[[package]]
name = "lyon_extra"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca94c7bf1e2557c2798989c43416822c12fc5dcc5e17cc3307ef0e71894a955"
dependencies = [
"lyon_path",
"thiserror 1.0.69",
]
[[package]]
name = "lyon_geom"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
dependencies = [
"arrayvec",
"euclid",
"num-traits",
]
[[package]]
name = "lyon_path"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e"
dependencies = [
"lyon_geom",
"num-traits",
]
[[package]]
name = "lyon_tessellation"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c"
dependencies = [
"float_next_after",
"lyon_path",
"num-traits",
]
[[package]]
name = "mac"
version = "0.1.1"
@@ -7553,9 +7659,9 @@ dependencies = [
[[package]]
name = "mdbook"
version = "0.4.43"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe1f98b8d66e537d2f0ba06e7dec4f44001deec539a2d18bfc102d6a86189148"
checksum = "f9da1e54401fe5d45a664c57e112e70f18e8c5a73e268c179305b932ee864574"
dependencies = [
"ammonia",
"anyhow",
@@ -7569,7 +7675,7 @@ dependencies = [
"ignore",
"log",
"memchr",
"notify",
"notify 8.0.0",
"notify-debouncer-mini",
"once_cell",
"opener",
@@ -7985,7 +8091,7 @@ dependencies = [
"crossbeam-channel",
"filetime",
"fsevent-sys 4.1.0",
"inotify",
"inotify 0.9.6",
"kqueue",
"libc",
"log",
@@ -7995,16 +8101,42 @@ dependencies = [
]
[[package]]
name = "notify-debouncer-mini"
version = "0.4.1"
name = "notify"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
dependencies = [
"crossbeam-channel",
"bitflags 2.8.0",
"filetime",
"fsevent-sys 4.1.0",
"inotify 0.11.0",
"kqueue",
"libc",
"log",
"notify",
"mio 1.0.3",
"notify-types",
"walkdir",
"windows-sys 0.59.0",
]
[[package]]
name = "notify-debouncer-mini"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a689eb4262184d9a1727f9087cd03883ea716682ab03ed24efec57d7716dccb8"
dependencies = [
"log",
"notify 8.0.0",
"notify-types",
"tempfile",
]
[[package]]
name = "notify-types"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]]
name = "ntapi"
version = "0.4.1"
@@ -10240,7 +10372,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -10622,6 +10754,7 @@ dependencies = [
"git",
"git_hosting_providers",
"gpui",
"gpui_tokio",
"http_client",
"language",
"language_extension",
@@ -11125,7 +11258,7 @@ dependencies = [
"libc",
"linux-raw-sys",
"once_cell",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -11293,6 +11426,19 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "schema_generator"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger 0.11.6",
"schemars",
"serde",
"serde_json",
"theme",
]
[[package]]
name = "schemars"
version = "0.8.21"
@@ -11634,9 +11780,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.137"
version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [
"indexmap",
"itoa",
@@ -12059,8 +12205,9 @@ dependencies = [
"gpui",
"parking_lot",
"paths",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
"snippet",
"util",
]
@@ -12180,9 +12327,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -12193,32 +12340,27 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
dependencies = [
"atoi",
"bigdecimal",
"byteorder",
"bytes 1.9.0",
"chrono",
"crc",
"crossbeam-queue",
"either",
"event-listener 5.3.1",
"futures-channel",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.14.5",
"hashlink 0.9.1",
"hex",
"hashbrown 0.15.2",
"hashlink 0.10.0",
"indexmap",
"log",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"rust_decimal",
"rustls 0.23.20",
@@ -12227,8 +12369,7 @@ dependencies = [
"serde_json",
"sha2",
"smallvec",
"sqlformat",
"thiserror 1.0.69",
"thiserror 2.0.6",
"time",
"tokio",
"tokio-stream",
@@ -12240,9 +12381,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
dependencies = [
"proc-macro2",
"quote",
@@ -12253,9 +12394,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
dependencies = [
"dotenvy",
"either",
@@ -12279,9 +12420,9 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -12317,7 +12458,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 1.0.69",
"thiserror 2.0.6",
"time",
"tracing",
"uuid",
@@ -12326,9 +12467,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -12341,7 +12482,6 @@ dependencies = [
"etcetera",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"hex",
"hkdf",
@@ -12361,7 +12501,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 1.0.69",
"thiserror 2.0.6",
"time",
"tracing",
"uuid",
@@ -12370,9 +12510,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
dependencies = [
"atoi",
"chrono",
@@ -12840,7 +12980,7 @@ dependencies = [
"fd-lock",
"io-lifetimes",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
"winx",
]
@@ -12965,16 +13105,16 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.15.0"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [
"cfg-if",
"fastrand 2.3.0",
"getrandom 0.2.15",
"getrandom 0.3.1",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -13139,7 +13279,6 @@ dependencies = [
"log",
"palette",
"rust-embed",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
@@ -13401,6 +13540,7 @@ dependencies = [
"windows 0.58.0",
"workspace",
"zed_actions",
"zed_predict_onboarding",
]
[[package]]
@@ -13919,9 +14059,9 @@ dependencies = [
[[package]]
name = "tree-sitter-regex"
version = "0.23.0"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9a7087b1cf769c96b7e74414947df067fb6135f04d176fd23be08b9396cc0e"
checksum = "712656f8c262a5a4b7d6026e6246950787d178d613864952554e1516a33ab0c1"
dependencies = [
"cc",
"tree-sitter-language",
@@ -14187,9 +14327,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unindent"
version = "0.1.11"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
[[package]]
name = "untrusted"
@@ -14291,9 +14431,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.11.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom 0.2.15",
"serde",
@@ -14400,6 +14540,7 @@ dependencies = [
"command_palette_hooks",
"editor",
"futures 0.3.31",
"git_ui",
"gpui",
"indoc",
"itertools 0.14.0",
@@ -14551,6 +14692,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt 0.33.0",
]
[[package]]
name = "wasite"
version = "0.1.0"
@@ -15722,7 +15872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
dependencies = [
"bitflags 2.8.0",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -15741,7 +15891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
dependencies = [
"bitflags 2.8.0",
"wit-bindgen-rt",
"wit-bindgen-rt 0.22.0",
"wit-bindgen-rust-macro",
]
@@ -15761,6 +15911,15 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.8.0",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.22.0"
@@ -16274,7 +16433,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.172.0"
version = "0.173.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16320,6 +16479,7 @@ dependencies = [
"git_ui",
"go_to_line",
"gpui",
"gpui_tokio",
"http_client",
"image_viewer",
"inline_completion_button",
@@ -16397,7 +16557,7 @@ dependencies = [
"winresource",
"workspace",
"zed_actions",
"zed_predict_tos",
"zed_predict_onboarding",
"zeta",
]
@@ -16512,23 +16672,24 @@ dependencies = [
]
[[package]]
name = "zed_predict_tos"
name = "zed_predict_onboarding"
version = "0.1.0"
dependencies = [
"chrono",
"client",
"db",
"feature_flags",
"fs",
"gpui",
"language",
"menu",
"settings",
"theme",
"ui",
"util",
"workspace",
]
[[package]]
name = "zed_prisma"
version = "0.0.4"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_proto"
version = "0.2.1"
@@ -16717,9 +16878,12 @@ dependencies = [
"client",
"clock",
"collections",
"command_palette_hooks",
"ctor",
"db",
"editor",
"env_logger 0.11.6",
"feature_flags",
"futures 0.3.31",
"gpui",
"http_client",
@@ -16731,6 +16895,7 @@ dependencies = [
"menu",
"reqwest_client",
"rpc",
"serde",
"serde_json",
"settings",
"similar",

View File

@@ -2,7 +2,6 @@
resolver = "2"
members = [
"crates/activity_indicator",
"crates/zed_predict_tos",
"crates/anthropic",
"crates/assets",
"crates/assistant",
@@ -31,6 +30,7 @@ members = [
"crates/context_server_settings",
"crates/copilot",
"crates/db",
"crates/deepseek",
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
@@ -44,16 +44,17 @@ members = [
"crates/feedback",
"crates/file_finder",
"crates/file_icons",
"crates/fireworks",
"crates/fs",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
"crates/git_hosting_providers",
"crates/git_ui",
"crates/go_to_line",
"crates/google_ai",
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/image_viewer",
@@ -102,9 +103,11 @@ members = [
"crates/remote_server",
"crates/repl",
"crates/reqwest_client",
"crates/reqwest_client",
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/schema_generator",
"crates/search",
"crates/semantic_index",
"crates/semantic_version",
@@ -140,7 +143,6 @@ members = [
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/reqwest_client",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -150,8 +152,8 @@ members = [
"crates/worktree",
"crates/zed",
"crates/zed_actions",
"crates/zed_predict_onboarding",
"crates/zeta",
"crates/git_ui",
#
# Extensions
@@ -168,7 +170,6 @@ members = [
"extensions/lua",
"extensions/php",
"extensions/perplexity",
"extensions/prisma",
"extensions/proto",
"extensions/purescript",
"extensions/ruff",
@@ -200,7 +201,6 @@ edition = "2021"
activity_indicator = { path = "crates/activity_indicator" }
ai = { path = "crates/ai" }
zed_predict_tos = { path = "crates/zed_predict_tos" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
@@ -229,6 +229,7 @@ context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -238,7 +239,6 @@ feature_flags = { path = "crates/feature_flags" }
feedback = { path = "crates/feedback" }
file_finder = { path = "crates/file_finder" }
file_icons = { path = "crates/file_icons" }
fireworks = { path = "crates/fireworks" }
fs = { path = "crates/fs" }
fsevent = { path = "crates/fsevent" }
fuzzy = { path = "crates/fuzzy" }
@@ -251,6 +251,7 @@ gpui = { path = "crates/gpui", default-features = false, features = [
"http_client",
] }
gpui_macros = { path = "crates/gpui_macros" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
image_viewer = { path = "crates/image_viewer" }
@@ -347,6 +348,7 @@ workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
zed_predict_onboarding = { path = "crates/zed_predict_onboarding" }
zeta = { path = "crates/zeta" }
#
@@ -373,9 +375,10 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
naga = { version = "23.1.0", features = ["wgsl-in"] }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -522,13 +525,13 @@ tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.23"
tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unindent = "0.1.7"
unindent = "0.2.0"
unicode-segmentation = "1.10"
unicode-script = "0.5.7"
url = "2.2"

View File

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

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -42,6 +42,12 @@
"elm": "elm",
"erl": "erlang",
"escript": "erlang",
"eslint.config.cjs": "eslint",
"eslint.config.cts": "eslint",
"eslint.config.js": "eslint",
"eslint.config.mjs": "eslint",
"eslint.config.mts": "eslint",
"eslint.config.ts": "eslint",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",

View File

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

Before

Width:  |  Height:  |  Size: 334 B

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1,19 @@
<svg width="420" height="128" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="tilePattern" width="22" height="22" patternUnits="userSpaceOnUse">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
</svg>
</pattern>
<linearGradient id="fade" y2="1" x2="0">
<stop offset="0" stop-color="white" stop-opacity=".24"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<mask id="fadeMask" maskContentUnits="objectBoundingBox">
<rect width="1" height="1" fill="url(#fade)"/>
</mask>
</defs>
<rect width="100%" height="100%" fill="url(#tilePattern)" mask="url(#fadeMask)"/>
</svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@@ -136,11 +136,12 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
"ctrl-h": "buffer_search::DeployReplace",
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
}
},
{
@@ -425,6 +426,7 @@
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-shift-g": "git_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"alt-save": "workspace::SaveAll",
"ctrl-alt-s": "workspace::SaveAll",
@@ -543,7 +545,7 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit",
"ctrl-alt-enter": "editor::OpenExcerptsSplit",
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
@@ -645,7 +647,7 @@
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit"
"ctrl-alt-enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -691,6 +693,34 @@
"space": "project_panel::Open"
}
},
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrev",
"down": "menu::SelectNext",
"enter": "menu::Confirm",
"space": "git::ToggleStaged",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll"
}
},
{
"context": "GitPanel && CommitEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::FocusChanges",
"ctrl-enter": "git::CommitChanges",
"ctrl-shift-enter": "git::CommitAllChanges"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -768,6 +798,8 @@
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
// Overrides for conflicting keybindings
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
"ctrl-shift-a": "editor::SelectAll",
@@ -791,5 +823,12 @@
"shift-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode"
}
},
{
"context": "ZedPredictModal",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel"
}
}
]

View File

@@ -145,12 +145,13 @@
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-k z": "editor::ToggleSoftWrap",
"cmd-f": "buffer_search::Deploy",
"cmd-alt-f": ["buffer_search::Deploy", { "replace_enabled": true }],
"cmd-alt-f": "buffer_search::DeployReplace",
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol"
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
}
},
{
@@ -621,7 +622,7 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit",
"cmd-alt-enter": "editor::OpenExcerptsSplit",
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
@@ -668,7 +669,7 @@
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit"
"cmd-alt-enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -833,6 +834,8 @@
// Terminal.app compatibility
"alt-left": ["terminal::SendText", "\u001bb"],
"alt-right": ["terminal::SendText", "\u001bf"],
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
// There are conflicting bindings for these keys in the global context.
// these bindings override them, remove at your own risk:
"up": ["terminal::SendKeystroke", "up"],
@@ -880,7 +883,7 @@
}
},
{
"context": "ZedPredictTos",
"context": "ZedPredictModal",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel"

View File

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

View File

@@ -408,6 +408,7 @@
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
// "b": "vim::AnyBrackets",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
@@ -668,5 +669,20 @@
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"k": "menu::SelectPrev",
"j": "menu::SelectNext",
"g g": "menu::SelectFirst",
"shift-g": "menu::SelectLast",
"g f": "menu::Confirm",
"i": "git_panel::FocusEditor",
"x": "git::ToggleStaged",
"shift-x": "git::StageAll",
"shift-u": "git::UnstageAll"
}
}
]

View File

@@ -10,8 +10,9 @@
"light": "One Light",
"dark": "One Dark"
},
"icon_theme": "Zed (Default)",
// The name of a base set of key bindings to use.
// This setting can take four values, each named after another
// This setting can take six values, each named after another
// text editor:
//
// 1. "VSCode"
@@ -202,7 +203,7 @@
// Example: ["string", "comment"]
"inline_completions_disabled_in": [],
// Whether to show tabs and spaces in the editor.
// This setting can take three values:
// This setting can take four values:
//
// 1. Draw tabs and spaces only for the selected text (default):
// "selection"
@@ -392,7 +393,7 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take four values:
/// This setting can take five values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
@@ -464,7 +465,7 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take four values:
/// This setting can take five values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
@@ -592,7 +593,9 @@
// Whether or not to show the tab bar in the editor
"show": true,
// Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true
"show_nav_history_buttons": true,
/// Whether or not to show the tab bar buttons.
"show_tab_bar_buttons": true
},
// Settings related to the editor's tabs
"tabs": {
@@ -912,7 +915,7 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the terminal.
/// This setting can take four values:
/// This setting can take five values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
@@ -1166,6 +1169,9 @@
},
"lmstudio": {
"api_url": "http://localhost:1234/api/v0"
},
"deepseek": {
"api_url": "https://api.deepseek.com"
}
},
// Zed's Prettier integration settings.

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ mod supported_countries;
use std::{pin::Pin, str::FromStr};
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Context as _, Result};
use chrono::{DateTime, Utc};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use http_client::http::{HeaderMap, HeaderValue};
@@ -148,8 +148,13 @@ impl Model {
}
}
pub const DEFAULT_BETA_HEADERS: &[&str] = &["prompt-caching-2024-07-31"];
pub fn beta_headers(&self) -> String {
let mut headers = vec!["prompt-caching-2024-07-31".to_string()];
let mut headers = Self::DEFAULT_BETA_HEADERS
.into_iter()
.map(|header| header.to_string())
.collect::<Vec<_>>();
if let Self::Custom {
extra_beta_headers, ..
@@ -186,12 +191,14 @@ pub async fn complete(
request: Request,
) -> Result<Response, AnthropicError> {
let uri = format!("{api_url}/v1/messages");
let model = Model::from_id(&request.model)?;
let beta_headers = Model::from_id(&request.model)
.map(|model| model.beta_headers())
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", model.beta_headers())
.header("Anthropic-Beta", beta_headers)
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
@@ -302,12 +309,14 @@ pub async fn stream_completion_with_rate_limit_info(
stream: true,
};
let uri = format!("{api_url}/v1/messages");
let model = Model::from_id(&request.base.model)?;
let beta_headers = Model::from_id(&request.base.model)
.map(|model| model.beta_headers())
.unwrap_or_else(|_err| Model::DEFAULT_BETA_HEADERS.join(","));
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", model.beta_headers())
.header("Anthropic-Beta", beta_headers)
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
let serialized_request =

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
use futures::channel::mpsc;
use futures::Stream;
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use gpui::{App, Entity, Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
@@ -20,6 +20,7 @@ use std::{
use ui::prelude::*;
use util::ResultExt;
use workspace::Workspace;
use worktree::ChildEntriesOptions;
pub struct FileSlashCommand;
@@ -28,8 +29,8 @@ impl FileSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
cx: &mut AppContext,
workspace: &Entity<Workspace>,
cx: &mut App,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -42,7 +43,13 @@ impl FileSlashCommand {
.chain(project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let id = worktree.id();
worktree.child_entries(Path::new("")).map(move |entry| {
let options = ChildEntriesOptions {
include_files: true,
include_dirs: true,
include_ignored: false,
};
let entries = worktree.child_entries_with_options(Path::new(""), options);
entries.map(move |entry| {
(
project::ProjectPath {
worktree_id: id,
@@ -134,8 +141,9 @@ impl SlashCommand for FileSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakEntity<Workspace>>,
_: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -187,9 +195,10 @@ impl SlashCommand for FileSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -209,9 +218,9 @@ impl SlashCommand for FileSlashCommand {
}
fn collect_files(
project: Model<Project>,
project: Entity<Project>,
glob_inputs: &[String],
cx: &mut AppContext,
cx: &mut App,
) -> impl Stream<Item = Result<SlashCommandEvent>> {
let Ok(matchers) = glob_inputs
.into_iter()

View File

@@ -7,7 +7,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use chrono::Local;
use gpui::{Task, WeakView};
use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -35,8 +35,9 @@ impl SlashCommand for NowSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -46,9 +47,10 @@ impl SlashCommand for NowSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
_window: &mut Window,
_cx: &mut App,
) -> Task<SlashCommandResult> {
let now = Local::now();
let text = format!("Today is {now}.", now = now.to_rfc2822());

View File

@@ -10,7 +10,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView, WindowContext};
use gpui::{App, Task, WeakEntity};
use language::{Anchor, CodeLabel, LspAdapterDelegate};
use language_model::{LanguageModelRegistry, LanguageModelTool};
use prompt_library::PromptBuilder;
@@ -43,7 +43,7 @@ impl SlashCommand for ProjectSlashCommand {
"project".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("project", &[], cx)
}
@@ -67,8 +67,9 @@ impl SlashCommand for ProjectSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -78,9 +79,10 @@ impl SlashCommand for ProjectSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<Anchor>],
context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let model_registry = LanguageModelRegistry::read_global(cx);
let current_model = model_registry.active_model();
@@ -97,7 +99,7 @@ impl SlashCommand for ProjectSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
};
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let current_model = current_model.ok_or_else(|| anyhow!("no model selected"))?;
let prompt =

View File

@@ -1,9 +1,9 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakView};
use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_library::PromptStore;
use std::sync::{atomic::AtomicBool, Arc};
@@ -37,8 +37,9 @@ impl SlashCommand for PromptSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let store = PromptStore::global(cx);
let query = arguments.to_owned().join(" ");
@@ -64,9 +65,10 @@ impl SlashCommand for PromptSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let title = arguments.to_owned().join(" ");
if title.trim().is_empty() {

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView};
use gpui::{App, Task, WeakEntity};
use language::{CodeLabel, LspAdapterDelegate};
use semantic_index::{LoadedSearchResult, SemanticDb};
use std::{
@@ -34,7 +34,7 @@ impl SlashCommand for SearchSlashCommand {
"search".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("search", &["--n"], cx)
}
@@ -58,8 +58,9 @@ impl SlashCommand for SearchSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -69,9 +70,10 @@ impl SlashCommand for SearchSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -107,7 +109,7 @@ impl SlashCommand for SearchSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
};
cx.spawn(|cx| async move {
window.spawn(cx, |cx| async move {
let results = project_index
.read_with(&cx, |project_index, cx| {
project_index.search(vec![query.clone()], limit.unwrap_or(5), cx)

View File

@@ -5,8 +5,7 @@ use assistant_slash_command::{
};
use editor::Editor;
use futures::StreamExt;
use gpui::{AppContext, Task, WeakView};
use gpui::{SharedString, ViewContext, WindowContext};
use gpui::{App, Context, SharedString, Task, WeakEntity, Window};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -22,7 +21,7 @@ impl SlashCommand for SelectionCommand {
"selection".into()
}
fn label(&self, _cx: &AppContext) -> CodeLabel {
fn label(&self, _cx: &App) -> CodeLabel {
CodeLabel::plain(self.name(), None)
}
@@ -50,8 +49,9 @@ impl SlashCommand for SelectionCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -61,9 +61,10 @@ impl SlashCommand for SelectionCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let mut events = vec![];
@@ -102,7 +103,7 @@ impl SlashCommand for SelectionCommand {
pub fn selections_creases(
workspace: &mut workspace::Workspace,
cx: &mut ViewContext<Workspace>,
cx: &mut Context<Workspace>,
) -> Option<Vec<(String, String)>> {
let editor = workspace
.active_item(cx)

View File

@@ -9,7 +9,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::channel::mpsc;
use gpui::{Task, WeakView};
use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate};
use smol::stream::StreamExt;
use smol::Timer;
@@ -45,8 +45,9 @@ impl SlashCommand for StreamingExampleSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -56,9 +57,10 @@ impl SlashCommand for StreamingExampleSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let (events_tx, events_rx) = mpsc::unbounded();
cx.background_executor()

View File

@@ -4,11 +4,11 @@ use assistant_slash_command::{
SlashCommandResult,
};
use editor::Editor;
use gpui::{Task, WeakView};
use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool};
use ui::{IconName, WindowContext};
use ui::{App, IconName, Window};
use workspace::Workspace;
pub struct OutlineSlashCommand;
@@ -34,8 +34,9 @@ impl SlashCommand for OutlineSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -49,9 +50,10 @@ impl SlashCommand for OutlineSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else {

View File

@@ -1,4 +1,4 @@
use anyhow::{Context, Result};
use anyhow::{Context as _, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
@@ -6,13 +6,13 @@ use assistant_slash_command::{
use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
use gpui::{Entity, Task, WeakView};
use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
use std::{
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, ActiveTheme, WindowContext};
use ui::{prelude::*, ActiveTheme, App, Window};
use util::ResultExt;
use workspace::Workspace;
@@ -51,8 +51,9 @@ impl SlashCommand for TabSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakEntity<Workspace>>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let mut has_all_tabs_completion_item = false;
let argument_set = arguments
@@ -82,10 +83,10 @@ impl SlashCommand for TabSlashCommand {
});
let current_query = arguments.last().cloned().unwrap_or_default();
let tab_items_search =
tab_items_for_queries(workspace, &[current_query], cancel, false, cx);
tab_items_for_queries(workspace, &[current_query], cancel, false, window, cx);
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
cx.spawn(|_| async move {
window.spawn(cx, |_| async move {
let tab_items = tab_items_search.await?;
let run_command = tab_items.len() == 1;
let tab_completion_items = tab_items.into_iter().filter_map(|(path, ..)| {
@@ -137,15 +138,17 @@ impl SlashCommand for TabSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let tab_items_search = tab_items_for_queries(
Some(workspace),
arguments,
Arc::new(AtomicBool::new(false)),
true,
window,
cx,
);
@@ -160,15 +163,16 @@ impl SlashCommand for TabSlashCommand {
}
fn tab_items_for_queries(
workspace: Option<WeakView<Workspace>>,
workspace: Option<WeakEntity<Workspace>>,
queries: &[String],
cancel: Arc<AtomicBool>,
strict_match: bool,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<anyhow::Result<Vec<(Option<PathBuf>, BufferSnapshot, usize)>>> {
let empty_query = queries.is_empty() || queries.iter().all(|query| query.trim().is_empty());
let queries = queries.to_owned();
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let mut open_buffers =
workspace
.context("no workspace")?
@@ -281,7 +285,7 @@ fn tab_items_for_queries(
fn active_item_buffer(
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
cx: &mut Context<Workspace>,
) -> anyhow::Result<BufferSnapshot> {
let active_editor = workspace
.active_item(cx)

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, Task, View, WeakView};
use gpui::{App, Entity, Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::prelude::*;
@@ -25,7 +25,7 @@ impl SlashCommand for TerminalSlashCommand {
"terminal".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("terminal", &[LINE_COUNT_ARG], cx)
}
@@ -53,8 +53,9 @@ impl SlashCommand for TerminalSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -64,9 +65,10 @@ impl SlashCommand for TerminalSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
_: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -83,7 +85,7 @@ impl SlashCommand for TerminalSlashCommand {
let lines = active_terminal
.read(cx)
.model()
.entity()
.read(cx)
.last_n_non_empty_lines(line_count);
@@ -107,9 +109,9 @@ impl SlashCommand for TerminalSlashCommand {
}
fn resolve_active_terminal(
workspace: &View<Workspace>,
cx: &WindowContext,
) -> Option<View<TerminalView>> {
workspace: &Entity<Workspace>,
cx: &mut App,
) -> Option<Entity<TerminalView>> {
if let Some(terminal_view) = workspace
.read(cx)
.active_item(cx)

View File

@@ -4,13 +4,13 @@ mod tool_working_set;
use std::sync::Arc;
use anyhow::Result;
use gpui::{AppContext, Task, WeakView, WindowContext};
use gpui::{App, Task, WeakEntity, Window};
use workspace::Workspace;
pub use crate::tool_registry::*;
pub use crate::tool_working_set::*;
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &mut App) {
ToolRegistry::default_global(cx);
}
@@ -31,7 +31,8 @@ pub trait Tool: 'static + Send + Sync {
fn run(
self: Arc<Self>,
input: serde_json::Value,
workspace: WeakView<Workspace>,
cx: &mut WindowContext,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<String>>;
}

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