Compare commits

..

80 Commits

Author SHA1 Message Date
Conrad Irwin
d8d8f90893 vim-global 2025-01-31 12:59:24 -07: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
173 changed files with 4938 additions and 2445 deletions

View File

@@ -1,15 +0,0 @@
name: Feature Request
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
type: "Feature"
body:
- type: textarea
attributes:
label: Describe the feature
description: A one line summary, and description of what you want to happen.
value: |
Summary:
Description:
Screenshots:
<!-- drag files here -->

View File

@@ -5,10 +5,10 @@ type: "Bug"
body:
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A one line summary, and detailed reproduction steps
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
Summary:
<!-- 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:

View File

@@ -4,10 +4,10 @@ type: "Crash"
body:
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A one line summary, and detailed reproduction steps
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
Summary:
<!-- 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:

View File

@@ -1,6 +1,9 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
blank_issues_enabled: false
contact_links:
- name: Feature Request
url: https://github.com/zed-industries/zed/discussions/new/choose
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
- name: Zed Discussion Forum
url: https://github.com/zed-industries/zed/discussions
about: A community discussion forum

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

@@ -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

@@ -29,8 +29,7 @@ jobs:
maxLength: 2000
truncationSymbol: "..."
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4 # v6.0.0
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ steps.get-content.outputs.string }}
flags: 4 # suppress embeds - https://discord.com/developers/docs/resources/message#message-object-message-flags

View File

@@ -22,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

@@ -23,7 +23,7 @@ jobs:
- 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"

302
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",
@@ -1200,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",
@@ -1268,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",
@@ -1290,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",
@@ -1324,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",
@@ -1346,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",
@@ -1368,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",
@@ -1839,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",
@@ -1871,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",
@@ -1881,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",
@@ -2184,7 +2182,7 @@ dependencies = [
"cap-primitives",
"cap-std",
"io-lifetimes",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -2212,7 +2210,7 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
"winx",
]
@@ -2720,7 +2718,6 @@ dependencies = [
"envy",
"extension",
"file_finder",
"fireworks",
"fs",
"futures 0.3.31",
"git",
@@ -4023,7 +4020,7 @@ dependencies = [
"util",
"uuid",
"workspace",
"zed_predict_tos",
"zed_predict_onboarding",
]
[[package]]
@@ -4223,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]]
@@ -4659,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"
@@ -4698,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"
@@ -4866,7 +4858,7 @@ dependencies = [
"gpui",
"libc",
"log",
"notify",
"notify 6.1.1",
"objc",
"parking_lot",
"paths",
@@ -4890,7 +4882,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
dependencies = [
"io-lifetimes",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -5163,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"
@@ -5426,8 +5430,10 @@ dependencies = [
"itertools 0.14.0",
"linkme",
"log",
"lyon",
"media",
"metal",
"naga",
"num_cpus",
"objc",
"objc2",
@@ -5484,6 +5490,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "gpui_tokio"
version = "0.1.0"
dependencies = [
"gpui",
"tokio",
"util",
]
[[package]]
name = "grid"
version = "0.13.0"
@@ -6388,7 +6403,7 @@ dependencies = [
"ui",
"workspace",
"zed_actions",
"zed_predict_tos",
"zed_predict_onboarding",
"zeta",
]
@@ -6403,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"
@@ -6459,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]]
@@ -7429,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"
@@ -7570,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",
@@ -7586,7 +7675,7 @@ dependencies = [
"ignore",
"log",
"memchr",
"notify",
"notify 8.0.0",
"notify-debouncer-mini",
"once_cell",
"opener",
@@ -8002,7 +8091,7 @@ dependencies = [
"crossbeam-channel",
"filetime",
"fsevent-sys 4.1.0",
"inotify",
"inotify 0.9.6",
"kqueue",
"libc",
"log",
@@ -8012,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"
@@ -10041,7 +10156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes 1.9.0",
"heck 0.4.1",
"heck 0.5.0",
"itertools 0.12.1",
"log",
"multimap 0.10.0",
@@ -10257,7 +10372,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -10639,6 +10754,7 @@ dependencies = [
"git",
"git_hosting_providers",
"gpui",
"gpui_tokio",
"http_client",
"language",
"language_extension",
@@ -11142,7 +11258,7 @@ dependencies = [
"libc",
"linux-raw-sys",
"once_cell",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -11310,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"
@@ -11651,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",
@@ -12851,7 +12980,7 @@ dependencies = [
"fd-lock",
"io-lifetimes",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
"winx",
]
@@ -12976,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]]
@@ -13150,7 +13279,6 @@ dependencies = [
"log",
"palette",
"rust-embed",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
@@ -13412,6 +13540,7 @@ dependencies = [
"windows 0.58.0",
"workspace",
"zed_actions",
"zed_predict_onboarding",
]
[[package]]
@@ -13930,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",
@@ -14198,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"
@@ -14302,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",
@@ -14409,6 +14538,7 @@ dependencies = [
"collections",
"command_palette",
"command_palette_hooks",
"db",
"editor",
"futures 0.3.31",
"git_ui",
@@ -14563,6 +14693,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"
@@ -15734,7 +15873,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]]
@@ -15753,7 +15892,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",
]
@@ -15773,6 +15912,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"
@@ -16286,7 +16434,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.172.0"
version = "0.173.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16332,6 +16480,7 @@ dependencies = [
"git_ui",
"go_to_line",
"gpui",
"gpui_tokio",
"http_client",
"image_viewer",
"inline_completion_button",
@@ -16409,7 +16558,7 @@ dependencies = [
"winresource",
"workspace",
"zed_actions",
"zed_predict_tos",
"zed_predict_onboarding",
"zeta",
]
@@ -16524,23 +16673,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"
@@ -16731,6 +16881,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"ctor",
"db",
"editor",
"env_logger 0.11.6",
"feature_flags",
@@ -16745,6 +16896,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,8 +30,8 @@ members = [
"crates/context_server_settings",
"crates/copilot",
"crates/db",
"crates/diagnostics",
"crates/deepseek",
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
"crates/evals",
@@ -45,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",
@@ -103,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",
@@ -141,7 +143,6 @@ members = [
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/reqwest_client",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -151,8 +152,8 @@ members = [
"crates/worktree",
"crates/zed",
"crates/zed_actions",
"crates/zed_predict_onboarding",
"crates/zeta",
"crates/git_ui",
#
# Extensions
@@ -169,7 +170,6 @@ members = [
"extensions/lua",
"extensions/php",
"extensions/perplexity",
"extensions/prisma",
"extensions/proto",
"extensions/purescript",
"extensions/ruff",
@@ -201,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" }
@@ -240,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" }
@@ -253,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" }
@@ -349,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" }
#
@@ -375,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"
@@ -524,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

@@ -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

@@ -122,8 +122,7 @@
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
"shift-f10": "editor::OpenContextMenu"
}
},
{
@@ -137,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"
}
},
{
@@ -798,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",
@@ -821,5 +823,12 @@
"shift-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode"
}
},
{
"context": "ZedPredictModal",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel"
}
}
]

View File

@@ -132,8 +132,7 @@
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp",
"ctrl-f12": "editor::GoToDeclaration",
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
}
},
{
@@ -146,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"
}
},
{
@@ -834,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"],
@@ -881,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

@@ -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
@@ -914,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

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

@@ -11,7 +11,6 @@ use assistant_context_editor::{
};
use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet;
use client::{proto, Client, Status};
use editor::{Editor, EditorEvent};
use fs::Fs;
@@ -100,11 +99,10 @@ impl AssistantPanel {
) -> Task<Result<Entity<Self>>> {
cx.spawn(|mut cx| async move {
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
let tools = Arc::new(ToolWorkingSet::default());
let context_store = workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx)
ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
})?
.await?;

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use collections::HashMap;
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 {
@@ -91,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, _window, 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()
@@ -143,26 +152,43 @@ impl Render for AssistantConfiguration {
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, _window, cx| cx.dispatch_action(&DeployPromptLibrary)),
),
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

@@ -134,7 +134,6 @@ impl AssistantPanel {
project,
prompt_builder.clone(),
slash_commands,
tools.clone(),
cx,
)
})?
@@ -613,7 +612,7 @@ impl AssistantPanel {
})
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
ActiveView::History | ActiveView::PromptEditorHistory => "History".into(),
ActiveView::Configuration => "Configuration".into(),
ActiveView::Configuration => "Assistant Settings".into(),
};
let sub_title = match self.active_view {
@@ -700,7 +699,7 @@ impl AssistantPanel {
IconButton::new("configure-assistant", IconName::Settings)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(Tooltip::text("Configure Assistant"))
.tooltip(Tooltip::text("Assistant Settings"))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenConfiguration.boxed_clone(), 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

@@ -8,11 +8,9 @@ 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::{
@@ -23,7 +21,6 @@ use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, P
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(),
}
}
@@ -599,9 +582,7 @@ pub struct AssistantContext {
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>,
@@ -654,7 +635,6 @@ impl AssistantContext {
telemetry: Option<Arc<Telemetry>>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
cx: &mut Context<Self>,
) -> Self {
Self::new(
@@ -664,7 +644,6 @@ impl AssistantContext {
language_registry,
prompt_builder,
slash_commands,
tools,
project,
telemetry,
cx,
@@ -679,7 +658,6 @@ impl AssistantContext {
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
@@ -707,7 +685,6 @@ impl AssistantContext {
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 AssistantContext {
project,
language_registry,
slash_commands,
tools,
patches: Vec::new(),
xml_tags: Vec::new(),
prompt_builder,
@@ -802,7 +778,6 @@ impl AssistantContext {
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
@@ -815,7 +790,6 @@ impl AssistantContext {
language_registry,
prompt_builder,
slash_commands,
tools,
project,
telemetry,
cx,
@@ -848,10 +822,6 @@ impl AssistantContext {
&self.slash_commands
}
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
&self.tools
}
pub fn set_capability(&mut self, capability: language::Capability, cx: &mut Context<Self>) {
self.buffer
.update(cx, |buffer, cx| buffer.set_capability(capability, cx));
@@ -1177,14 +1147,6 @@ impl AssistantContext {
})
}
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 Context<Self>) {
let markdown = self.language_registry.language_for_name("Markdown");
cx.spawn(|this, mut cx| async move {
@@ -2206,68 +2168,6 @@ impl AssistantContext {
);
}
pub fn insert_tool_output(
&mut self,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut Context<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 Context<Self>) {
self.count_remaining_tokens(cx);
}
@@ -2298,23 +2198,7 @@ impl AssistantContext {
// 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)
@@ -2371,44 +2255,7 @@ impl AssistantContext {
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(_) => {}
}
});
@@ -2491,9 +2338,7 @@ impl AssistantContext {
if let Ok(stop_reason) = result {
match stop_reason {
StopReason::ToolUse => {
cx.emit(ContextEvent::UsePendingTools);
}
StopReason::ToolUse => {}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
}
@@ -2572,23 +2417,6 @@ impl AssistantContext {
.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;

View File

@@ -8,7 +8,6 @@ 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::{
@@ -56,7 +55,6 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -197,7 +195,6 @@ fn test_message_splitting(cx: &mut App) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -300,7 +297,6 @@ fn test_messages_for_offsets(cx: &mut App) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -414,7 +410,6 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -704,7 +699,6 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -969,7 +963,6 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1088,7 +1081,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -1132,7 +1124,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1191,7 +1182,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1451,7 +1441,6 @@ fn test_mark_cache_anchors(cx: &mut App) {
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});

View File

@@ -5,7 +5,6 @@ use assistant_slash_commands::{
selections_creases, DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs,
FileSlashCommand,
};
use assistant_tool::ToolWorkingSet;
use client::{proto, zed_urls};
use collections::{hash_map, BTreeSet, HashMap, HashSet};
use editor::{
@@ -33,7 +32,7 @@ use indexed_docs::IndexedDocsStore;
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
use language_model::{
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
LanguageModelToolUse, Role,
Role,
};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use multi_buffer::MultiBufferRow;
@@ -171,7 +170,6 @@ pub struct ContextEditor {
context: Entity<AssistantContext>,
fs: Arc<dyn Fs>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
@@ -182,7 +180,6 @@ pub struct ContextEditor {
remote_id: Option<workspace::ViewId>,
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>,
pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
_subscriptions: Vec<Subscription>,
patches: HashMap<Range<language::Anchor>, PatchViewState>,
active_patch: Option<Range<language::Anchor>>,
@@ -244,11 +241,9 @@ impl ContextEditor {
let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands().clone();
let tools = context.read(cx).tools().clone();
let mut this = Self {
context,
slash_commands,
tools,
editor,
lsp_adapter_delegate,
blocks: Default::default(),
@@ -260,7 +255,6 @@ impl ContextEditor {
project,
pending_slash_command_creases: HashMap::default(),
invoked_slash_command_creases: HashMap::default(),
pending_tool_use_creases: HashMap::default(),
_subscriptions,
patches: HashMap::default(),
active_patch: None,
@@ -580,87 +574,6 @@ impl ContextEditor {
cx,
);
}
let new_tool_uses = self
.context
.read(cx)
.pending_tool_uses()
.into_iter()
.filter(|tool_use| {
!self
.pending_tool_use_creases
.contains_key(&tool_use.source_range)
})
.cloned()
.collect::<Vec<_>>();
let buffer = editor.buffer().read(cx).snapshot(cx);
let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
let excerpt_id = *excerpt_id;
let mut buffer_rows_to_fold = BTreeSet::new();
let creases = new_tool_uses
.iter()
.map(|tool_use| {
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(
cx.entity().downgrade(),
IconName::PocketKnife,
tool_use.name.clone().into(),
),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _window: &mut Window, _cx: &mut App| {
Empty.into_any()
};
let start = buffer
.anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
self.context.update(cx, |context, cx| {
context.insert_content(
Content::ToolUse {
range: tool_use.source_range.clone(),
tool_use: LanguageModelToolUse {
id: tool_use.id.clone(),
name: tool_use.name.clone(),
input: tool_use.input.clone(),
},
},
cx,
);
});
Crease::inline(
start..end,
placeholder,
fold_toggle("tool-use"),
render_trailer,
)
})
.collect::<Vec<_>>();
let crease_ids = editor.insert_creases(creases, cx);
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
editor.fold_at(&FoldAt { buffer_row }, window, cx);
}
self.pending_tool_use_creases.extend(
new_tool_uses
.iter()
.map(|tool_use| tool_use.source_range.clone())
.zip(crease_ids),
);
});
}
ContextEvent::PatchesUpdated { removed, updated } => {
@@ -758,66 +671,6 @@ impl ContextEditor {
ContextEvent::SlashCommandOutputSectionAdded { section } => {
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
}
ContextEvent::UsePendingTools => {
let pending_tool_uses = self
.context
.read(cx)
.pending_tool_uses()
.into_iter()
.filter(|tool_use| tool_use.status.is_idle())
.cloned()
.collect::<Vec<_>>();
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(), window, cx);
self.context.update(cx, |context, cx| {
context.insert_tool_output(tool_use.id.clone(), task, cx);
});
}
}
}
ContextEvent::ToolFinished {
tool_use_id,
output_range,
} => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
let excerpt_id = *excerpt_id;
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(
cx.entity().downgrade(),
IconName::PocketKnife,
format!("Tool Result: {tool_use_id}").into(),
),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
let start = buffer
.anchor_in_excerpt(excerpt_id, output_range.start)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, output_range.end)
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
let crease = Crease::inline(
start..end,
placeholder,
fold_toggle("tool-use"),
render_trailer,
);
editor.insert_creases([crease], cx);
editor.fold_at(&FoldAt { buffer_row }, window, cx);
});
}
ContextEvent::Operation(_) => {}
ContextEvent::ShowAssistError(error_message) => {
self.last_error = Some(AssistError::Message(error_message.clone()));
@@ -2112,18 +1965,13 @@ impl ContextEditor {
.context
.read(cx)
.contents(cx)
.filter_map(|content| {
if let Content::Image {
anchor,
render_image,
..
} = content
{
Some((anchor, render_image))
} else {
None
}
})
.map(
|Content::Image {
anchor,
render_image,
..
}| (anchor, render_image),
)
.filter_map(|(anchor, render_image)| {
const MAX_HEIGHT_IN_LINES: u32 = 8;
let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();

View File

@@ -4,12 +4,11 @@ use crate::{
};
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;
@@ -50,12 +49,10 @@ pub struct ContextStore {
contexts_metadata: Vec<SavedContextMetadata>,
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>,
@@ -98,7 +95,6 @@ impl ContextStore {
project: Entity<Project>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
cx: &mut App,
) -> Task<Result<Entity<Self>>> {
let fs = project.read(cx).fs().clone();
@@ -119,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 {
@@ -367,7 +361,6 @@ impl ContextStore {
Some(self.telemetry.clone()),
self.prompt_builder.clone(),
self.slash_commands.clone(),
self.tools.clone(),
cx,
)
});
@@ -391,7 +384,6 @@ 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?;
@@ -405,7 +397,6 @@ impl ContextStore {
language_registry,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -456,7 +447,6 @@ 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?;
@@ -467,7 +457,6 @@ impl ContextStore {
languages,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -535,7 +524,6 @@ 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")?;
@@ -547,7 +535,6 @@ impl ContextStore {
language_registry,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -816,7 +803,6 @@ impl ContextStore {
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) {
@@ -856,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();
@@ -890,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

@@ -121,9 +121,7 @@ pub enum Event {
},
ShowContacts,
ParticipantIndicesChanged,
TermsStatusUpdated {
accepted: bool,
},
PrivateUserInfoUpdated,
}
#[derive(Clone, Copy)]
@@ -227,9 +225,7 @@ impl UserStore {
};
this.set_current_user_accepted_tos_at(accepted_tos_at);
cx.emit(Event::TermsStatusUpdated {
accepted: accepted_tos_at.is_some(),
});
cx.emit(Event::PrivateUserInfoUpdated);
})
} else {
anyhow::Ok(())
@@ -244,6 +240,8 @@ impl UserStore {
Status::SignedOut => {
current_user_tx.send(None).await.ok();
this.update(&mut cx, |this, cx| {
this.accepted_tos_at = None;
cx.emit(Event::PrivateUserInfoUpdated);
cx.notify();
this.clear_contacts()
})?
@@ -714,7 +712,7 @@ impl UserStore {
this.update(&mut cx, |this, cx| {
this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at));
cx.emit(Event::TermsStatusUpdated { accepted: true });
cx.emit(Event::PrivateUserInfoUpdated);
})
} else {
Err(anyhow!("client not found"))

View File

@@ -34,7 +34,6 @@ collections.workspace = true
dashmap.workspace = true
derive_more.workspace = true
envy = "0.4.2"
fireworks.workspace = true
futures.workspace = true
google_ai.workspace = true
hex.workspace = true

View File

@@ -5,6 +5,7 @@ pub mod extensions;
pub mod ips_file;
pub mod slack;
use crate::api::events::SnowflakeRow;
use crate::{
auth,
db::{User, UserId},
@@ -99,6 +100,7 @@ pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
.route("/user", get(get_authenticated_user))
.route("/users/:id/access_tokens", post(create_access_token))
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
.route("/snowflake/events", post(write_snowflake_event))
.merge(billing::router())
.merge(contributors::router())
.layer(
@@ -245,3 +247,19 @@ async fn create_access_token(
encrypted_access_token,
}))
}
/// An endpoint that writes a Snowflake event to our event stream.
///
/// This endpoint is exposed such that other internal services can write
/// telemetry events without needing to talk to AWS Kinesis directly.
async fn write_snowflake_event(
Extension(app): Extension<Arc<AppState>>,
Json(event): Json<SnowflakeRow>,
) -> Result<()> {
let kinesis_client = app.kinesis_client.clone();
let kinesis_stream = app.config.kinesis_stream.clone();
event.write(&kinesis_client, &kinesis_stream).await?;
Ok(())
}

View File

@@ -21,15 +21,12 @@ use chrono::{DateTime, Duration, Utc};
use collections::HashMap;
use db::TokenUsage;
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
use futures::{FutureExt, Stream, StreamExt as _};
use futures::{Stream, StreamExt as _};
use reqwest_client::ReqwestClient;
use rpc::{
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
};
use rpc::{
ListModelsResponse, PredictEditsParams, PredictEditsResponse,
MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME,
};
use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME};
use serde_json::json;
use std::{
pin::Pin,
@@ -42,6 +39,8 @@ use util::ResultExt;
pub use token::*;
const ACTIVE_USER_COUNT_CACHE_DURATION: Duration = Duration::seconds(30);
pub struct LlmState {
pub config: Config,
pub executor: Executor,
@@ -52,8 +51,6 @@ pub struct LlmState {
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
}
const ACTIVE_USER_COUNT_CACHE_DURATION: Duration = Duration::seconds(30);
impl LlmState {
pub async fn new(config: Config, executor: Executor) -> Result<Arc<Self>> {
let database_url = config
@@ -120,7 +117,6 @@ pub fn routes() -> Router<(), Body> {
Router::new()
.route("/models", get(list_models))
.route("/completion", post(perform_completion))
.route("/predict_edits", post(predict_edits))
.layer(middleware::from_fn(validate_api_token))
}
@@ -434,156 +430,6 @@ fn normalize_model_name(known_models: Vec<String>, name: String) -> String {
}
}
async fn predict_edits(
Extension(state): Extension<Arc<LlmState>>,
Extension(claims): Extension<LlmTokenClaims>,
_country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
Json(params): Json<PredictEditsParams>,
) -> Result<impl IntoResponse> {
if !claims.is_staff && !claims.has_predict_edits_feature_flag {
return Err(Error::http(
StatusCode::FORBIDDEN,
"no access to Zed's edit prediction feature".to_string(),
));
}
let sample_input_output = claims.is_staff && rand::random::<f32>() < 0.1;
let api_url = state
.config
.prediction_api_url
.as_ref()
.context("no PREDICTION_API_URL configured on the server")?;
let api_key = state
.config
.prediction_api_key
.as_ref()
.context("no PREDICTION_API_KEY configured on the server")?;
let model = state
.config
.prediction_model
.as_ref()
.context("no PREDICTION_MODEL configured on the server")?;
let outline_prefix = params
.outline
.as_ref()
.map(|outline| format!("### Outline for current file:\n{}\n", outline))
.unwrap_or_default();
let prompt = include_str!("./llm/prediction_prompt.md")
.replace("<outline>", &outline_prefix)
.replace("<events>", &params.input_events)
.replace("<excerpt>", &params.input_excerpt);
let request_start = std::time::Instant::now();
let timeout = state
.executor
.sleep(std::time::Duration::from_secs(2))
.fuse();
let response = fireworks::complete(
&state.http_client,
api_url,
api_key,
fireworks::CompletionRequest {
model: model.to_string(),
prompt: prompt.clone(),
max_tokens: 2048,
temperature: 0.,
prediction: Some(fireworks::Prediction::Content {
content: params.input_excerpt.clone(),
}),
rewrite_speculation: Some(true),
},
)
.fuse();
futures::pin_mut!(timeout);
futures::pin_mut!(response);
futures::select! {
_ = timeout => {
state.executor.spawn_detached({
let kinesis_client = state.kinesis_client.clone();
let kinesis_stream = state.config.kinesis_stream.clone();
let model = model.clone();
async move {
SnowflakeRow::new(
"Fireworks Completion Timeout",
claims.metrics_id,
claims.is_staff,
claims.system_id.clone(),
json!({
"model": model.to_string(),
"prompt": prompt,
}),
)
.write(&kinesis_client, &kinesis_stream)
.await
.log_err();
}
});
Err(anyhow!("request timed out"))?
},
response = response => {
let duration = request_start.elapsed();
let mut response = response?;
let choice = response
.completion
.choices
.pop()
.context("no output from completion response")?;
state.executor.spawn_detached({
let kinesis_client = state.kinesis_client.clone();
let kinesis_stream = state.config.kinesis_stream.clone();
let model = model.clone();
let output = choice.text.clone();
async move {
let properties = if sample_input_output {
json!({
"model": model.to_string(),
"headers": response.headers,
"usage": response.completion.usage,
"duration": duration.as_secs_f64(),
"prompt": prompt,
"input_excerpt": params.input_excerpt,
"input_events": params.input_events,
"outline": params.outline,
"output": output,
"is_sampled": true,
})
} else {
json!({
"model": model.to_string(),
"headers": response.headers,
"usage": response.completion.usage,
"duration": duration.as_secs_f64(),
"is_sampled": false,
})
};
SnowflakeRow::new(
"Fireworks Completion Requested",
claims.metrics_id,
claims.is_staff,
claims.system_id.clone(),
properties,
)
.write(&kinesis_client, &kinesis_stream)
.await
.log_err();
}
});
Ok(Json(PredictEditsResponse {
output_excerpt: choice.text,
}))
},
}
}
/// The maximum monthly spending an individual user can reach on the free tier
/// before they have to pay.
pub const FREE_TIER_MONTHLY_SPENDING_LIMIT: Cents = Cents::from_dollars(10);

View File

@@ -1,13 +0,0 @@
<outline>## Task
Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
### Instruction:
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
### Events:
<events>
### Input:
<excerpt>
### Response:

View File

@@ -391,6 +391,9 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::OpenContext>)
.add_request_handler(forward_mutating_project_request::<proto::CreateContext>)
.add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
.add_request_handler(forward_mutating_project_request::<proto::Unstage>)
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
.add_message_handler(update_context)
.add_request_handler({

View File

@@ -8,7 +8,6 @@ use crate::{
use anyhow::{anyhow, Result};
use assistant_context_editor::ContextStore;
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet;
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet};
@@ -6547,7 +6546,6 @@ async fn test_context_collaboration_with_reconnect(
project_a.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
})
@@ -6559,7 +6557,6 @@ async fn test_context_collaboration_with_reconnect(
project_b.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
})

View File

@@ -466,7 +466,8 @@ impl ProjectDiagnosticsEditor {
}
let excerpt_id = excerpts
.push_excerpts(
.insert_excerpts_after(
prev_excerpt_id,
buffer.clone(),
[ExcerptRange {
context: context_range.clone(),

View File

@@ -88,7 +88,7 @@ url.workspace = true
util.workspace = true
uuid.workspace = true
workspace.workspace = true
zed_predict_tos.workspace = true
zed_predict_onboarding.workspace = true
[dev-dependencies]
ctor.workspace = true

View File

@@ -397,4 +397,4 @@ gpui::actions!(
action_as!(go_to_line, ToggleGoToLine as Toggle);
action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]);
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleDiffHunk"]);
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]);

View File

@@ -652,7 +652,7 @@ impl CompletionsMenu {
)
.on_click(cx.listener(move |editor, _event, window, cx| {
cx.stop_propagation();
editor.toggle_zed_predict_tos(window, cx);
editor.toggle_zed_predict_onboarding(window, cx);
})),
),

View File

@@ -1068,13 +1068,15 @@ impl DisplaySnapshot {
DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
}
pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
let mut point = point.0;
if point.column == self.line_len(DisplayRow(point.row)) {
point.column = point.column.saturating_sub(1);
point = self.block_snapshot.clip_point(point, Bias::Left);
pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
let mut point = self.display_point_to_point(display_point, Bias::Left);
if point.column != self.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
return display_point;
}
DisplayPoint(point)
point.column = point.column.saturating_sub(1);
point = self.buffer_snapshot.clip_point(point, Bias::Left);
self.point_to_display_point(point, Bias::Left)
}
pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>

View File

@@ -69,7 +69,7 @@ pub use element::{
};
use futures::{future, FutureExt};
use fuzzy::StringMatchCandidate;
use zed_predict_tos::ZedPredictTos;
use zed_predict_onboarding::ZedPredictModal;
use code_context_menus::{
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
@@ -81,9 +81,9 @@ use gpui::{
AsyncWindowContext, AvailableSpace, Bounds, ClipboardEntry, ClipboardItem, Context,
DispatchPhase, ElementId, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext,
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText,
Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size,
Styled, StyledText, Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection,
UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -681,6 +681,7 @@ pub struct Editor {
leader_peer_id: Option<PeerId>,
remote_id: Option<ViewId>,
hover_state: HoverState,
pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
gutter_hovered: bool,
hovered_link_state: Option<HoveredLinkState>,
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
@@ -727,6 +728,7 @@ pub struct Editor {
expect_bounds_change: Option<Bounds<Pixels>>,
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>,
in_project_search: bool,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
breadcrumb_header: Option<String>,
focused_block: Option<FocusedBlock>,
@@ -1375,6 +1377,7 @@ impl Editor {
leader_peer_id: None,
remote_id: None,
hover_state: Default::default(),
pending_mouse_down: None,
hovered_link_state: Default::default(),
inline_completion_provider: None,
active_inline_completion: None,
@@ -1424,6 +1427,7 @@ impl Editor {
],
tasks_update_task: None,
linked_edit_ranges: Default::default(),
in_project_search: false,
previous_search_ranges: None,
breadcrumb_header: None,
focused_block: None,
@@ -1701,6 +1705,10 @@ impl Editor {
self.collaboration_hub = Some(hub);
}
pub fn set_in_project_search(&mut self, in_project_search: bool) {
self.in_project_search = in_project_search;
}
pub fn set_custom_context_menu(
&mut self,
f: impl 'static
@@ -3948,12 +3956,21 @@ impl Editor {
self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
}
fn toggle_zed_predict_tos(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn toggle_zed_predict_onboarding(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let (Some(workspace), Some(project)) = (self.workspace(), self.project.as_ref()) else {
return;
};
ZedPredictTos::toggle(workspace, project.read(cx).user_store().clone(), window, cx);
let project = project.read(cx);
ZedPredictModal::toggle(
workspace,
project.user_store().clone(),
project.client().clone(),
project.fs().clone(),
window,
cx,
);
}
fn do_completion(
@@ -3985,7 +4002,7 @@ impl Editor {
)) => {
drop(entries);
drop(context_menu);
self.toggle_zed_predict_tos(window, cx);
self.toggle_zed_predict_onboarding(window, cx);
return Some(Task::ready(Ok(())));
}
_ => {}
@@ -8816,6 +8833,12 @@ impl Editor {
}
}
let reversed = self.selections.oldest::<usize>(cx).reversed;
for selection in new_selections.iter_mut() {
selection.reversed = reversed;
}
select_next_state.done = true;
self.unfold_ranges(
&new_selections
@@ -12144,6 +12167,10 @@ impl Editor {
.update(cx, |map, cx| map.set_wrap_width(width, cx))
}
pub fn set_soft_wrap(&mut self) {
self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
}
pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
if self.soft_wrap_mode_override.is_some() {
self.soft_wrap_mode_override.take();
@@ -13338,7 +13365,11 @@ impl Editor {
refresh_linked_ranges(self, window, cx);
telemetry.log_edit_event("editor", is_via_ssh);
}
multi_buffer::Event::ExcerptsAdded { buffer, excerpts } => {
multi_buffer::Event::ExcerptsAdded {
buffer,
predecessor,
excerpts,
} => {
self.tasks_update_task = Some(self.refresh_runnables(window, cx));
let buffer_id = buffer.read(cx).remote_id();
if self.buffer.read(cx).change_set_for(buffer_id).is_none() {
@@ -13353,6 +13384,7 @@ impl Editor {
}
cx.emit(EditorEvent::ExcerptsAdded {
buffer: buffer.clone(),
predecessor: *predecessor,
excerpts: excerpts.clone(),
});
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
@@ -15246,6 +15278,7 @@ pub enum EditorEvent {
},
ExcerptsAdded {
buffer: Entity<Buffer>,
predecessor: ExcerptId,
excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
},
ExcerptsRemoved {

View File

@@ -5236,11 +5236,27 @@ async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
// Test caret-only selections
cx.set_state("abc\nˇabc abc\ndefabc\nabc");
cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
.unwrap();
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
// Test left-to-right selections
cx.set_state("abc\n«abcˇ»\nabc");
cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
.unwrap();
cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
// Test right-to-left selections
cx.set_state("abc\n«ˇabc»\nabc");
cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
.unwrap();
cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
}
#[gpui::test]
@@ -10431,7 +10447,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
],
cx,
);
multibuffer.push_excerpts(
multibuffer.insert_excerpts_after(
excerpt_ids[0],
buffer_2.clone(),
[
ExcerptRange {
@@ -11282,7 +11299,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
cx.simulate_keystroke(".");
let item1 = lsp::CompletionItem {
label: "id".to_string(),
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: None,
documentation: None,
@@ -11332,7 +11349,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
.iter()
.map(|completion| &completion.label.text)
.collect::<Vec<_>>(),
vec!["id", "other"]
vec!["method id()", "other"]
)
}
CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
@@ -11387,7 +11404,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
.iter()
.map(|completion| &completion.label.text)
.collect::<Vec<_>>(),
vec!["method id()", "other"],
vec!["method id() Now resolved!", "other"],
"Should update first completion label, but not second as the filter text did not match."
);
}

View File

@@ -736,6 +736,10 @@ impl EditorElement {
fn mouse_up(
editor: &mut Editor,
event: &MouseUpEvent,
#[cfg_attr(
not(any(target_os = "linux", target_os = "freebsd")),
allow(unused_variables)
)]
position_map: &PositionMap,
text_hitbox: &Hitbox,
window: &mut Window,
@@ -748,18 +752,7 @@ impl EditorElement {
editor.select(SelectPhase::End, window, cx);
}
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
let multi_cursor_modifier = match multi_cursor_setting {
MultiCursorModifier::Alt => event.modifiers.secondary(),
MultiCursorModifier::CmdOrCtrl => event.modifiers.alt,
};
if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
let point = position_map.point_for_position(text_hitbox.bounds, event.position);
editor.handle_click_hovered_link(point, event.modifiers, window, cx);
cx.stop_propagation();
} else if end_selection && pending_nonempty_selections {
if end_selection && pending_nonempty_selections {
cx.stop_propagation();
} else if cfg!(any(target_os = "linux", target_os = "freebsd"))
&& event.button == MouseButton::Middle
@@ -791,6 +784,30 @@ impl EditorElement {
}
}
fn click(
editor: &mut Editor,
event: &ClickEvent,
position_map: &PositionMap,
text_hitbox: &Hitbox,
window: &mut Window,
cx: &mut Context<Editor>,
) {
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
let multi_cursor_modifier = match multi_cursor_setting {
MultiCursorModifier::Alt => event.modifiers().secondary(),
MultiCursorModifier::CmdOrCtrl => event.modifiers().alt,
};
if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
let point = position_map.point_for_position(text_hitbox.bounds, event.up.position);
editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
cx.stop_propagation();
}
}
fn mouse_dragged(
editor: &mut Editor,
event: &MouseMoveEvent,
@@ -3410,6 +3427,7 @@ impl EditorElement {
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
newest_selection_head: Option<DisplayPoint>,
editor_width: Pixels,
style: &EditorStyle,
window: &mut Window,
@@ -3526,7 +3544,7 @@ impl EditorElement {
crate::inline_completion_edit_text(&snapshot, edits, edit_preview, false, cx)
})?;
let line_count = highlighted_edits.text.lines().count() + 1;
let line_count = highlighted_edits.text.lines().count();
let longest_row =
editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
@@ -3556,25 +3574,58 @@ impl EditorElement {
.child(styled_text)
.into_any();
let viewport_bounds = Bounds::new(Default::default(), window.viewport_size())
.extend(Edges {
right: -Self::SCROLLBAR_WIDTH,
..Default::default()
});
let x_after_longest =
text_bounds.origin.x + longest_line_width + PADDING_X - scroll_pixel_position.x;
let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
let is_fully_visible =
editor_width >= longest_line_width + PADDING_X + element_bounds.width;
// Fully visible if it can be displayed within the window (allow overlapping other
// panes). However, this is only allowed if the popover starts within text_bounds.
let is_fully_visible = x_after_longest < text_bounds.right()
&& x_after_longest + element_bounds.width < viewport_bounds.right();
let origin = if is_fully_visible {
text_bounds.origin
+ point(
longest_line_width + PADDING_X - scroll_pixel_position.x,
edit_start.row().as_f32() * line_height - scroll_pixel_position.y,
)
point(
x_after_longest,
text_bounds.origin.y + edit_start.row().as_f32() * line_height
- scroll_pixel_position.y,
)
} else {
let target_above =
DisplayRow(edit_start.row().0.saturating_sub(line_count as u32));
let row_target = if visible_row_range
.contains(&DisplayRow(target_above.0.saturating_sub(1)))
{
target_above
// Avoid overlapping both the edited rows and the user's cursor.
let target_above = DisplayRow(
edit_start
.row()
.0
.min(
newest_selection_head
.map_or(u32::MAX, |cursor_row| cursor_row.row().0),
)
.saturating_sub(line_count as u32),
);
let mut row_target;
if visible_row_range.contains(&DisplayRow(target_above.0.saturating_sub(1))) {
row_target = target_above;
} else {
DisplayRow(edit_end.row().0 + 1)
row_target = DisplayRow(
edit_end.row().0.max(
newest_selection_head.map_or(0, |cursor_row| cursor_row.row().0),
) + 1,
);
if !visible_row_range.contains(&row_target) {
// Not visible, so fallback on displaying immediately below the cursor.
if let Some(cursor) = newest_selection_head {
row_target = DisplayRow(cursor.row().0 + 1);
} else {
// Not visible and no cursor visible, so fallback on displaying at the top of the editor.
row_target = DisplayRow(0);
}
}
};
text_bounds.origin
@@ -3584,8 +3635,10 @@ impl EditorElement {
)
};
element.prepaint_as_root(origin, element_bounds.into(), window, cx);
Some(element)
window.defer_draw(element, origin, 1);
// Do not return an element, since it will already be drawn due to defer_draw.
None
}
}
}
@@ -5269,6 +5322,13 @@ impl EditorElement {
if phase == DispatchPhase::Bubble {
match event.button {
MouseButton::Left => editor.update(cx, |editor, cx| {
let pending_mouse_down = editor
.pending_mouse_down
.get_or_insert_with(Default::default)
.clone();
*pending_mouse_down.borrow_mut() = Some(event.clone());
Self::mouse_left_down(
editor,
event,
@@ -5320,6 +5380,43 @@ impl EditorElement {
}
}
});
window.on_mouse_event({
let editor = self.editor.clone();
let position_map = layout.position_map.clone();
let text_hitbox = layout.text_hitbox.clone();
let mut captured_mouse_down = None;
move |event: &MouseUpEvent, phase, window, cx| match phase {
// Clear the pending mouse down during the capture phase,
// so that it happens even if another event handler stops
// propagation.
DispatchPhase::Capture => editor.update(cx, |editor, _cx| {
let pending_mouse_down = editor
.pending_mouse_down
.get_or_insert_with(Default::default)
.clone();
let mut pending_mouse_down = pending_mouse_down.borrow_mut();
if pending_mouse_down.is_some() && text_hitbox.is_hovered(window) {
captured_mouse_down = pending_mouse_down.take();
window.refresh();
}
}),
// Fire click handlers during the bubble phase.
DispatchPhase::Bubble => editor.update(cx, |editor, cx| {
if let Some(mouse_down) = captured_mouse_down.take() {
let event = ClickEvent {
down: mouse_down,
up: event.clone(),
};
Self::click(editor, &event, &position_map, &text_hitbox, window, cx);
}
}),
}
});
window.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
@@ -7088,6 +7185,7 @@ impl Element for EditorElement {
&line_layouts,
line_height,
scroll_pixel_position,
newest_selection_head,
editor_width,
&style,
window,
@@ -7836,8 +7934,8 @@ impl HighlightedRange {
};
let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
let mut path = gpui::Path::new(first_top_right - top_curve_width);
path.curve_to(first_top_right + curve_height, first_top_right);
let mut builder = gpui::PathBuilder::fill();
builder.curve_to(first_top_right + curve_height, first_top_right);
let mut iter = lines.iter().enumerate().peekable();
while let Some((ix, line)) = iter.next() {
@@ -7848,42 +7946,42 @@ impl HighlightedRange {
match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
Ordering::Equal => {
path.line_to(bottom_right);
builder.line_to(bottom_right);
}
Ordering::Less => {
let curve_width = curve_width(next_top_right.x, bottom_right.x);
path.line_to(bottom_right - curve_height);
builder.line_to(bottom_right - curve_height);
if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_right - curve_width, bottom_right);
builder.curve_to(bottom_right - curve_width, bottom_right);
}
path.line_to(next_top_right + curve_width);
builder.line_to(next_top_right + curve_width);
if self.corner_radius > Pixels::ZERO {
path.curve_to(next_top_right + curve_height, next_top_right);
builder.curve_to(next_top_right + curve_height, next_top_right);
}
}
Ordering::Greater => {
let curve_width = curve_width(bottom_right.x, next_top_right.x);
path.line_to(bottom_right - curve_height);
builder.line_to(bottom_right - curve_height);
if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_right + curve_width, bottom_right);
builder.curve_to(bottom_right + curve_width, bottom_right);
}
path.line_to(next_top_right - curve_width);
builder.line_to(next_top_right - curve_width);
if self.corner_radius > Pixels::ZERO {
path.curve_to(next_top_right + curve_height, next_top_right);
builder.curve_to(next_top_right + curve_height, next_top_right);
}
}
}
} else {
let curve_width = curve_width(line.start_x, line.end_x);
path.line_to(bottom_right - curve_height);
builder.line_to(bottom_right - curve_height);
if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_right - curve_width, bottom_right);
builder.curve_to(bottom_right - curve_width, bottom_right);
}
let bottom_left = point(line.start_x, bottom_right.y);
path.line_to(bottom_left + curve_width);
builder.line_to(bottom_left + curve_width);
if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_left - curve_height, bottom_left);
builder.curve_to(bottom_left - curve_height, bottom_left);
}
}
}
@@ -7891,24 +7989,26 @@ impl HighlightedRange {
if first_line.start_x > last_line.start_x {
let curve_width = curve_width(last_line.start_x, first_line.start_x);
let second_top_left = point(last_line.start_x, start_y + self.line_height);
path.line_to(second_top_left + curve_height);
builder.line_to(second_top_left + curve_height);
if self.corner_radius > Pixels::ZERO {
path.curve_to(second_top_left + curve_width, second_top_left);
builder.curve_to(second_top_left + curve_width, second_top_left);
}
let first_bottom_left = point(first_line.start_x, second_top_left.y);
path.line_to(first_bottom_left - curve_width);
builder.line_to(first_bottom_left - curve_width);
if self.corner_radius > Pixels::ZERO {
path.curve_to(first_bottom_left - curve_height, first_bottom_left);
builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
}
}
path.line_to(first_top_left + curve_height);
builder.line_to(first_top_left + curve_height);
if self.corner_radius > Pixels::ZERO {
path.curve_to(first_top_left + top_curve_width, first_top_left);
builder.curve_to(first_top_left + top_curve_width, first_top_left);
}
path.line_to(first_top_right - top_curve_width);
builder.line_to(first_top_right - top_curve_width);
window.paint_path(path, self.color);
if let Ok(path) = builder.build() {
window.paint_path(path, self.color);
}
}
}

View File

@@ -856,7 +856,8 @@ impl ProjectDiffEditor {
for (_, buffer, hunk_ranges) in excerpts_to_add {
let buffer_snapshot = buffer.read(cx).snapshot();
let max_point = buffer_snapshot.max_point();
let new_excerpts = multi_buffer.push_excerpts(
let new_excerpts = multi_buffer.insert_excerpts_after(
after_excerpt_id,
buffer,
hunk_ranges.into_iter().map(|range| {
let mut extended_point_range = range.to_point(&buffer_snapshot);

View File

@@ -38,8 +38,11 @@ use text::{BufferId, Selection};
use theme::{Theme, ThemeSettings};
use ui::{h_flex, prelude::*, IconDecorationKind, Label};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent};
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
use workspace::{
item::{BreadcrumbText, FollowEvent},
searchable::SearchOptions,
};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -255,12 +258,16 @@ impl FollowableItem for Editor {
match update {
proto::update_view::Variant::Editor(update) => match event {
EditorEvent::ExcerptsAdded { buffer, excerpts } => {
EditorEvent::ExcerptsAdded {
buffer,
predecessor,
excerpts,
} => {
let buffer_id = buffer.read(cx).remote_id();
let mut excerpts = excerpts.iter();
if let Some((id, range)) = excerpts.next() {
update.inserted_excerpts.push(proto::ExcerptInsertion {
previous_excerpt_id: None,
previous_excerpt_id: Some(predecessor.to_proto()),
excerpt: serialize_excerpt(buffer_id, id, range),
});
update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
@@ -390,7 +397,8 @@ async fn update_editor_from_message(
}
});
multibuffer.insert_excerpts_with_ids(
multibuffer.insert_excerpts_with_ids_after(
ExcerptId::from_proto(previous_excerpt_id),
buffer,
[excerpt]
.into_iter()
@@ -1035,10 +1043,10 @@ impl SerializableItem for Editor {
} => window.spawn(cx, |mut cx| {
let project = project.clone();
async move {
let language = if let Some(language_name) = language {
let language_registry =
project.update(&mut cx, |project, _| project.languages().clone())?;
let language_registry =
project.update(&mut cx, |project, _| project.languages().clone())?;
let language = if let Some(language_name) = language {
// We don't fail here, because we'd rather not set the language if the name changed
// than fail to restore the buffer.
language_registry
@@ -1056,6 +1064,7 @@ impl SerializableItem for Editor {
// Then set the text so that the dirty bit is set correctly
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language_registry(language_registry);
if let Some(language) = language {
buffer.set_language(Some(language), cx);
}
@@ -1318,6 +1327,28 @@ impl SearchableItem for Editor {
}
}
fn supported_options(&self) -> SearchOptions {
if self.in_project_search {
SearchOptions {
case: true,
word: true,
regex: true,
replacement: false,
selection: false,
find_in_results: true,
}
} else {
SearchOptions {
case: true,
word: true,
regex: true,
replacement: true,
selection: true,
find_in_results: false,
}
}
}
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
let snapshot = &self.snapshot(window, cx).buffer_snapshot;

View File

@@ -87,8 +87,8 @@ define_connection!(
// mtime_seconds: Option<i64>,
// mtime_nanos: Option<i32>,
// )
pub static ref DB: EditorDb<WorkspaceDb> =
&[sql! (
pub static ref DB: EditorDb<WorkspaceDb> = &[
sql! (
CREATE TABLE editors(
item_id INTEGER NOT NULL,
workspace_id INTEGER NOT NULL,
@@ -134,7 +134,7 @@ define_connection!(
ALTER TABLE editors ADD COLUMN mtime_seconds INTEGER DEFAULT NULL;
ALTER TABLE editors ADD COLUMN mtime_nanos INTEGER DEFAULT NULL;
),
];
];
);
impl EditorDb {

View File

@@ -167,6 +167,38 @@ async fn copy_extension_resources(
}
}
if !manifest.icon_themes.is_empty() {
let output_icon_themes_dir = output_dir.join("icon_themes");
fs::create_dir_all(&output_icon_themes_dir)?;
for icon_theme_path in &manifest.icon_themes {
fs::copy(
extension_path.join(icon_theme_path),
output_icon_themes_dir.join(
icon_theme_path
.file_name()
.ok_or_else(|| anyhow!("invalid icon theme path"))?,
),
)
.with_context(|| {
format!("failed to copy icon theme '{}'", icon_theme_path.display())
})?;
}
let output_icons_dir = output_dir.join("icons");
fs::create_dir_all(&output_icons_dir)?;
copy_recursive(
fs.as_ref(),
&extension_path.join("icons"),
&output_icons_dir,
CopyOptions {
overwrite: true,
ignore_if_exists: false,
},
)
.await
.with_context(|| "failed to copy icons")?;
}
if !manifest.languages.is_empty() {
let output_languages_dir = output_dir.join("languages");
fs::create_dir_all(&output_languages_dir)?;

View File

@@ -444,6 +444,23 @@ impl ExtensionStore {
.filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
}
/// Returns the names of icon themes provided by extensions.
pub fn extension_icon_themes<'a>(
&'a self,
extension_id: &'a str,
) -> impl Iterator<Item = &'a Arc<str>> {
self.extension_index
.icon_themes
.iter()
.filter_map(|(name, icon_theme)| {
icon_theme
.extension
.as_ref()
.eq(extension_id)
.then_some(name)
})
}
pub fn fetch_extensions(
&self,
search: Option<&str>,

View File

@@ -295,6 +295,25 @@ impl ExtensionsPage {
);
})
.ok();
return;
}
let icon_themes = extension_store
.extension_icon_themes(extension_id)
.map(|name| name.to_string())
.collect::<Vec<_>>();
if !icon_themes.is_empty() {
workspace
.update(cx, |_workspace, cx| {
window.dispatch_action(
zed_actions::icon_theme_selector::Toggle {
themes_filter: Some(icon_themes),
}
.boxed_clone(),
cx,
);
})
.ok();
}
}

View File

@@ -41,16 +41,6 @@ impl FeatureFlag for Assistant2FeatureFlag {
const NAME: &'static str = "assistant2";
}
pub struct ToolUseFeatureFlag;
impl FeatureFlag for ToolUseFeatureFlag {
const NAME: &'static str = "assistant-tool-use";
fn enabled_for_staff() -> bool {
false
}
}
pub struct PredictEditsFeatureFlag;
impl FeatureFlag for PredictEditsFeatureFlag {
const NAME: &'static str = "predict-edits";

View File

@@ -22,7 +22,7 @@ const fn zed_repo_url() -> &'static str {
}
fn request_feature_url() -> String {
"https://github.com/zed-industries/zed/issues/new?template=0_feature_request.yml".to_string()
"https://github.com/zed-industries/zed/discussions/new/choose".to_string()
}
fn file_bug_report_url(specs: &SystemSpecs) -> String {

View File

@@ -194,6 +194,7 @@ impl FeedbackModal {
editor.set_show_inline_completions(Some(false), window, cx);
editor.set_vertical_scroll_margin(5, cx);
editor.set_use_modal_editing(false);
editor.set_soft_wrap();
editor
});

View File

@@ -1,173 +0,0 @@
use anyhow::{anyhow, Result};
use futures::AsyncReadExt;
use http_client::{http::HeaderMap, AsyncBody, HttpClient, Method, Request as HttpRequest};
use serde::{Deserialize, Serialize};
pub const FIREWORKS_API_URL: &str = "https://api.openai.com/v1";
#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionRequest {
pub model: String,
pub prompt: String,
pub max_tokens: u32,
pub temperature: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prediction: Option<Prediction>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rewrite_speculation: Option<bool>,
}
#[derive(Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Prediction {
Content { content: String },
}
#[derive(Debug)]
pub struct Response {
pub completion: CompletionResponse,
pub headers: Headers,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CompletionResponse {
pub id: String,
pub object: String,
pub created: u64,
pub model: String,
pub choices: Vec<CompletionChoice>,
pub usage: Usage,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CompletionChoice {
pub text: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Headers {
pub server_processing_time: Option<f64>,
pub request_id: Option<String>,
pub prompt_tokens: Option<u32>,
pub speculation_generated_tokens: Option<u32>,
pub cached_prompt_tokens: Option<u32>,
pub backend_host: Option<String>,
pub num_concurrent_requests: Option<u32>,
pub deployment: Option<String>,
pub tokenizer_queue_duration: Option<f64>,
pub tokenizer_duration: Option<f64>,
pub prefill_queue_duration: Option<f64>,
pub prefill_duration: Option<f64>,
pub generation_queue_duration: Option<f64>,
}
impl Headers {
pub fn parse(headers: &HeaderMap) -> Self {
Headers {
request_id: headers
.get("x-request-id")
.and_then(|v| v.to_str().ok())
.map(String::from),
server_processing_time: headers
.get("fireworks-server-processing-time")
.and_then(|v| v.to_str().ok()?.parse().ok()),
prompt_tokens: headers
.get("fireworks-prompt-tokens")
.and_then(|v| v.to_str().ok()?.parse().ok()),
speculation_generated_tokens: headers
.get("fireworks-speculation-generated-tokens")
.and_then(|v| v.to_str().ok()?.parse().ok()),
cached_prompt_tokens: headers
.get("fireworks-cached-prompt-tokens")
.and_then(|v| v.to_str().ok()?.parse().ok()),
backend_host: headers
.get("fireworks-backend-host")
.and_then(|v| v.to_str().ok())
.map(String::from),
num_concurrent_requests: headers
.get("fireworks-num-concurrent-requests")
.and_then(|v| v.to_str().ok()?.parse().ok()),
deployment: headers
.get("fireworks-deployment")
.and_then(|v| v.to_str().ok())
.map(String::from),
tokenizer_queue_duration: headers
.get("fireworks-tokenizer-queue-duration")
.and_then(|v| v.to_str().ok()?.parse().ok()),
tokenizer_duration: headers
.get("fireworks-tokenizer-duration")
.and_then(|v| v.to_str().ok()?.parse().ok()),
prefill_queue_duration: headers
.get("fireworks-prefill-queue-duration")
.and_then(|v| v.to_str().ok()?.parse().ok()),
prefill_duration: headers
.get("fireworks-prefill-duration")
.and_then(|v| v.to_str().ok()?.parse().ok()),
generation_queue_duration: headers
.get("fireworks-generation-queue-duration")
.and_then(|v| v.to_str().ok()?.parse().ok()),
}
}
}
pub async fn complete(
client: &dyn HttpClient,
api_url: &str,
api_key: &str,
request: CompletionRequest,
) -> Result<Response> {
let uri = format!("{api_url}/completions");
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key));
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
let mut response = client.send(request).await?;
if response.status().is_success() {
let headers = Headers::parse(response.headers());
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
Ok(Response {
completion: serde_json::from_str(&body)?,
headers,
})
} else {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
#[derive(Deserialize)]
struct FireworksResponse {
error: FireworksError,
}
#[derive(Deserialize)]
struct FireworksError {
message: String,
}
match serde_json::from_str::<FireworksResponse>(&body) {
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
"Failed to connect to Fireworks API: {}",
response.error.message,
)),
_ => Err(anyhow!(
"Failed to connect to Fireworks API: {} {}",
response.status(),
body,
)),
}
}
}

View File

@@ -398,8 +398,15 @@ mod tests {
assert!(event.flags.contains(StreamFlags::ITEM_CREATED));
fs::remove_file(path.join("existing-file-5")).unwrap();
let events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
let event = events.last().unwrap();
let mut events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
let mut event = events.last().unwrap();
// we see this duplicate about 1/100 test runs.
if event.path == path.join("new-file")
&& event.flags.contains(StreamFlags::ITEM_CREATED)
{
events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
event = events.last().unwrap();
}
assert_eq!(event.path, path.join("existing-file-5"));
assert!(event.flags.contains(StreamFlags::ITEM_REMOVED));
drop(handle);

View File

@@ -827,6 +827,7 @@ impl GitPanel {
pub fn render_panel_header(
&self,
window: &mut Window,
has_write_access: bool,
cx: &mut Context<Self>,
) -> impl IntoElement {
let focus_handle = self.focus_handle(cx).clone();
@@ -869,7 +870,7 @@ impl GitPanel {
} else {
Tooltip::text("Stage all changes")
})
.disabled(entry_count == 0)
.disabled(!has_write_access || entry_count == 0)
.on_click(cx.listener(
move |git_panel, _, window, cx| match all_staged {
true => git_panel.unstage_all(&UnstageAll, window, cx),
@@ -960,7 +961,11 @@ impl GitPanel {
)
}
pub fn render_commit_editor(&self, cx: &Context<Self>) -> impl IntoElement {
pub fn render_commit_editor(
&self,
has_write_access: bool,
cx: &Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone();
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let (can_commit, can_commit_all) =
@@ -968,8 +973,8 @@ impl GitPanel {
.as_ref()
.map_or((false, false), |active_repository| {
(
active_repository.can_commit(false, cx),
active_repository.can_commit(true, cx),
has_write_access && active_repository.can_commit(false, cx),
has_write_access && active_repository.can_commit(true, cx),
)
});
@@ -1107,7 +1112,7 @@ impl GitPanel {
)
}
fn render_entries(&self, cx: &mut Context<Self>) -> impl IntoElement {
fn render_entries(&self, has_write_access: bool, cx: &mut Context<Self>) -> impl IntoElement {
let entry_count = self.visible_entries.len();
h_flex()
@@ -1118,7 +1123,7 @@ impl GitPanel {
move |git_panel, range, _window, cx| {
let mut items = Vec::with_capacity(range.end - range.start);
git_panel.for_each_visible_entry(range, cx, |ix, details, cx| {
items.push(git_panel.render_entry(ix, details, cx));
items.push(git_panel.render_entry(ix, details, has_write_access, cx));
});
items
}
@@ -1136,6 +1141,7 @@ impl GitPanel {
&self,
ix: usize,
entry_details: GitListEntry,
has_write_access: bool,
cx: &Context<Self>,
) -> impl IntoElement {
let repo_path = entry_details.repo_path.clone();
@@ -1215,6 +1221,7 @@ impl GitPanel {
.is_staged
.map_or(ToggleState::Indeterminate, ToggleState::from),
)
.disabled(!has_write_access)
.fill()
.elevation(ElevationIndex::Surface)
.on_click({
@@ -1296,19 +1303,23 @@ impl Render for GitPanel {
.map_or(false, |active_repository| {
active_repository.entry_count() > 0
});
let has_co_authors = self
let room = self
.workspace
.upgrade()
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned())
.map(|room| {
let room = room.read(cx);
room.local_participant().can_write()
&& room
.remote_participants()
.values()
.any(|remote_participant| remote_participant.can_write())
})
.unwrap_or(false);
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
let has_write_access = room
.as_ref()
.map_or(true, |room| room.read(cx).local_participant().can_write());
let has_co_authors = room.map_or(false, |room| {
has_write_access
&& room
.read(cx)
.remote_participants()
.values()
.any(|remote_participant| remote_participant.can_write())
});
v_flex()
.id("git_panel")
@@ -1366,15 +1377,15 @@ impl Render for GitPanel {
.font_buffer(cx)
.py_1()
.bg(ElevationIndex::Surface.bg(cx))
.child(self.render_panel_header(window, cx))
.child(self.render_panel_header(window, has_write_access, cx))
.child(self.render_divider(cx))
.child(if has_entries {
self.render_entries(cx).into_any_element()
self.render_entries(has_write_access, cx).into_any_element()
} else {
self.render_empty_state(cx).into_any_element()
})
.child(self.render_divider(cx))
.child(self.render_commit_editor(cx))
.child(self.render_commit_editor(has_write_access, cx))
}
}

View File

@@ -108,22 +108,7 @@ thiserror.workspace = true
util.workspace = true
uuid.workspace = true
waker-fn = "1.2.0"
[dev-dependencies]
backtrace = "0.3"
collections = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
rand.workspace = true
util = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0"
[target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.70.0"
cbindgen = { version = "0.28.0", default-features = false }
lyon = "1.0"
[target.'cfg(target_os = "macos")'.dependencies]
block = "0.1"
@@ -212,6 +197,30 @@ flume = "0.11"
rand.workspace = true
windows.workspace = true
windows-core = "0.58"
[dev-dependencies]
backtrace = "0.3"
collections = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
rand.workspace = true
util = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
lyon = { version = "1.0", features = ["extra"] }
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0"
naga.workspace = true
[target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.70.0"
cbindgen = { version = "0.28.0", default-features = false }
naga.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
naga.workspace = true
[[example]]
name = "hello_world"
path = "examples/hello_world.rs"

View File

@@ -8,6 +8,10 @@ use std::env;
fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
println!("cargo::rustc-check-cfg=cfg(gles)");
#[cfg(any(not(target_os = "macos"), feature = "macos-blade"))]
check_wgsl_shaders();
match target.as_deref() {
Ok("macos") => {
#[cfg(target_os = "macos")]
@@ -27,6 +31,28 @@ fn main() {
};
}
#[allow(dead_code)]
fn check_wgsl_shaders() {
use std::path::PathBuf;
use std::process;
use std::str::FromStr;
let shader_source_path = "./src/platform/blade/shaders.wgsl";
let shader_path = PathBuf::from_str(shader_source_path).unwrap();
println!("cargo:rerun-if-changed={}", &shader_path.display());
let shader_source = std::fs::read_to_string(&shader_path).unwrap();
match naga::front::wgsl::parse_str(&shader_source) {
Ok(_) => {
// All clear
}
Err(e) => {
eprintln!("WGSL shader compilation failed:\n{}", e);
process::exit(1);
}
}
}
#[cfg(target_os = "macos")]
mod macos {
use std::{

View File

@@ -218,13 +218,17 @@ impl Render for GradientViewer {
let height = square_bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(30.);
let mut path = gpui::Path::new(square_bounds.bottom_left());
path.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
path.line_to(
let mut builder = gpui::PathBuilder::fill();
builder.move_to(square_bounds.bottom_left());
builder
.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
builder.line_to(
square_bounds.top_right() + point(-horizontal_offset, vertical_offset),
);
path.line_to(square_bounds.bottom_right());
path.line_to(square_bounds.bottom_left());
builder.line_to(square_bounds.bottom_right());
builder.line_to(square_bounds.bottom_left());
let path = builder.build().unwrap();
window.paint_path(
path,
linear_gradient(

View File

@@ -1,46 +1,62 @@
use gpui::{
canvas, div, point, prelude::*, px, size, App, Application, Bounds, Context, MouseDownEvent,
Path, Pixels, Point, Render, Window, WindowOptions,
canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size, Application,
Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
Point, Render, StrokeOptions, Window, WindowOptions,
};
struct PaintingViewer {
default_lines: Vec<Path<Pixels>>,
default_lines: Vec<(Path<Pixels>, Background)>,
lines: Vec<Vec<Point<Pixels>>>,
start: Point<Pixels>,
_painting: bool,
}
impl PaintingViewer {
fn new() -> Self {
fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
let mut lines = vec![];
// draw a line
let mut path = Path::new(point(px(50.), px(180.)));
path.line_to(point(px(100.), px(120.)));
// go back to close the path
path.line_to(point(px(100.), px(121.)));
path.line_to(point(px(50.), px(181.)));
lines.push(path);
// draw a Rust logo
let mut builder = lyon::path::Path::svg_builder();
lyon::extra::rust_logo::build_logo_path(&mut builder);
// move down the Path
let mut builder: PathBuilder = builder.into();
builder.translate(point(px(10.), px(100.)));
builder.scale(0.9);
let path = builder.build().unwrap();
lines.push((path, gpui::black().into()));
// draw a lightening bolt ⚡
let mut path = Path::new(point(px(150.), px(200.)));
path.line_to(point(px(200.), px(125.)));
path.line_to(point(px(200.), px(175.)));
path.line_to(point(px(250.), px(100.)));
lines.push(path);
let mut builder = PathBuilder::fill();
builder.move_to(point(px(150.), px(200.)));
builder.line_to(point(px(200.), px(125.)));
builder.line_to(point(px(200.), px(175.)));
builder.line_to(point(px(250.), px(100.)));
let path = builder.build().unwrap();
lines.push((path, rgb(0x1d4ed8).into()));
// draw a ⭐
let mut path = Path::new(point(px(350.), px(100.)));
path.line_to(point(px(370.), px(160.)));
path.line_to(point(px(430.), px(160.)));
path.line_to(point(px(380.), px(200.)));
path.line_to(point(px(400.), px(260.)));
path.line_to(point(px(350.), px(220.)));
path.line_to(point(px(300.), px(260.)));
path.line_to(point(px(320.), px(200.)));
path.line_to(point(px(270.), px(160.)));
path.line_to(point(px(330.), px(160.)));
path.line_to(point(px(350.), px(100.)));
lines.push(path);
let mut builder = PathBuilder::fill();
builder.move_to(point(px(350.), px(100.)));
builder.line_to(point(px(370.), px(160.)));
builder.line_to(point(px(430.), px(160.)));
builder.line_to(point(px(380.), px(200.)));
builder.line_to(point(px(400.), px(260.)));
builder.line_to(point(px(350.), px(220.)));
builder.line_to(point(px(300.), px(260.)));
builder.line_to(point(px(320.), px(200.)));
builder.line_to(point(px(270.), px(160.)));
builder.line_to(point(px(330.), px(160.)));
builder.line_to(point(px(350.), px(100.)));
let path = builder.build().unwrap();
lines.push((
path,
linear_gradient(
180.,
linear_color_stop(rgb(0xFACC15), 0.7),
linear_color_stop(rgb(0xD56D0C), 1.),
)
.color_space(ColorSpace::Oklab),
));
let square_bounds = Bounds {
origin: point(px(450.), px(100.)),
@@ -49,18 +65,42 @@ impl PaintingViewer {
let height = square_bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(30.);
let mut path = Path::new(square_bounds.bottom_left());
path.curve_to(
let mut builder = PathBuilder::fill();
builder.move_to(square_bounds.bottom_left());
builder.curve_to(
square_bounds.origin + point(horizontal_offset, vertical_offset),
square_bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
builder.curve_to(
square_bounds.bottom_right(),
square_bounds.top_right() + point(px(0.0), vertical_offset),
);
path.line_to(square_bounds.bottom_left());
lines.push(path);
builder.line_to(square_bounds.bottom_left());
let path = builder.build().unwrap();
lines.push((
path,
linear_gradient(
180.,
linear_color_stop(gpui::blue(), 0.4),
linear_color_stop(gpui::red(), 1.),
),
));
// draw a wave
let options = StrokeOptions::default()
.with_line_width(1.)
.with_line_join(lyon::path::LineJoin::Bevel);
let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
builder.move_to(point(px(40.), px(320.)));
for i in 0..50 {
builder.line_to(point(
px(40.0 + i as f32 * 10.0),
px(320.0 + (i as f32 * 10.0).sin() * 40.0),
));
}
let path = builder.build().unwrap();
lines.push((path, gpui::green().into()));
Self {
default_lines: lines.clone(),
@@ -115,27 +155,28 @@ impl Render for PaintingViewer {
canvas(
move |_, _, _| {},
move |_, _, window, _| {
const STROKE_WIDTH: Pixels = px(2.0);
for path in default_lines {
window.paint_path(path, gpui::black());
for (path, color) in default_lines {
window.paint_path(path, color);
}
for points in lines {
let mut path = Path::new(points[0]);
for p in points.iter().skip(1) {
path.line_to(*p);
if points.len() < 2 {
continue;
}
let mut last = points.last().unwrap();
for p in points.iter().rev() {
let mut offset_x = px(0.);
if last.x == p.x {
offset_x = STROKE_WIDTH;
let mut builder = PathBuilder::stroke(px(1.));
for (i, p) in points.into_iter().enumerate() {
if i == 0 {
builder.move_to(p);
} else {
builder.line_to(p);
}
path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH));
last = p;
}
window.paint_path(path, gpui::black());
if let Ok(path) = builder.build() {
window.paint_path(path, gpui::black());
}
}
},
)
@@ -185,13 +226,13 @@ impl Render for PaintingViewer {
}
fn main() {
Application::new().run(|cx: &mut App| {
Application::new().run(|cx| {
cx.open_window(
WindowOptions {
focus: true,
..Default::default()
},
|_, cx| cx.new(|_| PaintingViewer::new()),
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
)
.unwrap();
cx.activate(true);

View File

@@ -1,6 +1,6 @@
use gpui::{
div, prelude::*, px, size, App, Application, Bounds, Context, Window, WindowBounds,
WindowOptions,
div, prelude::*, px, size, App, Application, Bounds, Context, TextOverflow, Window,
WindowBounds, WindowOptions,
};
struct HelloWorld {}
@@ -20,6 +20,7 @@ impl Render for HelloWorld {
div()
.flex()
.flex_row()
.flex_shrink_0()
.gap_2()
.child(
div()
@@ -49,29 +50,53 @@ impl Render for HelloWorld {
)
.child(
div()
.flex_shrink_0()
.text_xl()
.overflow_hidden()
.text_ellipsis()
.truncate()
.border_1()
.border_color(gpui::red())
.border_color(gpui::blue())
.child("ELLIPSIS: ".to_owned() + text),
)
.child(
div()
.flex_shrink_0()
.text_xl()
.overflow_hidden()
.truncate()
.text_ellipsis()
.line_clamp(2)
.border_1()
.border_color(gpui::blue())
.child("ELLIPSIS 2 lines: ".to_owned() + text),
)
.child(
div()
.flex_shrink_0()
.text_xl()
.overflow_hidden()
.text_overflow(TextOverflow::Ellipsis(""))
.border_1()
.border_color(gpui::green())
.child("TRUNCATE: ".to_owned() + text),
)
.child(
div()
.flex_shrink_0()
.text_xl()
.overflow_hidden()
.text_overflow(TextOverflow::Ellipsis(""))
.line_clamp(3)
.border_1()
.border_color(gpui::green())
.child("TRUNCATE 3 lines: ".to_owned() + text),
)
.child(
div()
.flex_shrink_0()
.text_xl()
.whitespace_nowrap()
.overflow_hidden()
.border_1()
.border_color(gpui::blue())
.border_color(gpui::black())
.child("NOWRAP: ".to_owned() + text),
)
.child(div().text_xl().w_full().child(text))
@@ -80,7 +105,7 @@ impl Render for HelloWorld {
fn main() {
Application::new().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(600.0), px(480.0)), cx);
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
@@ -89,5 +114,6 @@ fn main() {
|_, cx| cx.new(|_| HelloWorld {}),
)
.unwrap();
cx.activate(true);
});
}

View File

@@ -22,9 +22,9 @@ use slotmap::SlotMap;
pub use async_context::*;
use collections::{FxHashMap, FxHashSet, HashMap, VecDeque};
pub use context::*;
pub use entity_map::*;
use http_client::HttpClient;
pub use model_context::*;
#[cfg(any(test, feature = "test-support"))]
pub use test_context::*;
use util::ResultExt;
@@ -41,8 +41,8 @@ use crate::{
};
mod async_context;
mod context;
mod entity_map;
mod model_context;
#[cfg(any(test, feature = "test-support"))]
mod test_context;
@@ -1667,6 +1667,21 @@ impl AppContext for App {
Ok(read(view, self))
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
let mut g = self.global::<G>();
callback(&g, self)
}
}
/// These effects are processed at the end of each application update cycle.

View File

@@ -104,6 +104,22 @@ impl AppContext for AsyncApp {
let lock = app.borrow();
lock.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
Ok(lock.update(|this| this.read_global(callback)))
}
}
impl AsyncApp {
@@ -367,6 +383,20 @@ impl AppContext for AsyncWindowContext {
{
self.app.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.app.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Result<R>
where
G: Global,
{
self.app.read_global(callback)
}
}
impl VisualContext for AsyncWindowContext {

View File

@@ -1,7 +1,7 @@
use crate::{
AnyView, AnyWindowHandle, App, AppContext, AsyncApp, DispatchPhase, Effect, EntityId,
EventEmitter, FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation,
SubscriberSet, Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
AnyView, AnyWindowHandle, AppContext, AsyncApp, DispatchPhase, Effect, EntityId, EventEmitter,
FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation, SubscriberSet,
Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
};
use anyhow::Result;
use derive_more::{Deref, DerefMut};
@@ -13,7 +13,7 @@ use std::{
sync::Arc,
};
use super::{AsyncWindowContext, Entity, KeystrokeEvent};
use super::{App, AsyncWindowContext, Entity, KeystrokeEvent};
/// The app context, with specialized behavior for the given model.
#[derive(Deref, DerefMut)]
@@ -717,6 +717,20 @@ impl<'a, T> AppContext for Context<'a, T> {
{
self.app.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.app.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
self.app.read_global(callback)
}
}
impl<T> Borrow<App> for Context<'_, T> {

View File

@@ -94,6 +94,21 @@ impl AppContext for TestAppContext {
let app = self.app.borrow();
app.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
let app = self.app.borrow();
app.read_global(callback)
}
}
impl TestAppContext {
@@ -906,6 +921,20 @@ impl AppContext for VisualTestContext {
{
self.cx.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.cx.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
self.cx.read_global(callback)
}
}
impl VisualContext for VisualTestContext {

View File

@@ -1677,6 +1677,7 @@ impl Interactivity {
FONT_SIZE,
&[window.text_style().to_run(str_len)],
None,
None,
)
.ok()
.and_then(|mut text| text.pop())

View File

@@ -114,9 +114,9 @@ impl From<Arc<Image>> for ImageSource {
}
}
impl<
F: Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>> + 'static,
> From<F> for ImageSource
impl<F> From<F> for ImageSource
where
F: Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>> + 'static,
{
fn from(value: F) -> Self {
Self::Custom(Arc::new(value))

View File

@@ -2,7 +2,8 @@ use crate::{
register_tooltip_mouse_handlers, set_tooltip_on_window, ActiveTooltip, AnyView, App, Bounds,
DispatchPhase, Element, ElementId, GlobalElementId, HighlightStyle, Hitbox, IntoElement,
LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size,
TextRun, TextStyle, TooltipId, Truncate, WhiteSpace, Window, WrappedLine, WrappedLineLayout,
TextOverflow, TextRun, TextStyle, TooltipId, WhiteSpace, Window, WrappedLine,
WrappedLineLayout,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
@@ -255,8 +256,6 @@ struct TextLayoutInner {
bounds: Option<Bounds<Pixels>>,
}
const ELLIPSIS: &str = "";
impl TextLayout {
fn lock(&self) -> MutexGuard<Option<TextLayoutInner>> {
self.0.lock()
@@ -294,19 +293,22 @@ impl TextLayout {
None
};
let (truncate_width, ellipsis) = if let Some(truncate) = text_style.truncate {
let width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
_ => None,
});
let (truncate_width, ellipsis) =
if let Some(text_overflow) = text_style.text_overflow {
let width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => match text_style.line_clamp {
Some(max_lines) => Some(x * max_lines),
None => Some(x),
},
_ => None,
});
match truncate {
Truncate::Truncate => (width, None),
Truncate::Ellipsis => (width, Some(ELLIPSIS)),
}
} else {
(None, None)
};
match text_overflow {
TextOverflow::Ellipsis(s) => (width, Some(s)),
}
} else {
(None, None)
};
if let Some(text_layout) = element_state.0.lock().as_ref() {
if text_layout.size.is_some()
@@ -326,7 +328,11 @@ impl TextLayout {
let Some(lines) = window
.text_system()
.shape_text(
text, font_size, &runs, wrap_width, // Wrap if we know the width.
text,
font_size,
&runs,
wrap_width, // Wrap if we know the width.
text_style.line_clamp, // Limit the number of lines if line_clamp is set.
)
.log_err()
else {

View File

@@ -82,6 +82,7 @@ mod input;
mod interactive;
mod key_dispatch;
mod keymap;
mod path_builder;
mod platform;
pub mod prelude;
mod scene;
@@ -135,6 +136,7 @@ pub use input::*;
pub use interactive::*;
use key_dispatch::*;
pub use keymap::*;
pub use path_builder::*;
pub use platform::*;
pub use refineable::*;
pub use scene::*;
@@ -153,7 +155,7 @@ pub use util::arc_cow::ArcCow;
pub use view::*;
pub use window::*;
use std::{any::Any, borrow::BorrowMut};
use std::{any::Any, borrow::BorrowMut, future::Future};
use taffy::TaffyLayoutEngine;
/// The context trait, allows the different contexts in GPUI to be used
@@ -213,6 +215,16 @@ pub trait AppContext {
) -> Result<R>
where
T: 'static;
/// Spawn a future on a background thread
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static;
/// Read a global from this app context
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global;
}
/// Returned by [Context::reserve_entity] to later be passed to [Context::insert_model].

View File

@@ -147,6 +147,20 @@ pub struct ClickEvent {
pub up: MouseUpEvent,
}
impl ClickEvent {
/// Returns the modifiers that were held down during both the
/// mouse down and mouse up events
pub fn modifiers(&self) -> Modifiers {
Modifiers {
control: self.up.modifiers.control && self.down.modifiers.control,
alt: self.up.modifiers.alt && self.down.modifiers.alt,
shift: self.up.modifiers.shift && self.down.modifiers.shift,
platform: self.up.modifiers.platform && self.down.modifiers.platform,
function: self.up.modifiers.function && self.down.modifiers.function,
}
}
}
/// An enum representing the mouse button that was pressed.
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton {

View File

@@ -0,0 +1,241 @@
use anyhow::Error;
use etagere::euclid::Vector2D;
use lyon::geom::Angle;
use lyon::tessellation::{
BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
};
pub use lyon::math::Transform;
pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
use crate::{point, px, Path, Pixels, Point};
/// Style of the PathBuilder
pub enum PathStyle {
/// Stroke style
Stroke(StrokeOptions),
/// Fill style
Fill(FillOptions),
}
/// A [`Path`] builder.
pub struct PathBuilder {
raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
transform: Option<lyon::math::Transform>,
/// PathStyle of the PathBuilder
pub style: PathStyle,
}
impl From<lyon::path::Builder> for PathBuilder {
fn from(builder: lyon::path::Builder) -> Self {
Self {
raw: builder.with_svg(),
..Default::default()
}
}
}
impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
Self {
raw,
..Default::default()
}
}
}
impl From<lyon::math::Point> for Point<Pixels> {
fn from(p: lyon::math::Point) -> Self {
point(px(p.x), px(p.y))
}
}
impl From<Point<Pixels>> for lyon::math::Point {
fn from(p: Point<Pixels>) -> Self {
lyon::math::point(p.x.0, p.y.0)
}
}
impl Default for PathBuilder {
fn default() -> Self {
Self {
raw: lyon::path::Path::builder().with_svg(),
style: PathStyle::Fill(FillOptions::default()),
transform: None,
}
}
}
impl PathBuilder {
/// Creates a new [`PathBuilder`] to build a Stroke path.
pub fn stroke(width: Pixels) -> Self {
Self {
style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
..Self::default()
}
}
/// Creates a new [`PathBuilder`] to build a Fill path.
pub fn fill() -> Self {
Self::default()
}
/// Sets the style of the [`PathBuilder`].
pub fn with_style(self, style: PathStyle) -> Self {
Self { style, ..self }
}
/// Move the current point to the given point.
#[inline]
pub fn move_to(&mut self, to: Point<Pixels>) {
self.raw.move_to(to.into());
}
/// Draw a straight line from the current point to the given point.
#[inline]
pub fn line_to(&mut self, to: Point<Pixels>) {
self.raw.line_to(to.into());
}
/// Draw a curve from the current point to the given point, using the given control point.
#[inline]
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.raw.quadratic_bezier_to(ctrl.into(), to.into());
}
/// Adds a cubic Bézier to the [`Path`] given its two control points
/// and its end point.
#[inline]
pub fn cubic_bezier_to(
&mut self,
to: Point<Pixels>,
control_a: Point<Pixels>,
control_b: Point<Pixels>,
) {
self.raw
.cubic_bezier_to(control_a.into(), control_b.into(), to.into());
}
/// Close the current sub-path.
#[inline]
pub fn close(&mut self) {
self.raw.close();
}
/// Applies a transform to the path.
#[inline]
pub fn transform(&mut self, transform: Transform) {
self.transform = Some(transform);
}
/// Applies a translation to the path.
#[inline]
pub fn translate(&mut self, to: Point<Pixels>) {
if let Some(transform) = self.transform {
self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
} else {
self.transform = Some(Transform::translation(to.x.0, to.y.0))
}
}
/// Applies a scale to the path.
#[inline]
pub fn scale(&mut self, scale: f32) {
if let Some(transform) = self.transform {
self.transform = Some(transform.then_scale(scale, scale));
} else {
self.transform = Some(Transform::scale(scale, scale));
}
}
/// Applies a rotation to the path.
///
/// The `angle` is in degrees value in the range 0.0 to 360.0.
#[inline]
pub fn rotate(&mut self, angle: f32) {
let radians = angle.to_radians();
if let Some(transform) = self.transform {
self.transform = Some(transform.then_rotate(Angle::radians(radians)));
} else {
self.transform = Some(Transform::rotation(Angle::radians(radians)));
}
}
/// Builds into a [`Path`].
#[inline]
pub fn build(self) -> Result<Path<Pixels>, Error> {
let path = if let Some(transform) = self.transform {
self.raw.build().transformed(&transform)
} else {
self.raw.build()
};
match self.style {
PathStyle::Stroke(options) => Self::tessellate_stroke(&path, &options),
PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
}
}
fn tessellate_fill(
path: &lyon::path::Path,
options: &FillOptions,
) -> Result<Path<Pixels>, Error> {
// Will contain the result of the tessellation.
let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
let mut tessellator = FillTessellator::new();
// Compute the tessellation.
tessellator.tessellate_path(
path,
options,
&mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
)?;
Ok(Self::build_path(buf))
}
fn tessellate_stroke(
path: &lyon::path::Path,
options: &StrokeOptions,
) -> Result<Path<Pixels>, Error> {
// Will contain the result of the tessellation.
let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
let mut tessellator = StrokeTessellator::new();
// Compute the tessellation.
tessellator.tessellate_path(
path,
options,
&mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
)?;
Ok(Self::build_path(buf))
}
/// Builds a [`Path`] from a [`lyon::VertexBuffers`].
pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
if buf.vertices.is_empty() {
return Path::new(Point::default());
}
let first_point = buf.vertices[0];
let mut path = Path::new(first_point.into());
for i in 0..buf.indices.len() / 3 {
let i0 = buf.indices[i * 3] as usize;
let i1 = buf.indices[i * 3 + 1] as usize;
let i2 = buf.indices[i * 3 + 2] as usize;
let v0 = buf.vertices[i0];
let v1 = buf.vertices[i1];
let v2 = buf.vertices[i2];
path.push_triangle(
(v0.into(), v1.into(), v2.into()),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
path
}
}

View File

@@ -432,6 +432,9 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
Decorations::Server
}
fn set_app_id(&mut self, _app_id: &str) {}
fn map_window(&mut self) -> anyhow::Result<()> {
Ok(())
}
fn window_controls(&self) -> WindowControls {
WindowControls::default()
}

View File

@@ -27,6 +27,7 @@ struct BladeAtlasState {
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
initializations: Vec<AtlasTextureId>,
uploads: Vec<PendingUpload>,
path_sample_count: u32,
}
#[cfg(gles)]
@@ -42,10 +43,11 @@ impl BladeAtlasState {
pub struct BladeTextureInfo {
pub size: gpu::Extent,
pub raw_view: gpu::TextureView,
pub msaa_view: Option<gpu::TextureView>,
}
impl BladeAtlas {
pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu),
upload_belt: BufferBelt::new(BufferBeltDescriptor {
@@ -57,6 +59,7 @@ impl BladeAtlas {
tiles_by_key: Default::default(),
initializations: Vec::new(),
uploads: Vec::new(),
path_sample_count,
}))
}
@@ -106,6 +109,7 @@ impl BladeAtlas {
depth: 1,
},
raw_view: texture.raw_view,
msaa_view: texture.msaa_view,
}
}
}
@@ -204,6 +208,39 @@ impl BladeAtlasState {
}
}
// We currently only enable MSAA for path textures.
let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
let msaa = self.gpu.create_texture(gpu::TextureDesc {
name: "msaa path texture",
format,
size: gpu::Extent {
width: size.width.into(),
height: size.height.into(),
depth: 1,
},
array_layer_count: 1,
mip_level_count: 1,
sample_count: self.path_sample_count,
dimension: gpu::TextureDimension::D2,
usage: gpu::TextureUsage::TARGET,
});
(
Some(msaa),
Some(self.gpu.create_texture_view(
msaa,
gpu::TextureViewDesc {
name: "msaa texture view",
format,
dimension: gpu::ViewDimension::D2,
subresources: &Default::default(),
},
)),
)
} else {
(None, None)
};
let raw = self.gpu.create_texture(gpu::TextureDesc {
name: "atlas",
format,
@@ -240,6 +277,8 @@ impl BladeAtlasState {
format,
raw,
raw_view,
msaa,
msaa_view,
live_atlas_keys: 0,
};
@@ -354,6 +393,8 @@ struct BladeAtlasTexture {
allocator: BucketedAtlasAllocator,
raw: gpu::Texture,
raw_view: gpu::TextureView,
msaa: Option<gpu::Texture>,
msaa_view: Option<gpu::TextureView>,
format: gpu::TextureFormat,
live_atlas_keys: u32,
}
@@ -381,6 +422,12 @@ impl BladeAtlasTexture {
fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_texture(self.raw);
gpu.destroy_texture_view(self.raw_view);
if let Some(msaa) = self.msaa {
gpu.destroy_texture(msaa);
}
if let Some(msaa_view) = self.msaa_view {
gpu.destroy_texture_view(msaa_view);
}
}
fn bytes_per_pixel(&self) -> u8 {

View File

@@ -7,16 +7,18 @@ use crate::{
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
ScaledPixels, Scene, Shadow, Size, Underline,
};
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
use bytemuck::{Pod, Zeroable};
use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000;
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const PATH_SAMPLE_COUNT: u32 = 4;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
@@ -208,7 +210,10 @@ impl BladePipelines {
blend: Some(gpu::BlendState::ADDITIVE),
write_mask: gpu::ColorWrites::default(),
}],
multisample_state: gpu::MultisampleState::default(),
multisample_state: gpu::MultisampleState {
sample_count: PATH_SAMPLE_COUNT,
..Default::default()
},
}),
paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "paths",
@@ -348,7 +353,7 @@ impl BladeRenderer {
min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
});
let atlas = Arc::new(BladeAtlas::new(&context.gpu));
let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas",
mag_filter: gpu::FilterMode::Linear,
@@ -497,27 +502,38 @@ impl BladeRenderer {
};
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let mut pass = self.command_encoder.render(
let frame_view = tex_info.raw_view;
let color_target = if let Some(msaa_view) = tex_info.msaa_view {
gpu::RenderTarget {
view: msaa_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::ResolveTo(frame_view),
}
} else {
gpu::RenderTarget {
view: frame_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}
};
if let mut pass = self.command_encoder.render(
"paths",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: tex_info.raw_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}],
colors: &[color_target],
depth_stencil: None,
},
);
let mut encoder = pass.with(&self.pipelines.path_rasterization);
encoder.bind(
0,
&ShaderPathRasterizationData {
globals,
b_path_vertices: vertex_buf,
},
);
encoder.draw(0, vertices.len() as u32, 0, 1);
) {
let mut encoder = pass.with(&self.pipelines.path_rasterization);
encoder.bind(
0,
&ShaderPathRasterizationData {
globals,
b_path_vertices: vertex_buf,
},
);
encoder.draw(0, vertices.len() as u32, 0, 1);
}
}
}

View File

@@ -590,7 +590,6 @@ impl X11WindowState {
BladeRenderer::new(gpu_context, &raw_window, config)?
};
check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
Ok(Self {
@@ -1278,6 +1277,14 @@ impl PlatformWindow for X11Window {
.unwrap();
}
fn map_window(&mut self) -> anyhow::Result<()> {
check_reply(
|| "X11 MapWindow failed.",
self.0.xcb.map_window(self.0.x_window),
)?;
Ok(())
}
fn set_edited(&mut self, _edited: bool) {
log::info!("ignoring macOS specific set_edited");
}

View File

@@ -158,6 +158,33 @@ impl PlatformInput {
})
})
}
// Some mice (like Logitech MX Master) send navigation buttons as swipe events
NSEventType::NSEventTypeSwipe => {
let navigation_direction = match native_event.phase() {
NSEventPhase::NSEventPhaseEnded => match native_event.deltaX() {
x if x > 0.0 => Some(NavigationDirection::Back),
x if x < 0.0 => Some(NavigationDirection::Forward),
_ => return None,
},
_ => return None,
};
match navigation_direction {
Some(direction) => window_height.map(|window_height| {
Self::MouseDown(MouseDownEvent {
button: MouseButton::Navigate(direction),
position: point(
px(native_event.locationInWindow().x as f32),
window_height - px(native_event.locationInWindow().y as f32),
),
modifiers: read_modifiers(native_event),
click_count: 1,
first_mouse: false,
})
}),
_ => None,
}
}
NSEventType::NSScrollWheel => window_height.map(|window_height| {
let phase = match native_event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {

View File

@@ -13,13 +13,14 @@ use std::borrow::Cow;
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas {
pub(crate) fn new(device: Device) -> Self {
pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
path_textures: Default::default(),
tiles_by_key: Default::default(),
path_sample_count,
}))
}
@@ -27,6 +28,10 @@ impl MetalAtlas {
self.0.lock().texture(id).metal_texture.clone()
}
pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
self.0.lock().texture(id).msaa_texture.clone()
}
pub(crate) fn allocate(
&self,
size: Size<DevicePixels>,
@@ -54,6 +59,7 @@ struct MetalAtlasState {
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
path_textures: AtlasTextureList<MetalAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
path_sample_count: u32,
}
impl PlatformAtlas for MetalAtlas {
@@ -176,6 +182,18 @@ impl MetalAtlasState {
texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor);
// We currently only enable MSAA for path textures.
let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
let mut descriptor = texture_descriptor.clone();
descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
descriptor.set_storage_mode(metal::MTLStorageMode::Private);
descriptor.set_sample_count(self.path_sample_count as _);
let msaa_texture = self.device.new_texture(&descriptor);
Some(msaa_texture)
} else {
None
};
let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
@@ -191,6 +209,7 @@ impl MetalAtlasState {
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture),
msaa_texture: AssertSend(msaa_texture),
live_atlas_keys: 0,
};
@@ -217,6 +236,7 @@ struct MetalAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>,
msaa_texture: AssertSend<Option<metal::Texture>>,
live_atlas_keys: u32,
}

View File

@@ -28,6 +28,9 @@ pub(crate) type PointF = crate::Point<f32>;
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
#[cfg(feature = "runtime_shaders")]
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const PATH_SAMPLE_COUNT: u32 = 4;
pub type Context = Arc<Mutex<InstanceBufferPool>>;
pub type Renderer = MetalRenderer;
@@ -170,6 +173,7 @@ impl MetalRenderer {
"path_rasterization_vertex",
"path_rasterization_fragment",
MTLPixelFormat::R16Float,
PATH_SAMPLE_COUNT,
);
let path_sprites_pipeline_state = build_pipeline_state(
&device,
@@ -229,7 +233,7 @@ impl MetalRenderer {
);
let command_queue = device.new_command_queue();
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT));
let core_video_texture_cache =
unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
@@ -531,10 +535,20 @@ impl MetalRenderer {
.unwrap();
let texture = self.sprite_atlas.metal_texture(texture_id);
color_attachment.set_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
let msaa_texture = self.sprite_atlas.msaa_texture(texture_id);
if let Some(msaa_texture) = msaa_texture {
color_attachment.set_texture(Some(&msaa_texture));
color_attachment.set_resolve_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
} else {
color_attachment.set_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
}
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
command_encoder.set_vertex_buffer(
@@ -1160,6 +1174,7 @@ fn build_path_rasterization_pipeline_state(
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
path_sample_count: u32,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
@@ -1172,6 +1187,10 @@ fn build_path_rasterization_pipeline_state(
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
if path_sample_count > 1 {
descriptor.set_raster_sample_count(path_sample_count as _);
descriptor.set_alpha_to_coverage_enabled(true);
}
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);

View File

@@ -148,6 +148,10 @@ unsafe fn build_classes() {
sel!(scrollWheel:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(swipeWithEvent:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(flagsChanged:),
handle_view_event as extern "C" fn(&Object, Sel, id),

View File

@@ -715,6 +715,13 @@ impl Path<Pixels> {
}
}
/// Move the start, current point to the given point.
pub fn move_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
self.start = to;
self.current = to;
}
/// Draw a straight line from the current point to the given point.
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
@@ -744,7 +751,8 @@ impl Path<Pixels> {
self.current = to;
}
fn push_triangle(
/// Push a triangle to the Path.
pub fn push_triangle(
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),

View File

@@ -287,13 +287,10 @@ pub enum WhiteSpace {
}
/// How to truncate text that overflows the width of the element
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Truncate {
/// Truncate the text without an ellipsis
#[default]
Truncate,
/// Truncate the text with an ellipsis
Ellipsis,
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TextOverflow {
/// Truncate the text with an ellipsis, same as: `text-overflow: ellipsis;` in CSS
Ellipsis(&'static str),
}
/// The properties that can be used to style text in GPUI
@@ -337,7 +334,9 @@ pub struct TextStyle {
pub white_space: WhiteSpace,
/// The text should be truncated if it overflows the width of the element
pub truncate: Option<Truncate>,
pub text_overflow: Option<TextOverflow>,
/// The number of lines to display before truncating the text
pub line_clamp: Option<usize>,
}
impl Default for TextStyle {
@@ -362,7 +361,8 @@ impl Default for TextStyle {
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
truncate: None,
text_overflow: None,
line_clamp: None,
}
}
}

View File

@@ -1,9 +1,9 @@
use crate::TextStyleRefinement;
use crate::{
self as gpui, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, DefiniteLength,
Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla, JustifyContent, Length,
SharedString, StrikethroughStyle, StyleRefinement, WhiteSpace,
SharedString, StrikethroughStyle, StyleRefinement, TextOverflow, WhiteSpace,
};
use crate::{TextStyleRefinement, Truncate};
pub use gpui_macros::{
border_style_methods, box_shadow_style_methods, cursor_style_methods, margin_style_methods,
overflow_style_methods, padding_style_methods, position_style_methods,
@@ -11,6 +11,8 @@ pub use gpui_macros::{
};
use taffy::style::{AlignContent, Display};
const ELLIPSIS: &str = "";
/// A trait for elements that can be styled.
/// Use this to opt-in to a utility CSS-like styling API.
pub trait Styled: Sized {
@@ -64,19 +66,32 @@ pub trait Styled: Sized {
fn text_ellipsis(mut self) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.truncate = Some(Truncate::Ellipsis);
.text_overflow = Some(TextOverflow::Ellipsis(ELLIPSIS));
self
}
/// Sets the truncate overflowing text.
/// [Docs](https://tailwindcss.com/docs/text-overflow#truncate)
fn truncate(mut self) -> Self {
/// Sets the text overflow behavior of the element.
fn text_overflow(mut self, overflow: TextOverflow) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.truncate = Some(Truncate::Truncate);
.text_overflow = Some(overflow);
self
}
/// Sets the truncate to prevent text from wrapping and truncate overflowing text with an ellipsis (…) if needed.
/// [Docs](https://tailwindcss.com/docs/text-overflow#truncate)
fn truncate(mut self) -> Self {
self.overflow_hidden().whitespace_nowrap().text_ellipsis()
}
/// Sets number of lines to show before truncating the text.
/// [Docs](https://tailwindcss.com/docs/line-clamp)
fn line_clamp(mut self, lines: usize) -> Self {
let mut text_style = self.text_style().get_or_insert_with(Default::default);
text_style.line_clamp = Some(lines);
self.overflow_hidden()
}
/// Sets the flex direction of the element to `column`.
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
fn flex_col(mut self) -> Self {

View File

@@ -374,12 +374,15 @@ impl WindowTextSystem {
font_size: Pixels,
runs: &[TextRun],
wrap_width: Option<Pixels>,
line_clamp: Option<usize>,
) -> Result<SmallVec<[WrappedLine; 1]>> {
let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
let mut lines = SmallVec::new();
let mut line_start = 0;
let mut max_wrap_lines = line_clamp.unwrap_or(usize::MAX);
let mut wrapped_lines = 0;
let mut process_line = |line_text: SharedString| {
let line_end = line_start + line_text.len();
@@ -430,9 +433,14 @@ impl WindowTextSystem {
run_start += run_len_within_line;
}
let layout = self
.line_layout_cache
.layout_wrapped_line(&line_text, font_size, &font_runs, wrap_width);
let layout = self.line_layout_cache.layout_wrapped_line(
&line_text,
font_size,
&font_runs,
wrap_width,
Some(max_wrap_lines - wrapped_lines),
);
wrapped_lines += layout.wrap_boundaries.len();
lines.push(WrappedLine {
layout,

View File

@@ -129,9 +129,9 @@ impl LineLayout {
&self,
text: &str,
wrap_width: Pixels,
max_lines: Option<usize>,
) -> SmallVec<[WrapBoundary; 1]> {
let mut boundaries = SmallVec::new();
let mut first_non_whitespace_ix = None;
let mut last_candidate_ix = None;
let mut last_candidate_x = px(0.);
@@ -182,7 +182,15 @@ impl LineLayout {
let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
let width = next_x - last_boundary_x;
if width > wrap_width && boundary > last_boundary {
// When used line_clamp, we should limit the number of lines.
if let Some(max_lines) = max_lines {
if boundaries.len() >= max_lines - 1 {
break;
}
}
if let Some(last_candidate_ix) = last_candidate_ix.take() {
last_boundary = last_candidate_ix;
last_boundary_x = last_candidate_x;
@@ -190,7 +198,6 @@ impl LineLayout {
last_boundary = boundary;
last_boundary_x = x;
}
boundaries.push(last_boundary);
}
prev_ch = ch;
@@ -271,10 +278,34 @@ impl WrappedLineLayout {
}
/// The index corresponding to a given position in this layout for the given line height.
///
/// See also [`Self::closest_index_for_position`].
pub fn index_for_position(
&self,
position: Point<Pixels>,
line_height: Pixels,
) -> Result<usize, usize> {
self._index_for_position(position, line_height, false)
}
/// The closest index to a given position in this layout for the given line height.
///
/// Closest means the character boundary closest to the given position.
///
/// See also [`LineLayout::closest_index_for_x`].
pub fn closest_index_for_position(
&self,
position: Point<Pixels>,
line_height: Pixels,
) -> Result<usize, usize> {
self._index_for_position(position, line_height, true)
}
fn _index_for_position(
&self,
mut position: Point<Pixels>,
line_height: Pixels,
closest: bool,
) -> Result<usize, usize> {
let wrapped_line_ix = (position.y / line_height) as usize;
@@ -314,10 +345,16 @@ impl WrappedLineLayout {
} else if position_in_unwrapped_line.x >= wrapped_line_end_x {
Err(wrapped_line_end_index)
} else {
Ok(self
.unwrapped_layout
.index_for_x(position_in_unwrapped_line.x)
.unwrap())
if closest {
Ok(self
.unwrapped_layout
.closest_index_for_x(position_in_unwrapped_line.x))
} else {
Ok(self
.unwrapped_layout
.index_for_x(position_in_unwrapped_line.x)
.unwrap())
}
}
}
@@ -434,6 +471,7 @@ impl LineLayoutCache {
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
max_lines: Option<usize>,
) -> Arc<WrappedLineLayout>
where
Text: AsRef<str>,
@@ -464,7 +502,7 @@ impl LineLayoutCache {
let text = SharedString::from(text);
let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs);
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width, max_lines)
} else {
SmallVec::new()
};

View File

@@ -117,7 +117,7 @@ impl LineWrapper {
let mut char_indices = line.char_indices();
let mut truncate_ix = 0;
for (ix, c) in char_indices {
if width + ellipsis_width <= truncate_width {
if width + ellipsis_width < truncate_width {
truncate_ix = ix;
}
@@ -564,6 +564,7 @@ mod tests {
normal.with_len(7),
],
Some(px(72.)),
None,
)
.unwrap();

View File

@@ -878,6 +878,8 @@ impl Window {
platform_window.set_app_id(&app_id);
}
platform_window.map_window().unwrap();
Ok(Window {
handle,
invalidator,

View File

@@ -81,6 +81,20 @@ pub fn derive_app_context(input: TokenStream) -> TokenStream {
{
self.#app_variable.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl std::future::Future<Output = R> + Send + 'static) -> gpui::Task<R>
where
R: Send + 'static,
{
self.#app_variable.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &gpui::App) -> R) -> Self::Result<R>
where
G: gpui::Global,
{
self.#app_variable.read_global(callback)
}
}
};

View File

@@ -0,0 +1,18 @@
[package]
name = "gpui_tokio"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/gpui_tokio.rs"
doctest = false
[dependencies]
util.workspace = true
gpui.workspace = true
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }

View File

@@ -0,0 +1,58 @@
use std::future::Future;
use gpui::{App, AppContext, Global, ReadGlobal, Task};
use tokio::task::JoinError;
use util::defer;
pub fn init(cx: &mut App) {
cx.set_global(GlobalTokio::new());
}
struct GlobalTokio {
runtime: tokio::runtime::Runtime,
}
impl Global for GlobalTokio {}
impl GlobalTokio {
fn new() -> Self {
let runtime = tokio::runtime::Builder::new_multi_thread()
// Since we now have two executors, let's try to keep our footprint small
.worker_threads(2)
.enable_all()
.build()
.expect("Failed to initialize Tokio");
Self { runtime }
}
}
pub struct Tokio {}
impl Tokio {
/// Spawns the given future on Tokio's thread pool, and returns it via a GPUI task
/// Note that the Tokio task will be cancelled if the GPUI task is dropped
pub fn spawn<C, Fut, R>(cx: &mut C, f: Fut) -> C::Result<Task<Result<R, JoinError>>>
where
C: AppContext,
Fut: Future<Output = R> + Send + 'static,
R: Send + 'static,
{
cx.read_global(|tokio: &GlobalTokio, cx| {
let join_handle = tokio.runtime.spawn(f);
let abort_handle = join_handle.abort_handle();
let cancel = defer(move || {
abort_handle.abort();
});
cx.background_spawn(async move {
let result = join_handle.await;
drop(cancel);
result
})
})
}
pub fn handle(cx: &mut App) -> tokio::runtime::Handle {
GlobalTokio::global(cx).runtime.handle().clone()
}
}

View File

@@ -18,6 +18,31 @@ pub struct InlineCompletion {
pub edit_preview: Option<language::EditPreview>,
}
pub enum DataCollectionState {
/// The provider doesn't support data collection.
Unsupported,
/// When there's a file not saved yet. In this case, we can't tell to which project it belongs.
Unknown,
/// Data collection is enabled
Enabled,
/// Data collection is disabled or unanswered.
Disabled,
}
impl DataCollectionState {
pub fn is_supported(&self) -> bool {
!matches!(self, DataCollectionState::Unsupported)
}
pub fn is_unknown(&self) -> bool {
matches!(self, DataCollectionState::Unknown)
}
pub fn is_enabled(&self) -> bool {
matches!(self, DataCollectionState::Enabled)
}
}
pub trait InlineCompletionProvider: 'static + Sized {
fn name() -> &'static str;
fn display_name() -> &'static str;
@@ -26,6 +51,10 @@ pub trait InlineCompletionProvider: 'static + Sized {
fn show_tab_accept_marker() -> bool {
false
}
fn data_collection_state(&self, _cx: &App) -> DataCollectionState {
DataCollectionState::Unsupported
}
fn toggle_data_collection(&mut self, _cx: &mut App) {}
fn is_enabled(
&self,
buffer: &Entity<Buffer>,
@@ -72,6 +101,8 @@ pub trait InlineCompletionProviderHandle {
fn show_completions_in_menu(&self) -> bool;
fn show_completions_in_normal_mode(&self) -> bool;
fn show_tab_accept_marker(&self) -> bool;
fn data_collection_state(&self, cx: &App) -> DataCollectionState;
fn toggle_data_collection(&self, cx: &mut App);
fn needs_terms_acceptance(&self, cx: &App) -> bool;
fn is_refreshing(&self, cx: &App) -> bool;
fn refresh(
@@ -122,6 +153,14 @@ where
T::show_tab_accept_marker()
}
fn data_collection_state(&self, cx: &App) -> DataCollectionState {
self.read(cx).data_collection_state(cx)
}
fn toggle_data_collection(&self, cx: &mut App) {
self.update(cx, |this, cx| this.toggle_data_collection(cx))
}
fn is_enabled(
&self,
buffer: &Entity<Buffer>,

View File

@@ -29,7 +29,7 @@ workspace.workspace = true
zed_actions.workspace = true
zeta.workspace = true
client.workspace = true
zed_predict_tos.workspace = true
zed_predict_onboarding.workspace = true
[dev-dependencies]
copilot = { workspace = true, features = ["test-support"] }

View File

@@ -1,14 +1,15 @@
use anyhow::Result;
use client::UserStore;
use client::{Client, UserStore};
use copilot::{Copilot, Status};
use editor::{scroll::Autoscroll, Editor};
use editor::{actions::ShowInlineCompletion, scroll::Autoscroll, Editor};
use feature_flags::{
FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
};
use fs::Fs;
use gpui::{
actions, div, pulsating_between, Action, Animation, AnimationExt, App, AsyncWindowContext,
Corner, Entity, IntoElement, ParentElement, Render, Subscription, WeakEntity,
Corner, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render, Subscription,
WeakEntity,
};
use language::{
language_settings::{
@@ -19,18 +20,16 @@ use language::{
use settings::{update_settings_file, Settings, SettingsStore};
use std::{path::Path, sync::Arc, time::Duration};
use supermaven::{AccountStatus, Supermaven};
use ui::{prelude::*, ButtonLike, Color, Icon, IconWithIndicator, Indicator, PopoverMenuHandle};
use ui::{
prelude::*, ButtonLike, Clickable, ContextMenu, ContextMenuEntry, IconButton,
IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip,
};
use workspace::{
create_and_open_local_file,
item::ItemHandle,
notifications::NotificationId,
ui::{
ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, PopoverMenu, Tooltip,
},
StatusItemView, Toast, Workspace,
create_and_open_local_file, item::ItemHandle, notifications::NotificationId, StatusItemView,
Toast, Workspace,
};
use zed_actions::OpenBrowser;
use zed_predict_tos::ZedPredictTos;
use zed_predict_onboarding::ZedPredictModal;
use zeta::RateCompletionModal;
actions!(zeta, [RateCompletions]);
@@ -43,9 +42,11 @@ struct CopilotErrorToast;
pub struct InlineCompletionButton {
editor_subscription: Option<(Subscription, usize)>,
editor_enabled: Option<bool>,
editor_focus_handle: Option<FocusHandle>,
language: Option<Arc<Language>>,
file: Option<Arc<dyn File>>,
inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
client: Arc<Client>,
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>,
@@ -229,14 +230,16 @@ impl Render for InlineCompletionButton {
return div();
}
if !self
.user_store
.read(cx)
.current_user_has_accepted_terms()
.unwrap_or(false)
{
let current_user_terms_accepted =
self.user_store.read(cx).current_user_has_accepted_terms();
if !current_user_terms_accepted.unwrap_or(false) {
let workspace = self.workspace.clone();
let user_store = self.user_store.clone();
let client = self.client.clone();
let fs = self.fs.clone();
let signed_in = current_user_terms_accepted.is_some();
return div().child(
ButtonLike::new("zeta-pending-tos-icon")
@@ -250,20 +253,29 @@ impl Render for InlineCompletionButton {
))
.into_any_element(),
)
.tooltip(|window, cx| {
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Edit Predictions",
None,
"Read Terms of Service",
if signed_in {
"Read Terms of Service"
} else {
"Sign in to use"
},
window,
cx,
)
})
.on_click(cx.listener(move |_, _, window, cx| {
let user_store = user_store.clone();
if let Some(workspace) = workspace.upgrade() {
ZedPredictTos::toggle(workspace, user_store, window, cx);
ZedPredictModal::toggle(
workspace,
user_store.clone(),
client.clone(),
fs.clone(),
window,
cx,
);
}
})),
);
@@ -316,6 +328,7 @@ impl InlineCompletionButton {
workspace: WeakEntity<Workspace>,
fs: Arc<dyn Fs>,
user_store: Entity<UserStore>,
client: Arc<Client>,
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
cx: &mut Context<Self>,
) -> Self {
@@ -329,11 +342,13 @@ impl InlineCompletionButton {
Self {
editor_subscription: None,
editor_enabled: None,
editor_focus_handle: None,
language: None,
file: None,
inline_completion_provider: None,
popover_menu_handle,
workspace,
client,
fs,
user_store,
}
@@ -364,21 +379,26 @@ impl InlineCompletionButton {
})
}
// Predict Edits at Cursor alt-tab
// Automatically Predict:
// ✓ PATH
// ✓ Rust
// ✓ All Files
pub fn build_language_settings_menu(&self, mut menu: ContextMenu, cx: &mut App) -> ContextMenu {
let fs = self.fs.clone();
menu = menu.header("Predict Edits For:");
if let Some(language) = self.language.clone() {
let fs = fs.clone();
let language_enabled =
language_settings::language_settings(Some(language.name()), None, cx)
.show_inline_completions;
menu = menu.entry(
format!(
"{} Inline Completions for {}",
if language_enabled { "Hide" } else { "Show" },
language.name()
),
menu = menu.toggleable_entry(
language.name(),
language_enabled,
IconPosition::Start,
None,
move |_, cx| {
toggle_inline_completions_for_language(language.clone(), fs.clone(), cx)
@@ -387,16 +407,14 @@ impl InlineCompletionButton {
}
let settings = AllLanguageSettings::get_global(cx);
if let Some(file) = &self.file {
let path = file.path().clone();
let path_enabled = settings.inline_completions_enabled_for_path(&path);
menu = menu.entry(
format!(
"{} Inline Completions for This Path",
if path_enabled { "Hide" } else { "Show" }
),
menu = menu.toggleable_entry(
"This File",
path_enabled,
IconPosition::Start,
None,
move |window, cx| {
if let Some(workspace) = window.root().flatten() {
@@ -416,15 +434,48 @@ impl InlineCompletionButton {
}
let globally_enabled = settings.inline_completions_enabled(None, None, cx);
menu.entry(
if globally_enabled {
"Hide Inline Completions for All Files"
} else {
"Show Inline Completions for All Files"
},
menu = menu.toggleable_entry(
"All Files",
globally_enabled,
IconPosition::Start,
None,
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx),
)
);
if let Some(provider) = &self.inline_completion_provider {
let data_collection = provider.data_collection_state(cx);
if data_collection.is_supported() {
let provider = provider.clone();
menu = menu.separator().item(
ContextMenuEntry::new("Data Collection")
.toggleable(IconPosition::Start, data_collection.is_enabled())
.disabled(data_collection.is_unknown())
.handler(move |_, cx| {
provider.toggle_data_collection(cx);
}),
);
}
}
if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
menu = menu
.separator()
.entry(
"Predict Edit at Cursor",
Some(Box::new(ShowInlineCompletion)),
{
let editor_focus_handle = editor_focus_handle.clone();
move |window, cx| {
editor_focus_handle.dispatch_action(&ShowInlineCompletion, window, cx);
}
},
)
.context(editor_focus_handle);
}
menu
}
fn build_copilot_context_menu(
@@ -468,7 +519,7 @@ impl InlineCompletionButton {
self.build_language_settings_menu(menu, cx).when(
cx.has_flag::<PredictEditsRateCompletionsFeatureFlag>(),
|this| {
this.separator().entry(
this.entry(
"Rate Completions",
Some(RateCompletions.boxed_clone()),
move |window, cx| {
@@ -504,6 +555,7 @@ impl InlineCompletionButton {
self.inline_completion_provider = editor.inline_completion_provider();
self.language = language.cloned();
self.file = file;
self.editor_focus_handle = Some(editor.focus_handle(cx));
cx.notify();
}

View File

@@ -590,6 +590,7 @@ pub struct Runnable {
#[derive(Clone)]
pub struct EditPreview {
old_snapshot: text::BufferSnapshot,
applied_edits_snapshot: text::BufferSnapshot,
syntax_snapshot: SyntaxSnapshot,
}
@@ -608,66 +609,80 @@ impl EditPreview {
include_deletions: bool,
cx: &App,
) -> HighlightedEdits {
let mut text = String::new();
let mut highlights = Vec::new();
let Some(range) = self.compute_visible_range(edits, current_snapshot) else {
let Some(visible_range_in_preview_snapshot) = self.compute_visible_range(edits) else {
return HighlightedEdits::default();
};
let mut offset = range.start;
let mut delta = 0isize;
let status_colors = cx.theme().status();
let mut text = String::new();
let mut highlights = Vec::new();
let mut offset_in_preview_snapshot = visible_range_in_preview_snapshot.start;
let insertion_highlight_style = HighlightStyle {
background_color: Some(cx.theme().status().created_background),
..Default::default()
};
let deletion_highlight_style = HighlightStyle {
background_color: Some(cx.theme().status().deleted_background),
..Default::default()
};
for (range, edit_text) in edits {
let edit_range = range.to_offset(current_snapshot);
let new_edit_start = (edit_range.start as isize + delta) as usize;
let new_edit_range = new_edit_start..new_edit_start + edit_text.len();
let edit_new_end_in_preview_snapshot = range
.end
.bias_right(&self.old_snapshot)
.to_offset(&self.applied_edits_snapshot);
let edit_start_in_preview_snapshot = edit_new_end_in_preview_snapshot - edit_text.len();
let prev_range = offset..new_edit_start;
if !prev_range.is_empty() {
let start = text.len();
self.highlight_text(prev_range, &mut text, &mut highlights, None, cx);
offset += text.len() - start;
let unchanged_range_in_preview_snapshot =
offset_in_preview_snapshot..edit_start_in_preview_snapshot;
if !unchanged_range_in_preview_snapshot.is_empty() {
Self::highlight_text(
unchanged_range_in_preview_snapshot.clone(),
&mut text,
&mut highlights,
None,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
cx,
);
}
if include_deletions && !edit_range.is_empty() {
let start = text.len();
text.extend(current_snapshot.text_for_range(edit_range.clone()));
let end = text.len();
highlights.push((
start..end,
HighlightStyle {
background_color: Some(status_colors.deleted_background),
..Default::default()
},
));
let range_in_current_snapshot = range.to_offset(current_snapshot);
if include_deletions && !range_in_current_snapshot.is_empty() {
Self::highlight_text(
range_in_current_snapshot.clone(),
&mut text,
&mut highlights,
Some(deletion_highlight_style),
&current_snapshot.text,
&current_snapshot.syntax,
cx,
);
}
if !edit_text.is_empty() {
self.highlight_text(
new_edit_range,
Self::highlight_text(
edit_start_in_preview_snapshot..edit_new_end_in_preview_snapshot,
&mut text,
&mut highlights,
Some(HighlightStyle {
background_color: Some(status_colors.created_background),
..Default::default()
}),
Some(insertion_highlight_style),
&self.applied_edits_snapshot,
&self.syntax_snapshot,
cx,
);
offset += edit_text.len();
}
delta += edit_text.len() as isize - edit_range.len() as isize;
offset_in_preview_snapshot = edit_new_end_in_preview_snapshot;
}
self.highlight_text(
offset..(range.end as isize + delta) as usize,
Self::highlight_text(
offset_in_preview_snapshot..visible_range_in_preview_snapshot.end,
&mut text,
&mut highlights,
None,
&self.applied_edits_snapshot,
&self.syntax_snapshot,
cx,
);
@@ -678,14 +693,15 @@ impl EditPreview {
}
fn highlight_text(
&self,
range: Range<usize>,
text: &mut String,
highlights: &mut Vec<(Range<usize>, HighlightStyle)>,
override_style: Option<HighlightStyle>,
snapshot: &text::BufferSnapshot,
syntax_snapshot: &SyntaxSnapshot,
cx: &App,
) {
for chunk in self.highlighted_chunks(range) {
for chunk in Self::highlighted_chunks(range, snapshot, syntax_snapshot) {
let start = text.len();
text.push_str(chunk.text);
let end = text.len();
@@ -704,12 +720,14 @@ impl EditPreview {
}
}
fn highlighted_chunks(&self, range: Range<usize>) -> BufferChunks {
let captures =
self.syntax_snapshot
.captures(range.clone(), &self.applied_edits_snapshot, |grammar| {
grammar.highlights_query.as_ref()
});
fn highlighted_chunks<'a>(
range: Range<usize>,
snapshot: &'a text::BufferSnapshot,
syntax_snapshot: &'a SyntaxSnapshot,
) -> BufferChunks<'a> {
let captures = syntax_snapshot.captures(range.clone(), snapshot, |grammar| {
grammar.highlights_query.as_ref()
});
let highlight_maps = captures
.grammars()
@@ -718,7 +736,7 @@ impl EditPreview {
.collect();
BufferChunks::new(
self.applied_edits_snapshot.as_rope(),
snapshot.as_rope(),
range,
Some((captures, highlight_maps)),
false,
@@ -726,21 +744,24 @@ impl EditPreview {
)
}
fn compute_visible_range(
&self,
edits: &[(Range<Anchor>, String)],
snapshot: &BufferSnapshot,
) -> Option<Range<usize>> {
fn compute_visible_range(&self, edits: &[(Range<Anchor>, String)]) -> Option<Range<usize>> {
let (first, _) = edits.first()?;
let (last, _) = edits.last()?;
let start = first.start.to_point(snapshot);
let end = last.end.to_point(snapshot);
let start = first
.start
.bias_left(&self.old_snapshot)
.to_point(&self.applied_edits_snapshot);
let end = last
.end
.bias_right(&self.old_snapshot)
.to_point(&self.applied_edits_snapshot);
// Ensure that the first line of the first edit and the last line of the last edit are always fully visible
let range = Point::new(start.row, 0)..Point::new(end.row, snapshot.line_len(end.row));
let range = Point::new(start.row, 0)
..Point::new(end.row, self.applied_edits_snapshot.line_len(end.row));
Some(range.to_offset(&snapshot))
Some(range.to_offset(&self.applied_edits_snapshot))
}
}
@@ -1003,7 +1024,7 @@ impl Buffer {
) -> Task<EditPreview> {
let registry = self.language_registry();
let language = self.language().cloned();
let old_snapshot = self.text.snapshot();
let mut branch_buffer = self.text.branch();
let mut syntax_snapshot = self.syntax_map.lock().snapshot();
cx.background_executor().spawn(async move {
@@ -1017,6 +1038,7 @@ impl Buffer {
}
}
EditPreview {
old_snapshot,
applied_edits_snapshot: branch_buffer.snapshot(),
syntax_snapshot,
}

View File

@@ -6,8 +6,8 @@ use crate::Buffer;
use clock::ReplicaId;
use collections::BTreeMap;
use futures::FutureExt as _;
use gpui::TestAppContext;
use gpui::{App, AppContext as _, BorrowAppContext, Entity};
use gpui::{HighlightStyle, TestAppContext};
use indoc::indoc;
use proto::deserialize_operation;
use rand::prelude::*;
@@ -23,6 +23,7 @@ use syntax_map::TreeSitterOptions;
use text::network::Network;
use text::{BufferId, LineEnding};
use text::{Point, ToPoint};
use theme::ActiveTheme;
use unindent::Unindent as _;
use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
@@ -2634,43 +2635,113 @@ async fn test_preview_edits(cx: &mut TestAppContext) {
theme::init(theme::LoadThemes::JustBase, cx);
});
let text = indoc! {r#"
let insertion_style = HighlightStyle {
background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
..Default::default()
};
let deletion_style = HighlightStyle {
background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
..Default::default()
};
// no edits
assert_preview_edits(
indoc! {"
fn test_empty() -> bool {
false
}"
},
vec![],
true,
cx,
|hl| {
assert!(hl.text.is_empty());
assert!(hl.highlights.is_empty());
},
)
.await;
// only insertions
assert_preview_edits(
indoc! {"
fn calculate_area(: f64) -> f64 {
std::f64::consts::PI * .powi(2)
}"
},
vec![
(Point::new(0, 18)..Point::new(0, 18), "radius"),
(Point::new(1, 27)..Point::new(1, 27), "radius"),
],
true,
cx,
|hl| {
assert_eq!(
hl.text,
indoc! {"
fn calculate_area(radius: f64) -> f64 {
std::f64::consts::PI * radius.powi(2)"
}
);
assert_eq!(hl.highlights.len(), 2);
assert_eq!(hl.highlights[0], ((18..24), insertion_style));
assert_eq!(hl.highlights[1], ((67..73), insertion_style));
},
)
.await;
// insertions & deletions
assert_preview_edits(
indoc! {"
struct Person {
first_name: String,
}
impl Person {
fn last_name(&self) -> &String {
&self.last_name
fn first_name(&self) -> &String {
&self.first_name
}
}"#
};
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let highlighted_edits = preview_edits(
&buffer,
cx,
[
(Point::new(5, 7)..Point::new(5, 11), "first"),
(Point::new(6, 14)..Point::new(6, 18), "first"),
}"
},
vec![
(Point::new(1, 4)..Point::new(1, 9), "last"),
(Point::new(5, 7)..Point::new(5, 12), "last"),
(Point::new(6, 14)..Point::new(6, 19), "last"),
],
true,
cx,
|hl| {
assert_eq!(
hl.text,
indoc! {"
firstlast_name: String,
}
impl Person {
fn firstlast_name(&self) -> &String {
&self.firstlast_name"
}
);
assert_eq!(hl.highlights.len(), 6);
assert_eq!(hl.highlights[0], ((4..9), deletion_style));
assert_eq!(hl.highlights[1], ((9..13), insertion_style));
assert_eq!(hl.highlights[2], ((52..57), deletion_style));
assert_eq!(hl.highlights[3], ((57..61), insertion_style));
assert_eq!(hl.highlights[4], ((101..106), deletion_style));
assert_eq!(hl.highlights[5], ((106..110), insertion_style));
},
)
.await;
assert_eq!(
highlighted_edits.text,
" fn lastfirst_name(&self) -> &String {\n &self.lastfirst_name"
);
async fn preview_edits(
buffer: &Entity<Buffer>,
async fn assert_preview_edits(
text: &str,
edits: Vec<(Range<Point>, &str)>,
include_deletions: bool,
cx: &mut TestAppContext,
edits: impl IntoIterator<Item = (Range<Point>, &'static str)>,
) -> HighlightedEdits {
assert_fn: impl Fn(HighlightedEdits),
) {
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let edits = buffer.read_with(cx, |buffer, _| {
edits
.into_iter()
@@ -2687,85 +2758,10 @@ async fn test_preview_edits(cx: &mut TestAppContext) {
buffer.preview_edits(edits.clone().into(), cx)
})
.await;
cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, true, cx))
}
}
#[gpui::test]
async fn test_preview_edits_interpolate(cx: &mut TestAppContext) {
use theme::ActiveTheme;
cx.update(|cx| {
init_settings(cx, |_| {});
theme::init(theme::LoadThemes::JustBase, cx);
});
let text = indoc! {r#"
struct Person {
_name: String
}"#
};
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let edits = construct_edits(&buffer, [(Point::new(1, 4)..Point::new(1, 4), "first")], cx);
let edit_preview = buffer
.read_with(cx, |buffer, cx| buffer.preview_edits(edits.clone(), cx))
.await;
let highlighted_edits =
cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, false, cx));
let created_background = cx.read(|cx| cx.theme().status().created_background);
assert_eq!(highlighted_edits.text, " first_name: String");
assert_eq!(highlighted_edits.highlights.len(), 1);
assert_eq!(highlighted_edits.highlights[0].0, 4..9);
assert_eq!(
highlighted_edits.highlights[0].1.background_color,
Some(created_background)
);
let edits = construct_edits(&buffer, [(Point::new(1, 4)..Point::new(1, 4), "f")], cx);
cx.update(|cx| {
buffer.update(cx, |buffer, cx| {
buffer.edit(edits.iter().cloned(), None, cx);
})
});
let edits = construct_edits(&buffer, [(Point::new(1, 5)..Point::new(1, 5), "irst")], cx);
let highlighted_edits =
cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, false, cx));
assert_eq!(highlighted_edits.text, " first_name: String");
assert_eq!(highlighted_edits.highlights.len(), 1);
assert_eq!(highlighted_edits.highlights[0].0, (5..9));
assert_eq!(
highlighted_edits.highlights[0].1.background_color,
Some(created_background)
);
fn construct_edits(
buffer: &Entity<Buffer>,
edits: impl IntoIterator<Item = (Range<Point>, &'static str)>,
cx: &mut TestAppContext,
) -> Arc<[(Range<Anchor>, String)]> {
buffer
.read_with(cx, |buffer, _| {
edits
.into_iter()
.map(|(range, text)| {
(
buffer.anchor_after(range.start)..buffer.anchor_before(range.end),
text.to_string(),
)
})
.collect::<Vec<_>>()
})
.into()
let highlighted_edits = cx.read(|cx| {
edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
});
assert_fn(highlighted_edits);
}
}

View File

@@ -1702,6 +1702,58 @@ impl Grammar {
}
impl CodeLabel {
pub fn fallback_for_completion(
item: &lsp::CompletionItem,
language: Option<&Language>,
) -> Self {
let highlight_id = item.kind.and_then(|kind| {
let grammar = language?.grammar()?;
use lsp::CompletionItemKind as Kind;
match kind {
Kind::CLASS => grammar.highlight_id_for_name("type"),
Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
Kind::CONSTRUCTOR => grammar.highlight_id_for_name("constructor"),
Kind::ENUM => grammar
.highlight_id_for_name("enum")
.or_else(|| grammar.highlight_id_for_name("type")),
Kind::FIELD => grammar.highlight_id_for_name("property"),
Kind::FUNCTION => grammar.highlight_id_for_name("function"),
Kind::INTERFACE => grammar.highlight_id_for_name("type"),
Kind::METHOD => grammar
.highlight_id_for_name("function.method")
.or_else(|| grammar.highlight_id_for_name("function")),
Kind::OPERATOR => grammar.highlight_id_for_name("operator"),
Kind::PROPERTY => grammar.highlight_id_for_name("property"),
Kind::STRUCT => grammar.highlight_id_for_name("type"),
Kind::VARIABLE => grammar.highlight_id_for_name("variable"),
Kind::KEYWORD => grammar.highlight_id_for_name("keyword"),
_ => None,
}
});
let label = &item.label;
let label_length = label.len();
let runs = highlight_id
.map(|highlight_id| vec![(0..label_length, highlight_id)])
.unwrap_or_default();
let text = if let Some(detail) = &item.detail {
format!("{label} {detail}")
} else if let Some(description) = item
.label_details
.as_ref()
.and_then(|label_details| label_details.description.as_ref())
{
format!("{label} {description}")
} else {
label.clone()
};
Self {
text,
runs,
filter_range: 0..label_length,
}
}
pub fn plain(text: String, filter_text: Option<&str>) -> Self {
let mut result = Self {
runs: Vec::new(),

View File

@@ -647,11 +647,8 @@ impl ConfigurationView {
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
truncate: None,
..Default::default()
};
EditorElement::new(
&self.api_key_editor,
@@ -689,7 +686,7 @@ impl Render for ConfigurationView {
.child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
Button::new("anthropic_console", ANTHROPIC_CONSOLE_URL)
.style(ButtonStyle::Subtle)
.icon(IconName::ExternalLink)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, _, cx| cx.open_url(ANTHROPIC_CONSOLE_URL))
@@ -703,6 +700,8 @@ impl Render for ConfigurationView {
.px_2()
.py_1()
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.child(self.render_api_key_editor(cx)),
)

View File

@@ -466,7 +466,7 @@ impl ConfigurationView {
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
truncate: None,
..Default::default()
};
EditorElement::new(
&self.api_key_editor,
@@ -506,7 +506,7 @@ impl Render for ConfigurationView {
h_flex().child(Label::new(INSTRUCTIONS[1])).child(
Button::new("deepseek_console", DEEPSEEK_CONSOLE_URL)
.style(ButtonStyle::Subtle)
.icon(IconName::ExternalLink)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, _window, cx| cx.open_url(DEEPSEEK_CONSOLE_URL)),
@@ -520,12 +520,14 @@ impl Render for ConfigurationView {
.px_2()
.py_1()
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.child(self.render_api_key_editor(cx)),
)
.child(
Label::new(format!(
"Or set {} environment variable",
"Or set the {} environment variable.",
DEEPSEEK_API_KEY_VAR
))
.size(LabelSize::Small),

View File

@@ -409,11 +409,8 @@ impl ConfigurationView {
font_weight: settings.ui_font.weight,
font_style: FontStyle::Normal,
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
truncate: None,
..Default::default()
};
EditorElement::new(
&self.api_key_editor,
@@ -452,7 +449,7 @@ impl Render for ConfigurationView {
.child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
Button::new("google_console", GOOGLE_CONSOLE_URL)
.style(ButtonStyle::Subtle)
.icon(IconName::ExternalLink)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, _, cx| cx.open_url(GOOGLE_CONSOLE_URL))
@@ -466,6 +463,8 @@ impl Render for ConfigurationView {
.px_2()
.py_1()
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.child(self.render_api_key_editor(cx)),
)

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