Compare commits

...

91 Commits

Author SHA1 Message Date
Mikayla Maki
7c2dffc792 Wire through IPC mechanism for GIT_ASKPASS
co-authored-by: julia@zed.dev
2025-02-28 20:56:11 -08:00
Piotr Osiewicz
e4e758db3a Rust 1.85 (#25272)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-02-28 18:33:35 +01:00
Marshall Bowers
fc52b43159 assistant2: Factor out tool use into its own module (#25819)
This PR factors out the concerns related to tool use out of `Thread` and
into their own module.

Release Notes:

- N/A
2025-02-28 17:04:20 +00:00
Marshall Bowers
b445e4ce24 assistant2: Rework how tool results are stored and referred to (#25817)
This PR reworks how we store tool results and refer to them later.

We now maintain a mapping of the tool uses to their corresponding
results, with separate mappings for the messages and the tool uses they
correspond to.

Release Notes:

- N/A
2025-02-28 11:33:08 -05:00
Danilo Leal
508b581215 assistant: Refine settings view's instruction visuals (#25812)
I've been bothered by using simple hyphens for bullet lists here for a
while; it kinda looked cheap and not well-formatted. So, in this PR, I'm
adding a new, custom UI component in the `language_models` crate, called
`InstructionListItem`, based off the `ListItem` that's somewhat
mimic'ing what a `<li>` would be on the web.

It does have a "rigid" structure as in it's always a label followed by a
button (which is optional), but that seems okay given it has been the
overall shape of the copy we've been using here. Also, never really
loved that we were pasting URLs directly, that kinda felt cheap, too. I
could see an argument where it's just clearer, but it looks too
cluttered, as URLs aren't super pretty, necessarily.

| Before | After |
|--------|--------|
| <img
src="https://github.com/user-attachments/assets/ffd1ac27-b1f4-450d-abf5-079285fc9877"
width="700px" /> | <img
src="https://github.com/user-attachments/assets/28fb9d0d-205d-45d8-9e43-1aaa947adc96"
width="700px" /> |

Release Notes:

- N/A
2025-02-28 12:06:47 -03:00
张小白
c9aba6c10a windows: Use a clippy script instead of xtask (#25807)
Closes #ISSUE

Match the behaviour of our macOS and Linux tests

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-02-28 14:01:10 +00:00
Kirill Bulatov
5740fec9d5 Ensure search input always has regex language synced with search_options state (#25811)
Follow-up to https://github.com/zed-industries/zed/pull/25797

Release Notes:

- N/A
2025-02-28 13:15:25 +00:00
张小白
22220ed32e windows: Remove unnecessary code in #25412 (#25805)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-02-28 11:05:39 +00:00
Kirill Bulatov
7440833ff1 Add a way to toggle inlay hints with modifiers #2 (#25766)
https://github.com/zed-industries/zed/pull/25752 with fixes on top

* Ensures no flickering happens for all modifiers `: false` case
* Dismisses the toggled state on focus out
* Reworks cache state so that "enabled" and "toggled by modifiers" are
different states with their own lifecycle

Release Notes:

- N/A
2025-02-28 08:03:25 +00:00
Kirill Bulatov
bb3aef15eb Reset buffer language on buffer search redeploy (#25797)
Closes https://github.com/zed-industries/zed/issues/25792

Release Notes:

- Fixed search input regex highlight not going away after redeploy
2025-02-28 08:01:01 +00:00
Devzeth
ece1818301 docs: Add documentation for use_smartcase_search (#25786)
Closes #24795

Added missing documentation for `use_smartcase_search`. 

Release Notes:

- N/A
2025-02-28 09:45:40 +02:00
smit
604eb91a6c logging: Add runtime log replace upon max size limit (#25768)
Closes #25638

We currently only check the log size limit at startup and move `Zed.log`
to `Zed.log.old`. If a user runs Zed for an extended period, there's no
runtime restriction on the log file size, which can cause it to grow to
several gigabytes.

This PR fixes that by tracking the log file size while writing. If it
exceeds a certain threshold, we perform the same log replace and
continue logging.

Release Notes:

- Fixed an issue where `Zed.log` could grow excessively large during
long sessions of Zed.
2025-02-28 12:23:30 +05:30
5brian
472dde509f Capitalize default slash command description (#25794)
Update default slash command to use description constant format from
https://github.com/zed-industries/zed/pull/18595.

|Before|After|
|---|---|

|![image](https://github.com/user-attachments/assets/107a321e-0e91-40dd-8c38-4d55fd4f6d28)|![image](https://github.com/user-attachments/assets/f487a518-15bb-45c4-b524-25e6373a5886)|

^ This is when you type slash in the assistant panel.

Release Notes:

- N/A
2025-02-28 00:20:50 -03:00
Nathan Igo
212c8f4c31 html: Bump to v0.1.6 (#25791)
Includes:
- #25130

Release Notes:

- N/A
2025-02-27 20:38:55 -05:00
Shardul Vaidya
6092918be8 assistant: Improve Amazon Bedrock configuration instructions (#25699) 2025-02-27 21:36:41 -03:00
Joseph T. Lyons
a5f96909cb Update to suggest commit message based on file staging (#25790)
Currently, you only get a suggested commit message if you have a single
changed file in the repository. After the PR, the suggest happens per
single-staged file.

https://github.com/user-attachments/assets/4cc19fe6-099c-4690-967d-898b8ca7540b

Release Notes:

- N/A
2025-02-28 00:19:58 +00:00
Piotr Osiewicz
b15aa5e018 rust: Fix test/doctest tasks showing up outside of tests (#25787)
Closes #ISSUE

Release Notes:

- Fixes Rust test tasks showing up outside of tests
2025-02-28 00:48:19 +01:00
Cole Miller
62fb555e18 Use "restore" more consistently in the git panel (#25780)
This PR replaces almost all uses of "discard" in the git panel UI with
"restore", since that's the verb we settled on for the project diff.

The only exception is in the confirmation prompt for restoring files,
where I've kept the "discard changes" language. I think consistency is
less important here and it's helpful to rephrase the action that's being
taken to emphasize that it's destructive.

Release Notes:

- N/A
2025-02-27 18:45:56 -05:00
FalkWoldmann
c0ecf8684e Remove once_cell dependency (#25769)
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-27 23:34:37 +00:00
Kirill Bulatov
df7beb4217 Use active worktree's task sources (#25784)
Follow-up of https://github.com/zed-industries/zed/pull/25605

Previous PR made global tasks with `ZED_WORKTREE_ROOT` available for
"nothing open" scenario, this PR also gets all related worktree task
templates, using the centralized `TextContexts`' active worktree
detection.

Release Notes:

- N/A
2025-02-27 22:57:59 +00:00
Marshall Bowers
6f30d5da71 Use consistent comment style in default.json (#25783)
This PR updates the comments in the `default.json` file consistently use
`//`.

Some comments were using `///`, which doesn't make sense in JSONC.

Release Notes:

- N/A
2025-02-27 22:50:23 +00:00
Marshall Bowers
b8387c6077 docs: Clarify wording around ... in language_servers setting (#25782)
This PR clarifies the wording around how `...` is used in the
`language_servers` setting.

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

Release Notes:

- N/A
2025-02-27 22:27:40 +00:00
Marshall Bowers
c05ce882e9 docs: Add note about path in extensions.toml (#25778)
This PR adds a note about the `path` field in `extensions.toml` and how
to use it.

Suggested in https://github.com/zed-industries/extensions/pull/2128.

Release Notes:

- N/A
2025-02-27 21:47:01 +00:00
Agus Zubiaga
eaf3949614 edit predictions: Remove enabled_in_assistant docs for now (#25777)
I shouldn't have added this because it's not out yet

Release Notes:

- N/A
2025-02-27 21:23:20 +00:00
Devzeth
c47305dd7b title_bar: Fix the order of the collab buttons (#25775)
My previous #24761 and #25192 PR's changed the order of the buttons in
the title_bar for collab, the logic is kept the same but the order is
now as it was previously.

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-27 16:22:05 -05:00
Cole Miller
482a45feac Fix broken merge (#25776)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-02-27 20:48:14 +00:00
Cole Miller
7ec3702b47 Fix cursor position when navigating to a multibuffer's first excerpt (#25723)
This PR fixes an unexpected cursor position when jumping to the
beginning of the project diff editor's first excerpt if that excerpt
starts with a deleted region. Previously, the cursor would end up in the
*following* region in this situation; now it ends up at the start of the
deleted region, as happens already for excerpts that are not the first.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2025-02-27 14:53:34 -05:00
smit
91862ddc9f markdown_preview: Fix markdown preview not updating on AcceptEditPrediction (#25772)
Closes #25384

Markdown preview now subscribes to `ExcerptsEdited` event which is
emited when edit prediction is accepted.

Release Notes:

- Fixed markdown preview not updating when edit prediction is accepted.
2025-02-28 01:22:46 +05:30
Cole Miller
eb4fad52df Fix panic when scrolling in project diff (#25771)
It may happen that the column for the scroll anchor is nonzero, and the
adjustment we're doing here could result in an invalid point in that
case.

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2025-02-27 19:35:03 +00:00
Agus Zubiaga
541a5c01a4 edit predictions: Fix docs for enabled_in_assistant (#25770)
Remove mention of "prompt editor" since that feature isn't out yet.

Release Notes:

- N/A
2025-02-27 19:30:26 +00:00
Agus Zubiaga
82f793144e edit predictions: Add enabled_in_assistant setting (#25767)
Release Notes:

- edit predictions: Add `enabled_in_assistant` setting
2025-02-27 18:52:45 +00:00
Agus Zubiaga
6eb2ffe77a Support absolute disabled_globs (#25755)
Closes: #25556

We were always comparing `disabled_globs` against the relative file
path, we'll now use the absolute path if the glob is also absolute.

Release Notes:

- Support absolute globs in `edit_predictions.disabled_globs`
2025-02-27 15:29:32 -03:00
Mikayla Maki
c5632f8c31 Revert "Add a way to toggle inlay hints with modifiers" (#25764)
This PR caused inlay hints to show on every modifiers press

Reverts zed-industries/zed#25752

Release Notes:

- N/A
2025-02-27 10:11:36 -08:00
Cole Miller
6856e869fc Fix git panel's suggested commit message not updating (#25708)
Closes #ISSUE

Release Notes:

- N/A
2025-02-27 12:27:09 -05:00
João Marcos
cc3b5c729e Keep cursor at top when diff view is first opened (#25682)
Previously, we had the cursor at the bottom while the scroll stayed at
the top.

Now, if you run `git: diff`, the cursor will also be at the top.

The cursor moving to the end was possibly a side-effect of using
`Bias::Right` for selections.

---

Release Notes:

- N/A

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-02-27 17:19:44 +00:00
Cole Miller
4e60ebab5e Fix toggling deletion hunk with mouse at start of buffer (#25726)
Closes #ISSUE

Release Notes:

- N/A
2025-02-27 12:08:20 -05:00
Kirill Bulatov
e8ef36edcc Add a way to toggle inlay hints with modifiers (#25752) 2025-02-27 17:53:10 +02:00
Piotr Osiewicz
2e98bc17cb lsp: Use available workspace folders in initialize params (#25753)
Closes https://github.com/zed-industries/zed/issues/25743
Closes https://github.com/biomejs/biome-zed/issues/73

Release Notes:

- Fixed issues with launching Svelte/Biome language servers
2025-02-27 16:45:59 +01:00
Danilo Leal
5c400dac8d assistant2: Adjust empty state layout (#25745)
Going for a different, arguably simpler design for the Assistant 2 empty
state here. Also took the opportunity to adjust other elements like the
toolbar, message editor, and some items in the configuration page.

<img
src="https://github.com/user-attachments/assets/03fd1d48-a675-4eac-b694-bbe4eeaf06e9"
width="700px"/>

Release Notes:

- N/A
2025-02-27 11:33:53 -03:00
Danilo Leal
635b80ed51 assistant2: Fix submit button width depending on certain conditions (#25748)
This PR makes the Assistant 2 submit button have a different width if
the platform is Linux or Windows, or if Vim mode is turned on. That's
because we now use written out words instead of icons for keybindings
when in those conditions.

| Before | After |
|--------|--------|
| ![CleanShot 2025-02-27 at 9  59
10@2x](https://github.com/user-attachments/assets/f1f8e27c-71c2-402c-8f7b-f9ed91e8e3bf)
| ![CleanShot 2025-02-27 at 9  56
59@2x](https://github.com/user-attachments/assets/ad13b179-daf7-4b38-83e8-1511deb97d96)
|

Release Notes:

- N/A
2025-02-27 11:33:44 -03:00
Danilo Leal
73ab5abee1 assistant2: Adjust tool call accordion visuals (#25749)
Just fine-tuning it a bit more.

<img
src="https://github.com/user-attachments/assets/0d46af77-d111-40a3-9204-d5d8aa9d4886"
width="700px"/>

Release Notes:

- N/A
2025-02-27 11:33:37 -03:00
smit
1f52aab7c7 buffer: Fix panic when multi-byte character is used in languages like Swift (#25739)
Closes #25471

In languages like Swift, names can be concatinated in form like `class
Example: UI`, notice here `Example` and `:` are two different words.
Before, `name_ranges`translation of above text would look like:

```
"class" -> [0..5]
" Example" -> [5..13] (Spaces are intentional)
"e:" -> [12..14] (This is incorrect, and should be ":" -> [13..14])
" UI" -> [14..16]
```

Because this translation does not account for concatinated words, this
might affect queries, but most importantly this panics when multi-byte
character (`ф`) is used in place of `e`, as it then tries to access
index which lies inside that multi-byte. For example, it panics on
`class Examplф: UI`.

---

This PR fixes this by handing concatinated words when calculating
`name_ranges`.

Now, the corrected ranges will look like:

```
"class" -> [0..5]
" Example" -> [5..13]
":" -> [13..14] (Now it's correct)
" UI" -> [14..16]
```

and for multi-byte character

```
"class" -> [0..5]
" Examplф" -> [5..14] (Notice ф takes two bytes)
":" -> [14..15]
" UI" -> [15..17]
```

This way, it no longer tries to access a previous index, preventing a
panic when that index contains a multi-byte character.

Release Notes:

- Fixed a panic when Cyrillic characters are used in languages like
Swift.
2025-02-27 16:27:07 +05:30
renovate[bot]
1732cdb90a Update Rust crate sea-orm to v1.1.6 (#25696)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sea-orm](https://www.sea-ql.org/SeaORM)
([source](https://redirect.github.com/SeaQL/sea-orm)) | dev-dependencies
| patch | `1.1.5` -> `1.1.6` |
| [sea-orm](https://www.sea-ql.org/SeaORM)
([source](https://redirect.github.com/SeaQL/sea-orm)) | dependencies |
patch | `1.1.5` -> `1.1.6` |

---

### Release Notes

<details>
<summary>SeaQL/sea-orm (sea-orm)</summary>

###
[`v1.1.6`](https://redirect.github.com/SeaQL/sea-orm/blob/HEAD/CHANGELOG.md#116---2025-02-24)

[Compare
Source](https://redirect.github.com/SeaQL/sea-orm/compare/1.1.5...1.1.6)

##### New Features

- Support PgVector
[https://github.com/SeaQL/sea-orm/pull/2500](https://redirect.github.com/SeaQL/sea-orm/pull/2500)
- Added `Insert::exec_with_returning_keys` &
`Insert::exec_with_returning_many` (Postgres only)

```rust
assert_eq!(
    Entity::insert_many([
        ActiveModel { id: NotSet, name: Set("two".into()) },
        ActiveModel { id: NotSet, name: Set("three".into()) },
    ])
    .exec_with_returning_many(db)
    .await
    .unwrap(),
    [
        Model { id: 2, name: "two".into() },
        Model { id: 3, name: "three".into() },
    ]
);

assert_eq!(
    cakes_bakers::Entity::insert_many([
        cakes_bakers::ActiveModel {
            cake_id: Set(1),
            baker_id: Set(2),
        },
        cakes_bakers::ActiveModel {
            cake_id: Set(2),
            baker_id: Set(1),
        },
    ])
    .exec_with_returning_keys(db)
    .await
    .unwrap(),
    [(1, 2), (2, 1)]
);
```

- Added `DeleteOne::exec_with_returning` &
`DeleteMany::exec_with_returning`
[https://github.com/SeaQL/sea-orm/pull/2432](https://redirect.github.com/SeaQL/sea-orm/pull/2432)

##### Enhancements

- Expose underlying row types (e.g. `sqlx::postgres::PgRow`)
[https://github.com/SeaQL/sea-orm/pull/2265](https://redirect.github.com/SeaQL/sea-orm/pull/2265)
- \[sea-orm-cli] Added `acquire-timeout` option
[https://github.com/SeaQL/sea-orm/pull/2461](https://redirect.github.com/SeaQL/sea-orm/pull/2461)
- \[sea-orm-cli] Added `with-prelude` option
[https://github.com/SeaQL/sea-orm/pull/2322](https://redirect.github.com/SeaQL/sea-orm/pull/2322)
- \[sea-orm-cli] Added `impl-active-model-behavior` option
[https://github.com/SeaQL/sea-orm/pull/2487](https://redirect.github.com/SeaQL/sea-orm/pull/2487)

##### Bug Fixes

- Fixed `seaography::register_active_enums` macro
[https://github.com/SeaQL/sea-orm/pull/2475](https://redirect.github.com/SeaQL/sea-orm/pull/2475)

##### House keeping

- Remove `futures` crate, replace with `futures-util`
[https://github.com/SeaQL/sea-orm/pull/2466](https://redirect.github.com/SeaQL/sea-orm/pull/2466)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 08:13:25 +00:00
张小白
0625006a9e windows: Missing commit of #25412 (#25732)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-02-27 08:00:42 +00:00
renovate[bot]
0b96690446 Update Rust crate convert_case to 0.8.0 (#25711)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [convert_case](https://redirect.github.com/rutrum/convert-case) |
workspace.dependencies | minor | `0.7.0` -> `0.8.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.

🔕 **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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzYuMiIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 07:53:30 +00:00
renovate[bot]
bc22690620 Update Rust crate ctor to 0.4.0 (#25712)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ctor](https://redirect.github.com/mmastrac/rust-ctor) |
workspace.dependencies | minor | `0.3.0` -> `0.4.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.

🔕 **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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzYuMiIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 07:48:46 +00:00
ishanray
4eb82c0731 docs: Add DeepSeek to list of providers (#25730)
Release Notes:

- N/A
2025-02-27 07:42:14 +00:00
renovate[bot]
fa91379119 Update Rust crate libc to v0.2.170 (#25690)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [libc](https://redirect.github.com/rust-lang/libc) |
workspace.dependencies | patch | `0.2.169` -> `0.2.170` |

---

### Release Notes

<details>
<summary>rust-lang/libc (libc)</summary>

###
[`v0.2.170`](https://redirect.github.com/rust-lang/libc/releases/tag/0.2.170)

[Compare
Source](https://redirect.github.com/rust-lang/libc/compare/0.2.169...0.2.170)

##### Added

- Android: Declare `setdomainname` and `getdomainname`
[#&#8203;4212](https://redirect.github.com/rust-lang/libc/pull/4212)
- FreeBSD: Add `evdev` structures
[#&#8203;3756](https://redirect.github.com/rust-lang/libc/pull/3756)
- FreeBSD: Add the new `st_filerev` field to `stat32`
([#&#8203;4254](https://redirect.github.com/rust-lang/libc/pull/4254))
- Linux: Add ` SI_*`` and `TRAP_\*\`\` signal codes
[#&#8203;4225](https://redirect.github.com/rust-lang/libc/pull/4225)
- Linux: Add experimental configuration to enable 64-bit time in kernel
APIs, set by `RUST_LIBC_UNSTABLE_LINUX_TIME_BITS64`.
[#&#8203;4148](https://redirect.github.com/rust-lang/libc/pull/4148)
- Linux: Add recent socket timestamping flags
[#&#8203;4273](https://redirect.github.com/rust-lang/libc/pull/4273)
- Linux: Added new CANFD_FDF flag for the flags field of canfd_frame
[#&#8203;4223](https://redirect.github.com/rust-lang/libc/pull/4223)
- Musl: add CLONE_NEWTIME
[#&#8203;4226](https://redirect.github.com/rust-lang/libc/pull/4226)
- Solarish: add the posix_spawn family of functions
[#&#8203;4259](https://redirect.github.com/rust-lang/libc/pull/4259)

##### Deprecated

- Linux: deprecate kernel modules syscalls
[#&#8203;4228](https://redirect.github.com/rust-lang/libc/pull/4228)

##### Changed

- Emscripten: Assume version is at least 3.1.42
[#&#8203;4243](https://redirect.github.com/rust-lang/libc/pull/4243)

##### Fixed

- BSD: Correct the definition of `WEXITSTATUS`
[#&#8203;4213](https://redirect.github.com/rust-lang/libc/pull/4213)
- Hurd: Fix CMSG_DATA on 64bit systems
([#&#8203;4240](https://redirect.github.com/rust-lang/libc/pull/424))
- NetBSD: fix `getmntinfo`
([#&#8203;4265](https://redirect.github.com/rust-lang/libc/pull/4265)
- VxWorks: Fix the size of `time_t`
[#&#8203;426](https://redirect.github.com/rust-lang/libc/pull/426)

##### Other

- Add labels to FIXMEs
[#&#8203;4230](https://redirect.github.com/rust-lang/libc/pull/4230),
[#&#8203;4229](https://redirect.github.com/rust-lang/libc/pull/4229),
[#&#8203;4237](https://redirect.github.com/rust-lang/libc/pull/4237)
- CI: Bump FreeBSD CI to 13.4 and 14.2
[#&#8203;4260](https://redirect.github.com/rust-lang/libc/pull/4260)
- Copy definitions from core::ffi and centralize them
[#&#8203;4256](https://redirect.github.com/rust-lang/libc/pull/4256)
- Define c_char at top-level and remove per-target c_char definitions
[#&#8203;4202](https://redirect.github.com/rust-lang/libc/pull/4202)
- Port style.rs to syn and add tests for the style checker
[#&#8203;4220](https://redirect.github.com/rust-lang/libc/pull/4220)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 09:28:52 +02:00
renovate[bot]
da2320fb40 Update Rust crate chrono to v0.4.40 (#25684)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [chrono](https://redirect.github.com/chronotope/chrono) |
workspace.dependencies | patch | `0.4.39` -> `0.4.40` |

---

### Release Notes

<details>
<summary>chronotope/chrono (chrono)</summary>

###
[`v0.4.40`](https://redirect.github.com/chronotope/chrono/releases/tag/v0.4.40):
0.4.40

[Compare
Source](https://redirect.github.com/chronotope/chrono/compare/v0.4.39...v0.4.40)

#### What's Changed

- Add Month::num_days() by
[@&#8203;djc](https://redirect.github.com/djc) in
[https://github.com/chronotope/chrono/pull/1645](https://redirect.github.com/chronotope/chrono/pull/1645)
- Update Windows dependencies by
[@&#8203;kennykerr](https://redirect.github.com/kennykerr) in
[https://github.com/chronotope/chrono/pull/1646](https://redirect.github.com/chronotope/chrono/pull/1646)
- Feature/round_up method on DurationRound trait by
[@&#8203;MagnumTrader](https://redirect.github.com/MagnumTrader) in
[https://github.com/chronotope/chrono/pull/1651](https://redirect.github.com/chronotope/chrono/pull/1651)
- Expose `write_to` for `DelayedFormat` by
[@&#8203;tugtugtug](https://redirect.github.com/tugtugtug) in
[https://github.com/chronotope/chrono/pull/1654](https://redirect.github.com/chronotope/chrono/pull/1654)
- Update LICENSE.txt by
[@&#8203;maximevtush](https://redirect.github.com/maximevtush) in
[https://github.com/chronotope/chrono/pull/1656](https://redirect.github.com/chronotope/chrono/pull/1656)
- docs: fix minor typo by
[@&#8203;samfolo](https://redirect.github.com/samfolo) in
[https://github.com/chronotope/chrono/pull/1659](https://redirect.github.com/chronotope/chrono/pull/1659)
- Use NaiveDateTime for internal tz_info methods. by
[@&#8203;AVee](https://redirect.github.com/AVee) in
[https://github.com/chronotope/chrono/pull/1658](https://redirect.github.com/chronotope/chrono/pull/1658)
- Upgrade to windows-bindgen 0.60 by
[@&#8203;djc](https://redirect.github.com/djc) in
[https://github.com/chronotope/chrono/pull/1665](https://redirect.github.com/chronotope/chrono/pull/1665)
- Add quarter (%q) date string specifier by
[@&#8203;drinkcat](https://redirect.github.com/drinkcat) in
[https://github.com/chronotope/chrono/pull/1666](https://redirect.github.com/chronotope/chrono/pull/1666)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 09:28:33 +02:00
renovate[bot]
7bc31a69c3 Update aws-sdk-rust monorepo (#25704)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aws-config](https://redirect.github.com/smithy-lang/smithy-rs) |
dependencies | patch | `1.5.16` -> `1.5.17` |
| [aws-config](https://redirect.github.com/smithy-lang/smithy-rs) |
workspace.dependencies | patch | `1.5.16` -> `1.5.17` |
|
[aws-sdk-bedrockruntime](https://redirect.github.com/awslabs/aws-sdk-rust)
| workspace.dependencies | minor | `1.74.0` -> `1.75.0` |
| [aws-sdk-kinesis](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.61.0` -> `1.62.0` |
| [aws-sdk-s3](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.76.0` -> `1.77.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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzYuMiIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 09:26:59 +02:00
renovate[bot]
1e4752972f Update Rust crate rust-embed to v8.6.0 (#25720)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rust-embed](https://redirect.github.com/pyros2097/rust-embed) |
workspace.dependencies | minor | `8.5.0` -> `8.6.0` |

---

### Release Notes

<details>
<summary>pyros2097/rust-embed (rust-embed)</summary>

###
[`v8.6.0`](https://redirect.github.com/pyros2097/rust-embed/blob/HEAD/changelog.md#860---2025-02-25)

- Update include-flate to 0.3
[#&#8203;246](https://redirect.github.com/pyrossh/rust-embed/pull/246).
Thanks to [krant](https://redirect.github.com/krant)
- refactor: remove redundant reference and closure
[#&#8203;250](https://redirect.github.com/pyrossh/rust-embed/pull/250).
Thanks to [hamirmahal](https://redirect.github.com/hamirmahal)
- refactor: replace map().unwrap_or_else().
[#&#8203;250](https://redirect.github.com/pyrossh/rust-embed/pull/255).
Thanks to [hamirmahal](https://redirect.github.com/hamirmahal)
- Compatible with Axum 0.7.9
[#&#8203;253](https://redirect.github.com/pyrossh/rust-embed/pull/253).
Thanks to [wkmyws](https://redirect.github.com/wkmyws)
- Add allow_missing option to derive macro
[#&#8203;256](https://redirect.github.com/pyrossh/rust-embed/pull/256).
Thanks to [lirannl](https://redirect.github.com/lirannl)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 09:21:16 +02:00
renovate[bot]
91148a72a3 Update Rust crate uuid to v1.15.1 (#25728)
This PR contains the following updates:

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

---

### Release Notes

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

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

[Compare
Source](https://redirect.github.com/uuid-rs/uuid/compare/v1.15.0...v1.15.1)

#### What's Changed

- Guarantee v7 timestamp will never overflow by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/811](https://redirect.github.com/uuid-rs/uuid/pull/811)
- Prepare for 1.15.1 release by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/812](https://redirect.github.com/uuid-rs/uuid/pull/812)

**Full Changelog**:
https://github.com/uuid-rs/uuid/compare/v1.15.0...v1.15.1

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

[Compare
Source](https://redirect.github.com/uuid-rs/uuid/compare/v1.14.0...v1.15.0)

#### What's Changed

- Add a manual `Debug` implementation for NonNilUUid by
[@&#8203;rick-de-water](https://redirect.github.com/rick-de-water) in
[https://github.com/uuid-rs/uuid/pull/808](https://redirect.github.com/uuid-rs/uuid/pull/808)
- Support higher precision, shiftable timestamps in V7 UUIDs by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/809](https://redirect.github.com/uuid-rs/uuid/pull/809)
- Prepare for 1.15.0 release by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/810](https://redirect.github.com/uuid-rs/uuid/pull/810)

#### New Contributors

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

**Full Changelog**:
https://github.com/uuid-rs/uuid/compare/v1.14.0...v1.15.0

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

[Compare
Source](https://redirect.github.com/uuid-rs/uuid/compare/v1.13.2...v1.14.0)

#### What's Changed

- Add FromStr impls to the fmt structs by
[@&#8203;tysen](https://redirect.github.com/tysen) in
[https://github.com/uuid-rs/uuid/pull/806](https://redirect.github.com/uuid-rs/uuid/pull/806)
- Prepare for 1.14.0 release by
[@&#8203;KodrAus](https://redirect.github.com/KodrAus) in
[https://github.com/uuid-rs/uuid/pull/807](https://redirect.github.com/uuid-rs/uuid/pull/807)

#### New Contributors

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

**Full Changelog**:
https://github.com/uuid-rs/uuid/compare/v1.13.2...v1.14.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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzYuMiIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 09:10:49 +02:00
Anthony Eid
878b50c991 Update git panel entry checked box tooltip to say Stage/Unstage (#25678)
Before it would always say staged when a user hovered over the check
box. Now it will show the correct hover message depending on the state
of the entry

Release Notes:

- N/A
2025-02-27 00:29:26 -05:00
Marshall Bowers
e7df5ce61c assistant2: Avoid unnecessary String cloning in tool use (#25725)
This PR removes some unnecessary `String` cloning in the tool use paths.

We now store the data in `Arc<str>`s for cheap cloning.

Release Notes:

- N/A
2025-02-27 03:16:09 +00:00
Marshall Bowers
da22f21dec Move PopoverButton into ui (#25724)
This PR moves the `PopoverButton` component into the `ui` crate.

The `popover_button` crate only depended on `ui`, so there doesn't seem
to be a need for it to live in its own crate and add another step in the
crate graph.

Release Notes:

- N/A
2025-02-27 02:51:19 +00:00
Marshall Bowers
3505a17452 git_ui: Combine disjoint conditions into one (#25722)
This PR combines two disjoint conditions for the same value into one.

This makes it so the type checker can accurately reason about the
branches.

Release Notes:

- N/A
2025-02-27 02:33:25 +00:00
Marshall Bowers
81badd1fe6 Sort Cargo.tomls (#25721)
This PR sorts some `Cargo.toml`s that had become unsorted.

Release Notes:

- N/A
2025-02-27 02:28:59 +00:00
renovate[bot]
6dacc751fc Update Rust crate schemars to v0.8.22 (#25695)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [schemars](https://graham.cool/schemars/)
([source](https://redirect.github.com/GREsau/schemars)) |
workspace.dependencies | patch | `0.8.21` -> `0.8.22` |

---

### Release Notes

<details>
<summary>GREsau/schemars (schemars)</summary>

###
[`v0.8.22`](https://redirect.github.com/GREsau/schemars/blob/HEAD/CHANGELOG.md#0822---2025-02-25)

[Compare
Source](https://redirect.github.com/GREsau/schemars/compare/v0.8.21...v0.8.22)

##### Fixed:

- Fix compatibility with rust 2024 edition
([https://github.com/GREsau/schemars/pull/378](https://redirect.github.com/GREsau/schemars/pull/378))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 21:28:21 -05:00
张小白
a0d1555470 windows: Fix terminal inline assistant (#25715)
Closes #18518
Closes #20546

Release Notes:

- N/A
2025-02-27 10:03:46 +08:00
Mikayla Maki
8ba7b349a5 Make the branch picker in the commit modal a popover (#25697)
Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
2025-02-27 01:56:07 +00:00
Shardul Vaidya
11838cf89e bedrock: Fix region bug (#25716)
Closes #25714

Internal team reported issue where the Bedrock provider defaulted to
"us-east-1" for all requests regardless of what is configured in the
credentials until first zed restart.

Release Notes:

- Fixed an issue where the Bedrock model provider would not always
respect the region.
2025-02-26 20:55:03 -05:00
renovate[bot]
84ded96cb2 Update serde monorepo to v1.0.218 (#25705)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [serde](https://serde.rs)
([source](https://redirect.github.com/serde-rs/serde)) | dependencies |
patch | `1.0.217` -> `1.0.218` |
| [serde](https://serde.rs)
([source](https://redirect.github.com/serde-rs/serde)) |
workspace.dependencies | patch | `1.0.217` -> `1.0.218` |
| [serde_derive](https://serde.rs)
([source](https://redirect.github.com/serde-rs/serde)) |
workspace.dependencies | patch | `1.0.217` -> `1.0.218` |

---

### Release Notes

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

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

[Compare
Source](https://redirect.github.com/serde-rs/serde/compare/v1.0.217...v1.0.218)

-   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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNzYuMiIsInVwZGF0ZWRJblZlciI6IjM5LjE3Ni4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 19:59:20 -05:00
Marshall Bowers
afc61b9527 assistant2: Automatically respond to the model with tool results (#25706)
This PR updates the tool use flow in Assistant 2 to automatically
respond to the model with tool results when the tools have finished
running.

Release Notes:

- N/A
2025-02-27 00:37:44 +00:00
张小白
672a472a23 windows: Implement cli and handle open_urls (#25412)
Closes #ISSUE

Release Notes:

- N/A
2025-02-26 16:27:19 -08:00
Marshall Bowers
9822d9673c assistant2: Add Thread::send_to_model method (#25703)
This PR adds a new `send_to_model` method to the `Thread` to encapsulate
more of the thread-specific capabilities.

We then call this in `MessageEditor::send_to_model`.

Release Notes:

- N/A
2025-02-27 00:16:44 +00:00
Marshall Bowers
f0dec2f576 assistant2: Visualize tool use (#25692)
This PR adds visuals for tool use in Assistant 2:

<img width="1309" alt="Screenshot 2025-02-26 at 5 57 14 PM"
src="https://github.com/user-attachments/assets/4083ff65-a2f1-4a43-8815-0bade2c00af2"
/>

Release Notes:

- N/A
2025-02-26 23:19:24 +00:00
renovate[bot]
9f7c65df44 Update Rust crate clap to v4.5.31 (#25685)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [clap](https://redirect.github.com/clap-rs/clap) |
workspace.dependencies | patch | `4.5.30` -> `4.5.31` |

---

### Release Notes

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

###
[`v4.5.31`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4531---2025-02-24)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.5.30...v4.5.31)

##### Features

-   Add `ValueParserFactory` for `Saturating<T>`

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 17:55:48 -05:00
renovate[bot]
372e485ba8 Update Rust crate log to v0.4.26 (#25691)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [log](https://redirect.github.com/rust-lang/log) |
workspace.dependencies | patch | `0.4.25` -> `0.4.26` |

---

### Release Notes

<details>
<summary>rust-lang/log (log)</summary>

###
[`v0.4.26`](https://redirect.github.com/rust-lang/log/blob/HEAD/CHANGELOG.md#0426---2025-02-18)

[Compare
Source](https://redirect.github.com/rust-lang/log/compare/0.4.25...0.4.26)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 17:55:29 -05:00
Marshall Bowers
b8fb416892 assistant2: Exclude tool uses and results when summarizing threads (#25689)
This PR fixes the generation of summaries for threads when tools are
being used.

Previously we were including the tool uses in the summarization request,
but this would result in invalid messages being sent to the model and
summaries not being generated.

We now exclude any tool uses or results from the model when summarizing
a thread.

Release Notes:

- N/A
2025-02-26 22:24:56 +00:00
Marshall Bowers
6a1c104522 language_settings: Add auto alias for subtle edit prediction mode (#25686)
This PR makes `auto` an alias for the `subtle` edit prediction mode.

Right now I'm in a state where I can't have valid settings in both
development and Nightly because the settings values are disparate.

Release Notes:

- N/A
2025-02-26 22:06:17 +00:00
Peter Tripp
b06da7f7fd ssh: Allow ssh -F ssh_config (#25619)
- Closes https://github.com/zed-industries/zed/issues/22818

Usage: `ssh -F ssh_config user@host.tld`

```
-F configfile
    Specifies an alternative per-user configuration file. If a configuration file
    is given on the command line, the system-wide configuration file
    (/etc/ssh/ssh_config) will be ignored. The default for the per-user
    configuration file is ~/.ssh/config. If set to “none”, no configuration files
    will be read.
```

Release Notes:

- ssh: Added support for specifying ssh_config files (`ssh -F
ssh_config`) in connection string
2025-02-26 16:23:25 -05:00
Peter Tripp
f80035e0ff Support busybox wget for downloading zed-remote-server (#25621)
- Closes: https://github.com/zed-industries/zed/issues/22380

Arch linux ships busybox wget not gnu wget.
BusyBox wget does not support `--max-redirect`.

Release Notes:

- ssh: Add support for downloading `zed-remote-server` with busybox wget (ArchLinux, etc)
2025-02-26 16:22:56 -05:00
renovate[bot]
7664c1cef5 Update actions/upload-artifact digest to 4cec3d8 (#25680)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | digest | `65c4c4a` -> `4cec3d8` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 16:07:26 -05:00
renovate[bot]
967792119b Pin clechasseur/rs-cargo action to 8435b10 (#25679)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[clechasseur/rs-cargo](https://redirect.github.com/clechasseur/rs-cargo)
| action | pinDigest | -> `8435b10` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 16:07:05 -05:00
João Marcos
be1ac78e11 Unfold buffers in multibuffers when editing them (#25677)
Release Notes:

- Multibuffers: Unfold excerpts when editing their contents.

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-02-26 17:39:33 -03:00
Kirill Bulatov
b5a1ae6526 Improve Zed tasks' ZED_WORKTREE_ROOT fallbacks (#25605)
Closes https://github.com/zed-industries/zed/issues/22912

Reworks the task context infrastructure so that it's possible to have
multiple contexts at the same time, and stores all possible worktree
context there.
Task UI code is now falling back to the "active" worktree context, if
active item's context did not produce a resolved task.

Current code does not produce meaningful results for projects with
multiple worktrees to avoid ambiguity and design changes: instead of
resolving tasks per worktree context available, extra worktree context
is only used when resolving tasks from the same worktree.

Release Notes:

- Improved Zed tasks' `ZED_WORKTREE_ROOT` fallbacks
2025-02-26 22:30:31 +02:00
Kirill Bulatov
d2b49de0e4 Dismiss active diagnostics on invalidation (#25646)
When migrating to gpui2,
588976d27a (diff-a3da3181e4ab4f73aa1697d7b6dc0caa0c17b2a187fb83b076dfc0234ec91f54R21)
removed the diagnostic style for "active but invalid" case: presumably,
it served as some sort of a cursor to show where to move on after the
diagnostics update, on the next `GoTo[Prev]Diagnostic` action call.

As this change went unchanged for some time, another approach is tested
now, to be more integrated with inline diagnostics: now, the active
state is cleared

Same as before this change, another `GoTo[Prev]Diagnostic` action call
will be needed to re-expand a new diagnostics, but this change makes
this expansion to happen after the cursor — before the change, Zed would
continue from the stale diagnostics.

Release Notes:

- Fixed active diagnostics becoming stale
2025-02-26 22:30:23 +02:00
Agus Zubiaga
d694458659 edit predictions: Rename edit prediction modes (#25657)
`auto` -> `stealth`
`eager_preview` -> `eager`

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-02-26 20:23:39 +00:00
Cole Miller
7a34dd9888 Save buffers after restoring hunks in the project diff (#25620)
This PR fixes a bug where using the project diff editor to restore hunks
from a file that's not open in its own buffer would cause those reverts
to be lost once the project diff drops its excerpts for that file.

The fix is to save the buffers after restoring them but before the
excerpts are (potentially) dropped. This is done for the project diff
editor only. If we fail to save the affected files, we add their buffers
to the active workspace, so that the reverted contents are preserved and
the user can try again to save them.

- [x] Get it working
- [x] Test
- [ ] ~~Clean up boolean soup~~

Co-authored-by: Max <max@zed.dev>

Release Notes:

- N/A
2025-02-26 15:16:17 -05:00
Michael Sloan
add7ae8052 Try to reveal selection changing issues in undo/redo via logging (#25676)
This will hopefully help debug #22692. I tried this for a while locally
and saw neither these logs nor the issue.

Release Notes:

- N/A
2025-02-26 19:48:15 +00:00
Marshall Bowers
c53020ceaf vim: Combine match arms in Mode::is_visual (#25675)
This PR refactors the `Mode::is_visual` implementation to combine some
of the `match` arms.

Release Notes:

- N/A
2025-02-26 19:45:04 +00:00
Cole Miller
eeac1a9287 Style filenames and paths in project diff buffer headers according to git status (#25653)
This substitutes for the icons that we previously kept in these headers.

cc @iamnbutler 

Release Notes:

- N/A
2025-02-26 14:43:10 -05:00
0x2CA
e83ebd1fab vim: Add more tests for replace with register (#25316)
Closes #ISSUE

Add more tests

Release Notes:

- N/A
2025-02-26 12:38:50 -07:00
Conrad Irwin
afb0fd609b Chunk git status entries (#25627)
Prevents us trying to write 5M untracked files to postgres in one commit

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-02-26 12:38:16 -07:00
Conrad Irwin
b2a685f00a Fix staging error reporting (#25630)
Closes #ISSUE

Release Notes:

- N/A
2025-02-26 12:38:04 -07:00
Yicheng Liu
089ea5da50 vim: Fix back quotes not recognized as object (#24999)
Currently back quotes ``` `` ``` not recognized as an object in vim
mode, so ```c i ` ```, ```d i ` ``` not working.

It seems to be a typo introduced in #22632 : The`DoubleQuotes` line was
doubled while the `BackQuotes` line was missing.

Release Notes:

- vim: Fixed back quotes ``` `` ``` not recognized as object.

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-26 14:21:12 -05:00
smit
b6e8db244c vim: Fix search submit panic (#25673)
In file search submit action, handle unwrap when there are no prior
selection.

Fix is for recently made commits, hence no release notes.

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <anthony@zed.dev>
2025-02-27 00:40:46 +05:30
Brandon Li
6267ab0396 vim: Add ability to change default mode (#25067)
Closes #13881, and technically resolves #14927.

Release Notes:

- Added the ability to set the default Vim mode.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-26 13:51:07 -05:00
Joseph T. Lyons
d105f04be5 Bump Zed to v0.177 (#25669)
Release Notes:

-N/A
2025-02-26 18:31:58 +00:00
Mikayla Maki
5edded5c02 Simplify project git code (#25662)
This was originally a part of another PR, but I wanted to get the
refactoring in and shift focus to working on bugs.

This causes all git commands via the `Repository` entity to be
serialized, and allows us to return values other than `Result<()>`

Release Notes:

- N/A
2025-02-26 18:16:10 +00:00
Marshall Bowers
78da39e19b assistant2: Add ability to delete past prompt editors (#25667)
This PR adds the ability to delete past prompt editors in Assistant 2,
the same way you can with threads.

Release Notes:

- N/A
2025-02-26 18:02:36 +00:00
176 changed files with 4700 additions and 2284 deletions

View File

@@ -110,13 +110,13 @@ jobs:
run: ./script/clippy run: ./script/clippy
- name: Install cargo-machete - name: Install cargo-machete
uses: clechasseur/rs-cargo@v2 uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with: with:
command: install command: install
args: cargo-machete@0.7.0 args: cargo-machete@0.7.0
- name: Check unused dependencies - name: Check unused dependencies
uses: clechasseur/rs-cargo@v2 uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with: with:
command: machete command: machete
@@ -240,7 +240,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
name: (Windows) Run Clippy and tests name: (Windows) Run Clippy and tests
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-1 runs-on: hosted-windows-2
steps: steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020 # more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git - name: Enable longer pathnames for git
@@ -273,8 +273,7 @@ jobs:
- name: cargo clippy - name: cargo clippy
working-directory: ${{ env.ZED_WORKSPACE }} working-directory: ${{ env.ZED_WORKSPACE }}
# Windows can't run shell scripts, so we need to use `cargo xtask`. run: ./script/clippy.ps1
run: cargo xtask clippy
- name: Run tests - name: Run tests
uses: ./.github/actions/run_tests_windows uses: ./.github/actions/run_tests_windows
@@ -359,14 +358,14 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label - name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label - name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -417,7 +416,7 @@ jobs:
run: script/bundle-linux run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label - name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -465,7 +464,7 @@ jobs:
run: script/bundle-linux run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label - name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with: with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz

View File

@@ -65,7 +65,7 @@ jobs:
command: deploy .cloudflare/docs-proxy/src/worker.js command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Preserve Wrangler logs - name: Preserve Wrangler logs
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
if: always() if: always()
with: with:
name: wrangler_logs name: wrangler_logs

191
Cargo.lock generated
View File

@@ -490,6 +490,7 @@ dependencies = [
"ui", "ui",
"util", "util",
"uuid", "uuid",
"vim_mode_setting",
"workspace", "workspace",
"zed_actions", "zed_actions",
] ]
@@ -1175,9 +1176,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-config" name = "aws-config"
version = "1.5.16" version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915" checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1268,9 +1269,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-bedrockruntime" name = "aws-sdk-bedrockruntime"
version = "1.74.0" version = "1.75.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6938541d1948a543bca23303fec4cff9c36bf0e63b8fa3ae1b337bcb9d5b81af" checksum = "2ddf7475b6f50a1a5be8edb1bcdf6e4ae00feed5b890d14a3f1f0e14d76f5a16"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1292,9 +1293,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-kinesis" name = "aws-sdk-kinesis"
version = "1.61.0" version = "1.62.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89f2163d8704e8fdcd51ec6c2e0441c418471e422ee9690451b17a1c46344e1a" checksum = "e31622345afd0c35d33c1cbba73ccf9fb88e09857413d8963dea2c493e00704d"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1314,9 +1315,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-s3" name = "aws-sdk-s3"
version = "1.76.0" version = "1.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e83401ad7287ad15244d557e35502c2a94105ca5b41d656c391f1a4fc04ca2" checksum = "34e87342432a3de0e94e82c99a7cbd9042f99de029ae1f4e368160f9e9929264"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1348,9 +1349,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sso" name = "aws-sdk-sso"
version = "1.58.0" version = "1.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770" checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1370,9 +1371,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-ssooidc" name = "aws-sdk-ssooidc"
version = "1.59.0" version = "1.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809" checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1392,9 +1393,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sts" name = "aws-sdk-sts"
version = "1.59.0" version = "1.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00" checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1455,9 +1456,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-checksums" name = "aws-smithy-checksums"
version = "0.62.0" version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f45a1c384d7a393026bc5f5c177105aa9fa68e4749653b985707ac27d77295" checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c"
dependencies = [ dependencies = [
"aws-smithy-http", "aws-smithy-http",
"aws-smithy-types", "aws-smithy-types",
@@ -1807,7 +1808,7 @@ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"log", "log",
@@ -1830,7 +1831,7 @@ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"log", "log",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@@ -2400,25 +2401,6 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "cbindgen"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [
"clap",
"heck 0.4.1",
"indexmap",
"log",
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn 2.0.90",
"tempfile",
"toml 0.8.20",
]
[[package]] [[package]]
name = "cbindgen" name = "cbindgen"
version = "0.28.0" version = "0.28.0"
@@ -2516,9 +2498,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.39" version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -2526,7 +2508,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.52.6", "windows-link",
] ]
[[package]] [[package]]
@@ -2586,9 +2568,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.30" version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -2596,9 +2578,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.30" version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -2653,6 +2635,7 @@ dependencies = [
"serde", "serde",
"tempfile", "tempfile",
"util", "util",
"windows 0.58.0",
] ]
[[package]] [[package]]
@@ -3020,7 +3003,6 @@ dependencies = [
"collections", "collections",
"gpui", "gpui",
"linkme", "linkme",
"once_cell",
"parking_lot", "parking_lot",
"theme", "theme",
] ]
@@ -3142,9 +3124,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.7.1" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@@ -3523,11 +3505,10 @@ dependencies = [
[[package]] [[package]]
name = "crc64fast-nvme" name = "crc64fast-nvme"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5e2ee08013e3f228d6d2394116c4549a6df77708442c62d887d83f68ef2ee37" checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3"
dependencies = [ dependencies = [
"cbindgen 0.27.0",
"crc", "crc",
] ]
@@ -3664,18 +3645,19 @@ dependencies = [
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.3.6" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21d960ecacd0a1bf55e73144b72de745e7bf275c7952c50e36e8af0a0cb7ab1f" checksum = "a7747ac3a66a06f4ee6718686c8ea976d2d05fb30ada93ebd76b3f9aef97257c"
dependencies = [ dependencies = [
"ctor-proc-macro", "ctor-proc-macro",
"dtor",
] ]
[[package]] [[package]]
name = "ctor-proc-macro" name = "ctor-proc-macro"
version = "0.0.4" version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c426d2ba3e525b39c1f0a9ba41b9fe61878dee11fa4e4a76b6ab440f46c5db5d" checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
@@ -4051,6 +4033,21 @@ dependencies = [
"phf", "phf",
] ]
[[package]]
name = "dtor"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf39a0bfd1f94d62ffdb2802a7e6244c0f34f6ebacf5d4c26547d08cd1d67a5"
dependencies = [
"dtor-proc-macro",
]
[[package]]
name = "dtor-proc-macro"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055"
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@@ -4105,7 +4102,7 @@ dependencies = [
"client", "client",
"clock", "clock",
"collections", "collections",
"convert_case 0.7.1", "convert_case 0.8.0",
"ctor", "ctor",
"db", "db",
"emojis", "emojis",
@@ -5353,6 +5350,7 @@ dependencies = [
"serde_json", "serde_json",
"smol", "smol",
"sum_tree", "sum_tree",
"tempfile",
"text", "text",
"time", "time",
"unindent", "unindent",
@@ -5422,6 +5420,7 @@ dependencies = [
"theme", "theme",
"time", "time",
"ui", "ui",
"unindent",
"util", "util",
"windows 0.58.0", "windows 0.58.0",
"workspace", "workspace",
@@ -5556,7 +5555,7 @@ dependencies = [
"bytemuck", "bytemuck",
"calloop", "calloop",
"calloop-wayland-source", "calloop-wayland-source",
"cbindgen 0.28.0", "cbindgen",
"cocoa 0.26.0", "cocoa 0.26.0",
"collections", "collections",
"core-foundation 0.9.4", "core-foundation 0.9.4",
@@ -7229,9 +7228,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]] [[package]]
name = "libdbus-sys" name = "libdbus-sys"
@@ -7575,9 +7574,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.25" version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
dependencies = [ dependencies = [
"serde", "serde",
"value-bag", "value-bag",
@@ -7948,7 +7947,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"convert_case 0.7.1", "convert_case 0.8.0",
"log", "log",
"pretty_assertions", "pretty_assertions",
"streaming-iterator", "streaming-iterator",
@@ -9758,6 +9757,15 @@ dependencies = [
"indexmap", "indexmap",
] ]
[[package]]
name = "pgvector"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e8871b6d7ca78348c6cd29b911b94851f3429f0cd403130ca17f26c1fb91a6"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.2" version = "0.11.2"
@@ -10382,7 +10390,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [ dependencies = [
"bytes 1.10.0", "bytes 1.10.0",
"heck 0.5.0", "heck 0.5.0",
"itertools 0.12.1", "itertools 0.10.5",
"log", "log",
"multimap 0.10.0", "multimap 0.10.0",
"once_cell", "once_cell",
@@ -10415,7 +10423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.12.1", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.90",
@@ -11443,9 +11451,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed" name = "rust-embed"
version = "8.5.0" version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
dependencies = [ dependencies = [
"rust-embed-impl", "rust-embed-impl",
"rust-embed-utils", "rust-embed-utils",
@@ -11454,9 +11462,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-impl" name = "rust-embed-impl"
version = "8.5.0" version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -11467,9 +11475,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-utils" name = "rust-embed-utils"
version = "8.5.0" version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
dependencies = [ dependencies = [
"globset", "globset",
"sha2", "sha2",
@@ -11744,9 +11752,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.21" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"indexmap", "indexmap",
@@ -11757,9 +11765,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.21" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -11822,17 +11830,18 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm" name = "sea-orm"
version = "1.1.5" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00733e5418e8ae3758cdb988c3654174e716230cc53ee2cb884207cf86a23029" checksum = "13fba7b2c749b2d0a00303d5cb13e6761e39a4172554bdf930852cac4e7aeabd"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"bigdecimal", "bigdecimal",
"chrono", "chrono",
"futures 0.3.31", "futures-util",
"log", "log",
"ouroboros", "ouroboros",
"pgvector",
"rust_decimal", "rust_decimal",
"sea-orm-macros", "sea-orm-macros",
"sea-query", "sea-query",
@@ -11850,9 +11859,9 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm-macros" name = "sea-orm-macros"
version = "1.1.5" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a98408f82fb4875d41ef469a79944a7da29767c7b3e4028e22188a3dd613b10f" checksum = "2568cff8d35d5150b4276cc0dd766192a587f64b6ece60ae3706e0872c4eb209"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
@@ -12045,18 +12054,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.217" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -14569,7 +14578,7 @@ dependencies = [
name = "ui_macros" name = "ui_macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"convert_case 0.7.1", "convert_case 0.8.0",
"linkme", "linkme",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -14772,9 +14781,9 @@ dependencies = [
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.13.2" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
"serde", "serde",
@@ -15897,6 +15906,12 @@ dependencies = [
"syn 2.0.90", "syn 2.0.90",
] ]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]] [[package]]
name = "windows-registry" name = "windows-registry"
version = "0.2.0" version = "0.2.0"
@@ -16732,7 +16747,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.176.0" version = "0.177.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"anyhow", "anyhow",
@@ -16836,6 +16851,7 @@ dependencies = [
"tasks_ui", "tasks_ui",
"telemetry", "telemetry",
"telemetry_events", "telemetry_events",
"tempfile",
"terminal_view", "terminal_view",
"theme", "theme",
"theme_extension", "theme_extension",
@@ -16852,6 +16868,7 @@ dependencies = [
"vim", "vim",
"vim_mode_setting", "vim_mode_setting",
"welcome", "welcome",
"which 6.0.3",
"windows 0.58.0", "windows 0.58.0",
"winresource", "winresource",
"workspace", "workspace",
@@ -16950,7 +16967,7 @@ dependencies = [
[[package]] [[package]]
name = "zed_html" name = "zed_html"
version = "0.1.5" version = "0.1.6"
dependencies = [ dependencies = [
"zed_extension_api 0.1.0", "zed_extension_api 0.1.0",
] ]

View File

@@ -370,7 +370,7 @@ zeta = { path = "crates/zeta" }
# #
aho-corasick = "1.1" aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e"} alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" }
any_vec = "0.14" any_vec = "0.14"
anyhow = "1.0.86" anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] } arrayvec = { version = "0.7.4", features = ["serde"] }
@@ -405,10 +405,10 @@ chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26" cocoa = "0.26"
cocoa-foundation = "0.2.0" cocoa-foundation = "0.2.0"
convert_case = "0.7.0" convert_case = "0.8.0"
core-foundation = "0.9.3" core-foundation = "0.9.3"
core-foundation-sys = "0.8.6" core-foundation-sys = "0.8.6"
ctor = "0.3.0" ctor = "0.4.0"
dashmap = "6.0" dashmap = "6.0"
derive_more = "0.99.17" derive_more = "0.99.17"
dirs = "4.0" dirs = "4.0"
@@ -455,7 +455,6 @@ nanoid = "0.4"
nbformat = { version = "0.10.0" } nbformat = { version = "0.10.0" }
nix = "0.29" nix = "0.29"
num-format = "0.4.4" num-format = "0.4.4"
once_cell = "1.20"
ordered-float = "2.1.1" ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] } palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"
@@ -544,7 +543,7 @@ tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23" tree-sitter-css = "0.23"
tree-sitter-elixir = "0.3" tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0" tree-sitter-embedded-template = "0.23.0"
tree-sitter-gitcommit = {git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9"} tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
tree-sitter-go = "0.23" tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" } tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" } tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
@@ -619,6 +618,7 @@ features = [
"Win32_Storage_FileSystem", "Win32_Storage_FileSystem",
"Win32_System_Com", "Win32_System_Com",
"Win32_System_Com_StructuredStorage", "Win32_System_Com_StructuredStorage",
"Win32_System_Console",
"Win32_System_DataExchange", "Win32_System_DataExchange",
"Win32_System_LibraryLoader", "Win32_System_LibraryLoader",
"Win32_System_Memory", "Win32_System_Memory",
@@ -639,7 +639,7 @@ features = [
# TODO livekit https://github.com/RustAudio/cpal/pull/891 # TODO livekit https://github.com/RustAudio/cpal/pull/891
[patch.crates-io] [patch.crates-io]
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" } cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls"} real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" }
[profile.dev] [profile.dev]
split-debuginfo = "unpacked" split-debuginfo = "unpacked"
@@ -709,6 +709,9 @@ debug = "full"
lto = false lto = false
codegen-units = 16 codegen-units = 16
[workspace.lints.rust]
unexpected_cfgs = { level = "allow" }
[workspace.lints.clippy] [workspace.lints.clippy]
dbg_macro = "deny" dbg_macro = "deny"
todo = "deny" todo = "deny"

View File

@@ -606,7 +606,7 @@
"ctrl-n": "assistant2::NewThread", "ctrl-n": "assistant2::NewThread",
"new": "assistant2::NewThread", "new": "assistant2::NewThread",
"ctrl-shift-h": "assistant2::OpenHistory", "ctrl-shift-h": "assistant2::OpenHistory",
"ctrl-alt-/": "assistant2::ToggleModelSelector", "ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-shift-a": "assistant2::ToggleContextPicker", "ctrl-shift-a": "assistant2::ToggleContextPicker",
"ctrl-e": "assistant2::ChatMode", "ctrl-e": "assistant2::ChatMode",
"ctrl-alt-e": "assistant2::RemoveAllContext" "ctrl-alt-e": "assistant2::RemoveAllContext"

View File

@@ -238,7 +238,7 @@
"cmd-n": "assistant2::NewThread", "cmd-n": "assistant2::NewThread",
"cmd-alt-p": "assistant2::NewPromptEditor", "cmd-alt-p": "assistant2::NewPromptEditor",
"cmd-shift-h": "assistant2::OpenHistory", "cmd-shift-h": "assistant2::OpenHistory",
"cmd-alt-/": "assistant2::ToggleModelSelector", "cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-shift-a": "assistant2::ToggleContextPicker", "cmd-shift-a": "assistant2::ToggleContextPicker",
"cmd-e": "assistant2::ChatMode", "cmd-e": "assistant2::ChatMode",
"cmd-alt-e": "assistant2::RemoveAllContext" "cmd-alt-e": "assistant2::RemoveAllContext"
@@ -658,7 +658,7 @@
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-shift-a": "assistant2::ToggleContextPicker", "cmd-shift-a": "assistant2::ToggleContextPicker",
"cmd-alt-/": "assistant2::ToggleModelSelector", "cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-alt-e": "assistant2::RemoveAllContext", "cmd-alt-e": "assistant2::RemoveAllContext",
"ctrl-[": "assistant::CyclePreviousInlineAssist", "ctrl-[": "assistant::CyclePreviousInlineAssist",
"ctrl-]": "assistant::CycleNextInlineAssist" "ctrl-]": "assistant::CycleNextInlineAssist"

View File

@@ -176,8 +176,8 @@
"show_completion_documentation": true, "show_completion_documentation": true,
// Show method signatures in the editor, when inside parentheses. // Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false, "auto_signature_help": false,
/// Whether to show the signature help after completion or a bracket pair inserted. // Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also. // If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false, "show_signature_help_after_edits": false,
// Whether to show wrap guides (vertical rulers) in the editor. // Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value // Setting this to true will show a guide at the 'preferred_line_length' value
@@ -298,11 +298,11 @@
// - "information": show only errors, warnings, and information // - "information": show only errors, warnings, and information
// - "all" or true: show all diagnostics // - "all" or true: show all diagnostics
"diagnostics": "all", "diagnostics": "all",
/// Forcefully enable or disable the scrollbar for each axis // Forcefully enable or disable the scrollbar for each axis
"axes": { "axes": {
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. // When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
"horizontal": true, "horizontal": true,
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings. // When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
"vertical": true "vertical": true
} }
}, },
@@ -328,24 +328,24 @@
"folds": true "folds": true
}, },
"indent_guides": { "indent_guides": {
/// Whether to show indent guides in the editor. // Whether to show indent guides in the editor.
"enabled": true, "enabled": true,
/// The width of the indent guides in pixels, between 1 and 10. // The width of the indent guides in pixels, between 1 and 10.
"line_width": 1, "line_width": 1,
/// The width of the active indent guide in pixels, between 1 and 10. // The width of the active indent guide in pixels, between 1 and 10.
"active_line_width": 1, "active_line_width": 1,
/// Determines how indent guides are colored. // Determines how indent guides are colored.
/// This setting can take the following three values: // This setting can take the following three values:
/// ///
/// 1. "disabled" // 1. "disabled"
/// 2. "fixed" // 2. "fixed"
/// 3. "indent_aware" // 3. "indent_aware"
"coloring": "fixed", "coloring": "fixed",
/// Determines how indent guide backgrounds are colored. // Determines how indent guide backgrounds are colored.
/// This setting can take the following two values: // This setting can take the following two values:
/// ///
/// 1. "disabled" // 1. "disabled"
/// 2. "indent_aware" // 2. "indent_aware"
"background_coloring": "disabled" "background_coloring": "disabled"
}, },
// Whether the editor will scroll beyond the last line. // Whether the editor will scroll beyond the last line.
@@ -379,6 +379,9 @@
// 3. Never populate the search query // 3. Never populate the search query
// "never" // "never"
"seed_search_query_from_cursor": "always", "seed_search_query_from_cursor": "always",
// When enabled, automatically adjusts search case sensitivity based on your query.
// If your search query contains any uppercase letters, the search becomes case-sensitive;
// if it contains only lowercase letters, the search becomes case-insensitive.
"use_smartcase_search": false, "use_smartcase_search": false,
// Inlay hint related settings // Inlay hint related settings
"inlay_hints": { "inlay_hints": {
@@ -398,7 +401,16 @@
"edit_debounce_ms": 700, "edit_debounce_ms": 700,
// Time to wait after scrolling the buffer, before requesting the hints, // Time to wait after scrolling the buffer, before requesting the hints,
// set to 0 to disable debouncing. // set to 0 to disable debouncing.
"scroll_debounce_ms": 50 "scroll_debounce_ms": 50,
/// A set of modifiers which, when pressed, will toggle the visibility of inlay hints.
/// If the set if empty or not all the modifiers specified are pressed, inlay hints will not be toggled.
"toggle_on_modifiers_press": {
"control": false,
"shift": false,
"alt": false,
"platform": false,
"function": false
}
}, },
"project_panel": { "project_panel": {
// Whether to show the project panel button in the status bar // Whether to show the project panel button in the status bar
@@ -424,32 +436,32 @@
// Whether to fold directories automatically and show compact folders // Whether to fold directories automatically and show compact folders
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside. // (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
"auto_fold_dirs": true, "auto_fold_dirs": true,
/// Scrollbar-related settings // Scrollbar-related settings
"scrollbar": { "scrollbar": {
/// When to show the scrollbar in the project panel. // When to show the scrollbar in the project panel.
/// This setting can take five values: // This setting can take five values:
/// ///
/// 1. null (default): Inherit editor settings // 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or // 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default): // follow the system's configured behavior (default):
/// "auto" // "auto"
/// 3. Match the system's configured behavior: // 3. Match the system's configured behavior:
/// "system" // "system"
/// 4. Always show the scrollbar: // 4. Always show the scrollbar:
/// "always" // "always"
/// 5. Never show the scrollbar: // 5. Never show the scrollbar:
/// "never" // "never"
"show": null "show": null
}, },
/// Which files containing diagnostic errors/warnings to mark in the project panel. // Which files containing diagnostic errors/warnings to mark in the project panel.
/// This setting can take the following three values: // This setting can take the following three values:
/// ///
/// 1. Do not mark any files: // 1. Do not mark any files:
/// "off" // "off"
/// 2. Only mark files with errors: // 2. Only mark files with errors:
/// "errors" // "errors"
/// 3. Mark files with errors and warnings: // 3. Mark files with errors and warnings:
/// "all" // "all"
"show_diagnostics": "all", "show_diagnostics": "all",
// Settings related to indent guides in the project panel. // Settings related to indent guides in the project panel.
"indent_guides": { "indent_guides": {
@@ -482,8 +494,8 @@
// when a corresponding outline entry becomes active. // when a corresponding outline entry becomes active.
// Gitignored entries are never auto revealed. // Gitignored entries are never auto revealed.
"auto_reveal_entries": true, "auto_reveal_entries": true,
/// Whether to fold directories automatically // Whether to fold directories automatically
/// when a directory has only one directory inside. // when a directory has only one directory inside.
"auto_fold_dirs": true, "auto_fold_dirs": true,
// Settings related to indent guides in the outline panel. // Settings related to indent guides in the outline panel.
"indent_guides": { "indent_guides": {
@@ -496,21 +508,21 @@
// "never" // "never"
"show": "always" "show": "always"
}, },
/// Scrollbar-related settings // Scrollbar-related settings
"scrollbar": { "scrollbar": {
/// When to show the scrollbar in the project panel. // When to show the scrollbar in the project panel.
/// This setting can take five values: // This setting can take five values:
/// ///
/// 1. null (default): Inherit editor settings // 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or // 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default): // follow the system's configured behavior (default):
/// "auto" // "auto"
/// 3. Match the system's configured behavior: // 3. Match the system's configured behavior:
/// "system" // "system"
/// 4. Always show the scrollbar: // 4. Always show the scrollbar:
/// "always" // "always"
/// 5. Never show the scrollbar: // 5. Never show the scrollbar:
/// "never" // "never"
"show": null "show": null
} }
}, },
@@ -628,7 +640,7 @@
"show": true, "show": true,
// Whether or not to show the navigation history buttons. // Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true, "show_nav_history_buttons": true,
/// Whether or not to show the tab bar buttons. // Whether or not to show the tab bar buttons.
"show_tab_bar_buttons": true "show_tab_bar_buttons": true
}, },
// Settings related to the editor's tabs // Settings related to the editor's tabs
@@ -650,16 +662,16 @@
// 3. Activate the left neighbour tab if present // 3. Activate the left neighbour tab if present
// "left_neighbour" // "left_neighbour"
"activate_on_close": "history", "activate_on_close": "history",
/// Which files containing diagnostic errors/warnings to mark in the tabs. // Which files containing diagnostic errors/warnings to mark in the tabs.
/// Diagnostics are only shown when file icons are also active. // Diagnostics are only shown when file icons are also active.
/// This setting only works when can take the following three values: // This setting only works when can take the following three values:
/// ///
/// 1. Do not mark any files: // 1. Do not mark any files:
/// "off" // "off"
/// 2. Only mark files with errors: // 2. Only mark files with errors:
/// "errors" // "errors"
/// 3. Mark files with errors and warnings: // 3. Mark files with errors and warnings:
/// "all" // "all"
"show_diagnostics": "off" "show_diagnostics": "off"
}, },
// Settings related to preview tabs. // Settings related to preview tabs.
@@ -829,6 +841,8 @@
// A list of globs representing files that edit predictions should be disabled for. // A list of globs representing files that edit predictions should be disabled for.
// There's a sensible default list of globs already included. // There's a sensible default list of globs already included.
// Any addition to this list will be merged with the default list. // Any addition to this list will be merged with the default list.
// Globs are matched relative to the worktree root,
// except when starting with a slash (/) or equivalent in Windows.
"disabled_globs": [ "disabled_globs": [
"**/.env*", "**/.env*",
"**/*.pem", "**/*.pem",
@@ -840,11 +854,14 @@
], ],
// When to show edit predictions previews in buffer. // When to show edit predictions previews in buffer.
// This setting takes two possible values: // This setting takes two possible values:
// 1. Display inline when there are no language server completions available. // 1. Display predictions inline when there are no language server completions available.
// "mode": "eager_preview" // "mode": "eager"
// 2. Display inline when holding modifier key (alt by default). // 2. Display predictions inline only when holding a modifier key (alt by default).
// "mode": "auto" // "mode": "subtle"
"mode": "eager_preview" "mode": "eager",
// Whether edit predictions are enabled in the assistant panel.
// This setting has no effect if globally disabled.
"enabled_in_assistant": true
}, },
// Settings specific to journaling // Settings specific to journaling
"journal": { "journal": {
@@ -980,21 +997,21 @@
// Example: `echo -e "\e]2;New Title\007";` // Example: `echo -e "\e]2;New Title\007";`
"breadcrumbs": true "breadcrumbs": true
}, },
/// Scrollbar-related settings // Scrollbar-related settings
"scrollbar": { "scrollbar": {
/// When to show the scrollbar in the terminal. // When to show the scrollbar in the terminal.
/// This setting can take five values: // This setting can take five values:
/// ///
/// 1. null (default): Inherit editor settings // 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or // 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default): // follow the system's configured behavior (default):
/// "auto" // "auto"
/// 3. Match the system's configured behavior: // 3. Match the system's configured behavior:
/// "system" // "system"
/// 4. Always show the scrollbar: // 4. Always show the scrollbar:
/// "always" // "always"
/// 5. Never show the scrollbar: // 5. Never show the scrollbar:
/// "never" // "never"
"show": null "show": null
} }
// Set the terminal's font size. If this option is not included, // Set the terminal's font size. If this option is not included,
@@ -1013,7 +1030,7 @@
// "max_scroll_history_lines": 10000, // "max_scroll_history_lines": 10000,
}, },
"code_actions_on_format": {}, "code_actions_on_format": {},
/// Settings related to running tasks. // Settings related to running tasks.
"tasks": { "tasks": {
"variables": {} "variables": {}
}, },
@@ -1034,20 +1051,20 @@
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"], "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"Shell Script": [".env.*"] "Shell Script": [".env.*"]
}, },
/// By default use a recent system version of node, or install our own. // By default use a recent system version of node, or install our own.
/// You can override this to use a version of node that is not in $PATH with: // You can override this to use a version of node that is not in $PATH with:
/// { // {
/// "node": { // "node": {
/// "path": "/path/to/node" // "path": "/path/to/node"
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm) // "npm_path": "/path/to/npm" (defaults to node_path/../npm)
/// } // }
/// } // }
/// or to ensure Zed always downloads and installs an isolated version of node: // or to ensure Zed always downloads and installs an isolated version of node:
/// { // {
/// "node": { // "node": {
/// "ignore_system_version": true, // "ignore_system_version": true,
/// } // }
/// NOTE: changing this setting currently requires restarting Zed. // NOTE: changing this setting currently requires restarting Zed.
"node": {}, "node": {},
// The extensions that Zed should automatically install on startup. // The extensions that Zed should automatically install on startup.
// //
@@ -1296,6 +1313,7 @@
}, },
// Vim settings // Vim settings
"vim": { "vim": {
"default_mode": "normal",
"toggle_relative_line_numbers": false, "toggle_relative_line_numbers": false,
"use_system_clipboard": "always", "use_system_clipboard": "always",
"use_multiline_find": false, "use_multiline_find": false,

View File

@@ -35,7 +35,7 @@ use language_model::{
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest, report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role, LanguageModelRequestMessage, LanguageModelTextStream, Role,
}; };
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
@@ -1589,29 +1589,10 @@ impl Render for PromptEditor {
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)) .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.justify_center() .justify_center()
.gap_2() .gap_2()
.child(LanguageModelSelectorPopoverMenu::new( .child(
self.language_model_selector.clone(), InlineLanguageModelSelector::new(self.language_model_selector.clone())
IconButton::new("context", IconName::SettingsAlt) .render(window, cx),
.shape(IconButtonShape::Square) )
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
gpui::Corner::TopRight,
))
.map(|el| { .map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else { let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
return el; return el;

View File

@@ -19,7 +19,7 @@ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest, report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role, LanguageModelRequestMessage, Role,
}; };
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use std::{ use std::{
@@ -506,7 +506,7 @@ struct PromptEditor {
impl EventEmitter<PromptEditorEvent> for PromptEditor {} impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor { impl Render for PromptEditor {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status; let status = &self.codegen.read(cx).status;
let buttons = match status { let buttons = match status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
@@ -641,29 +641,10 @@ impl Render for PromptEditor {
.w_12() .w_12()
.justify_center() .justify_center()
.gap_2() .gap_2()
.child(LanguageModelSelectorPopoverMenu::new( .child(
self.language_model_selector.clone(), InlineLanguageModelSelector::new(self.language_model_selector.clone())
IconButton::new("context", IconName::SettingsAlt) .render(window, cx),
.shape(IconButtonShape::Square) )
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
gpui::Corner::TopRight,
))
.children( .children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status { if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string()); let error_message = SharedString::from(error.to_string());
@@ -1073,7 +1054,10 @@ pub enum CodegenEvent {
impl EventEmitter<CodegenEvent> for Codegen {} impl EventEmitter<CodegenEvent> for Codegen {}
#[cfg(not(target_os = "windows"))]
const CLEAR_INPUT: &str = "\x15"; const CLEAR_INPUT: &str = "\x15";
#[cfg(target_os = "windows")]
const CLEAR_INPUT: &str = "\x03";
const CARRIAGE_RETURN: &str = "\x0d"; const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction { struct TerminalTransaction {

View File

@@ -73,6 +73,7 @@ time_format.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true
uuid.workspace = true uuid.workspace = true
vim_mode_setting.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true

View File

@@ -8,15 +8,16 @@ use gpui::{
UnderlineStyle, WeakEntity, UnderlineStyle, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::Role; use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
use markdown::{Markdown, MarkdownStyle}; use markdown::{Markdown, MarkdownStyle};
use settings::Settings as _; use settings::Settings as _;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::prelude::*; use ui::{prelude::*, Disclosure};
use workspace::Workspace; use workspace::Workspace;
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent}; use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::tool_use::{ToolUse, ToolUseStatus};
use crate::ui::ContextPill; use crate::ui::ContextPill;
pub struct ActiveThread { pub struct ActiveThread {
@@ -28,6 +29,7 @@ pub struct ActiveThread {
messages: Vec<MessageId>, messages: Vec<MessageId>,
list_state: ListState, list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>, rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
last_error: Option<ThreadError>, last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -55,6 +57,7 @@ impl ActiveThread {
thread: thread.clone(), thread: thread.clone(),
messages: Vec::new(), messages: Vec::new(),
rendered_messages_by_id: HashMap::default(), rendered_messages_by_id: HashMap::default(),
expanded_tool_uses: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), { list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade(); let this = cx.entity().downgrade();
move |ix, _: &mut Window, cx: &mut App| { move |ix, _: &mut Window, cx: &mut App| {
@@ -251,17 +254,29 @@ impl ActiveThread {
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx); let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
self.thread.update(cx, |thread, cx| { self.thread.update(cx, |thread, cx| {
thread.insert_tool_output( thread.insert_tool_output(tool_use.id.clone(), task, cx);
tool_use.assistant_message_id, });
tool_use.id.clone(), }
task, }
cx, }
); ThreadEvent::ToolFinished { .. } => {
let all_tools_finished = self
.thread
.read(cx)
.pending_tool_uses()
.into_iter()
.all(|tool_use| tool_use.status.is_error());
if all_tools_finished {
let model_registry = LanguageModelRegistry::read_global(cx);
if let Some(model) = model_registry.active_model() {
self.thread.update(cx, |thread, cx| {
// Insert an empty user message to contain the tool results.
thread.insert_user_message("", Vec::new(), cx);
thread.send_to_model(model, RequestKind::Chat, true, cx);
}); });
} }
} }
} }
ThreadEvent::ToolFinished { .. } => {}
} }
} }
@@ -276,8 +291,17 @@ impl ActiveThread {
}; };
let context = self.thread.read(cx).context_for_message(message_id); let context = self.thread.read(cx).context_for_message(message_id);
let tool_uses = self.thread.read(cx).tool_uses_for_message(message_id);
let colors = cx.theme().colors(); let colors = cx.theme().colors();
// Don't render user messages that are just there for returning tool results.
if message.role == Role::User
&& message.text.is_empty()
&& self.thread.read(cx).message_has_tool_results(message_id)
{
return Empty.into_any();
}
let message_content = v_flex() let message_content = v_flex()
.child(div().p_2p5().text_ui(cx).child(markdown.clone())) .child(div().p_2p5().text_ui(cx).child(markdown.clone()))
.when_some(context, |parent, context| { .when_some(context, |parent, context| {
@@ -332,7 +356,22 @@ impl ActiveThread {
) )
.child(message_content), .child(message_content),
), ),
Role::Assistant => div().id(("message-container", ix)).child(message_content), Role::Assistant => div()
.id(("message-container", ix))
.child(message_content)
.map(|parent| {
if tool_uses.is_empty() {
return parent;
}
parent.child(
v_flex().children(
tool_uses
.into_iter()
.map(|tool_use| self.render_tool_use(tool_use, cx)),
),
)
}),
Role::System => div().id(("message-container", ix)).py_1().px_2().child( Role::System => div().id(("message-container", ix)).py_1().px_2().child(
v_flex() v_flex()
.bg(colors.editor_background) .bg(colors.editor_background)
@@ -343,6 +382,102 @@ impl ActiveThread {
styled_message.into_any() styled_message.into_any()
} }
fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement {
let is_open = self
.expanded_tool_uses
.get(&tool_use.id)
.copied()
.unwrap_or_default();
div().px_2p5().child(
v_flex()
.gap_1()
.rounded_lg()
.border_1()
.border_color(cx.theme().colors().border)
.child(
h_flex()
.justify_between()
.py_0p5()
.pl_1()
.pr_2()
.bg(cx.theme().colors().editor_foreground.opacity(0.02))
.when(is_open, |element| element.border_b_1().rounded_t(px(6.)))
.when(!is_open, |element| element.rounded(px(6.)))
.border_color(cx.theme().colors().border)
.child(
h_flex()
.gap_1()
.child(Disclosure::new("tool-use-disclosure", is_open).on_click(
cx.listener({
let tool_use_id = tool_use.id.clone();
move |this, _event, _window, _cx| {
let is_open = this
.expanded_tool_uses
.entry(tool_use_id.clone())
.or_insert(false);
*is_open = !*is_open;
}
}),
))
.child(Label::new(tool_use.name)),
)
.child(
Label::new(match tool_use.status {
ToolUseStatus::Pending => "Pending",
ToolUseStatus::Running => "Running",
ToolUseStatus::Finished(_) => "Finished",
ToolUseStatus::Error(_) => "Error",
})
.size(LabelSize::XSmall)
.buffer_font(cx),
),
)
.map(|parent| {
if !is_open {
return parent;
}
parent.child(
v_flex()
.child(
v_flex()
.gap_0p5()
.py_1()
.px_2p5()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(Label::new("Input:"))
.child(Label::new(
serde_json::to_string_pretty(&tool_use.input)
.unwrap_or_default(),
)),
)
.map(|parent| match tool_use.status {
ToolUseStatus::Finished(output) => parent.child(
v_flex()
.gap_0p5()
.py_1()
.px_2p5()
.child(Label::new("Result:"))
.child(Label::new(output)),
),
ToolUseStatus::Error(err) => parent.child(
v_flex()
.gap_0p5()
.py_1()
.px_2p5()
.child(Label::new("Error:"))
.child(Label::new(err)),
),
ToolUseStatus::Pending | ToolUseStatus::Running => parent,
}),
)
}),
)
}
} }
impl Render for ActiveThread { impl Render for ActiveThread {

View File

@@ -16,6 +16,7 @@ mod terminal_inline_assistant;
mod thread; mod thread;
mod thread_history; mod thread_history;
mod thread_store; mod thread_store;
mod tool_use;
mod ui; mod ui;
use std::sync::Arc; use std::sync::Arc;
@@ -38,7 +39,6 @@ actions!(
NewThread, NewThread,
NewPromptEditor, NewPromptEditor,
ToggleContextPicker, ToggleContextPicker,
ToggleModelSelector,
RemoveAllContext, RemoveAllContext,
OpenHistory, OpenHistory,
OpenConfiguration, OpenConfiguration,

View File

@@ -158,8 +158,16 @@ impl Render for AssistantConfiguration {
.child( .child(
v_flex() v_flex()
.p(DynamicSpacing::Base16.rems(cx)) .p(DynamicSpacing::Base16.rems(cx))
.gap_1() .gap_2()
.child(Headline::new("Prompt Library").size(HeadlineSize::Small)) .child(
v_flex()
.gap_0p5()
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
.child(
Label::new("Create reusable prompts and tag which ones you want sent in every LLM interaction.")
.color(Color::Muted),
),
)
.child( .child(
Button::new("open-prompt-library", "Open Prompt Library") Button::new("open-prompt-library", "Open Prompt Library")
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)

View File

@@ -1,24 +1,19 @@
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use fs::Fs; use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString}; use gpui::{Entity, FocusHandle};
use language_model::LanguageModelRegistry; use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file; use settings::update_settings_file;
use std::sync::Arc; use std::sync::Arc;
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip}; use ui::prelude::*;
use crate::ToggleModelSelector;
pub struct AssistantModelSelector { pub struct AssistantModelSelector {
selector: Entity<LanguageModelSelector>, pub selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
impl AssistantModelSelector { impl AssistantModelSelector {
pub(crate) fn new( pub(crate) fn new(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@@ -38,50 +33,14 @@ impl AssistantModelSelector {
cx, cx,
) )
}), }),
menu_handle,
focus_handle, focus_handle,
} }
} }
} }
impl Render for AssistantModelSelector { impl Render for AssistantModelSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model(); AssistantLanguageModelSelector::new(self.focus_handle.clone(), self.selector.clone())
let focus_handle = self.focus_handle.clone(); .render(window, cx)
let model_name = match active_model {
Some(model) => model.name().0,
_ => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomRight,
)
.with_handle(self.menu_handle.clone())
} }
} }

View File

@@ -14,7 +14,7 @@ use client::zed_urls;
use editor::Editor; use editor::Editor;
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter, prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
@@ -458,6 +458,12 @@ impl AssistantPanel {
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> { pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
self.context_editor.clone() self.context_editor.clone()
} }
pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
self.context_store
.update(cx, |this, cx| this.delete_local_context(path, cx))
.detach_and_log_err(cx);
}
} }
impl Focusable for AssistantPanel { impl Focusable for AssistantPanel {
@@ -590,7 +596,6 @@ impl AssistantPanel {
h_flex() h_flex()
.id("assistant-toolbar") .id("assistant-toolbar")
.px(DynamicSpacing::Base08.rems(cx))
.h(Tab::container_height(cx)) .h(Tab::container_height(cx))
.flex_none() .flex_none()
.justify_between() .justify_between()
@@ -598,72 +603,86 @@ impl AssistantPanel {
.bg(cx.theme().colors().tab_bar_background) .bg(cx.theme().colors().tab_bar_background)
.border_b_1() .border_b_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child(
div()
.id("title")
.overflow_x_scroll()
.px(DynamicSpacing::Base08.rems(cx))
.child(Label::new(title).text_ellipsis()),
)
.child( .child(
h_flex() h_flex()
.w_full() .h_full()
.gap_1() .pl_2()
.justify_between() .gap_2()
.child(Label::new(title)) .bg(cx.theme().colors().tab_bar_background)
.children(if matches!(self.active_view, ActiveView::PromptEditor) { .children(if matches!(self.active_view, ActiveView::PromptEditor) {
self.context_editor self.context_editor
.as_ref() .as_ref()
.and_then(|editor| render_remaining_tokens(editor, cx)) .and_then(|editor| render_remaining_tokens(editor, cx))
} else { } else {
None None
}), })
)
.child(
h_flex()
.h_full()
.pl_1p5()
.border_l_1()
.border_color(cx.theme().colors().border)
.gap(DynamicSpacing::Base02.rems(cx))
.child( .child(
PopoverMenu::new("assistant-toolbar-new-popover-menu") h_flex()
.trigger_with_tooltip( .h_full()
IconButton::new("new", IconName::Plus) .px(DynamicSpacing::Base08.rems(cx))
.icon_size(IconSize::Small) .border_l_1()
.style(ButtonStyle::Subtle), .border_color(cx.theme().colors().border)
Tooltip::text("New…"), .gap(DynamicSpacing::Base02.rems(cx))
) .child(
.anchor(Corner::TopRight) PopoverMenu::new("assistant-toolbar-new-popover-menu")
.with_handle(self.new_item_context_menu_handle.clone()) .trigger_with_tooltip(
.menu(move |window, cx| { IconButton::new("new", IconName::Plus)
Some(ContextMenu::build(window, cx, |menu, _window, _cx| { .icon_size(IconSize::Small)
menu.action("New Thread", NewThread.boxed_clone()) .style(ButtonStyle::Subtle),
.action("New Prompt Editor", NewPromptEditor.boxed_clone()) Tooltip::text("New…"),
}))
}),
)
.child(
IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = self.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"History",
&OpenHistory,
&focus_handle,
window,
cx,
) )
} .anchor(Corner::TopRight)
}) .with_handle(self.new_item_context_menu_handle.clone())
.on_click(move |_event, window, cx| { .menu(move |window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx); Some(ContextMenu::build(
}), window,
) cx,
.child( |menu, _window, _cx| {
IconButton::new("configure-assistant", IconName::Settings) menu.action("New Thread", NewThread.boxed_clone())
.icon_size(IconSize::Small) .action(
.style(ButtonStyle::Subtle) "New Prompt Editor",
.tooltip(Tooltip::text("Assistant Settings")) NewPromptEditor.boxed_clone(),
.on_click(move |_event, window, cx| { )
window.dispatch_action(OpenConfiguration.boxed_clone(), cx); },
}), ))
}),
)
.child(
IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = self.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"History",
&OpenHistory,
&focus_handle,
window,
cx,
)
}
})
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
}),
)
.child(
IconButton::new("configure-assistant", IconName::Settings)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(Tooltip::text("Assistant Settings"))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
}),
),
), ),
) )
} }
@@ -705,12 +724,11 @@ impl AssistantPanel {
) -> impl IntoElement { ) -> impl IntoElement {
let recent_history = self let recent_history = self
.history_store .history_store
.update(cx, |this, cx| this.recent_entries(3, cx)); .update(cx, |this, cx| this.recent_entries(6, cx));
let create_welcome_heading = || { let create_welcome_heading = || {
h_flex() h_flex()
.w_full() .w_full()
.justify_center()
.child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small)) .child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
}; };
@@ -718,36 +736,27 @@ impl AssistantPanel {
let no_error = configuration_error.is_none(); let no_error = configuration_error.is_none();
v_flex() v_flex()
.gap_2() .p_1p5()
.child( .size_full()
v_flex().w_full().child( .justify_end()
svg() .gap_1()
.path("icons/logo_96.svg")
.text_color(cx.theme().colors().text)
.w(px(40.))
.h(px(40.))
.mx_auto()
.mb_4(),
),
)
.map(|parent| { .map(|parent| {
match configuration_error { match configuration_error {
Some(ConfigurationError::ProviderNotAuthenticated) Some(ConfigurationError::ProviderNotAuthenticated)
| Some(ConfigurationError::NoProvider) => { | Some(ConfigurationError::NoProvider) => {
parent.child( parent.child(
v_flex() v_flex()
.px_1p5()
.gap_0p5() .gap_0p5()
.child(create_welcome_heading()) .child(create_welcome_heading())
.child( .child(
h_flex().mb_2().w_full().justify_center().child( Label::new(
Label::new( "To start using the assistant, configure at least one LLM provider.",
"To start using the assistant, configure at least one LLM provider.", )
) .color(Color::Muted),
.color(Color::Muted),
),
) )
.child( .child(
h_flex().w_full().justify_center().child( h_flex().mt_1().w_full().child(
Button::new("open-configuration", "Configure a Provider") Button::new("open-configuration", "Configure a Provider")
.size(ButtonSize::Compact) .size(ButtonSize::Compact)
.icon(Some(IconName::Sliders)) .icon(Some(IconName::Sliders))
@@ -761,7 +770,7 @@ impl AssistantPanel {
) )
} }
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
.child(v_flex().gap_0p5().child(create_welcome_heading()).children( .child(v_flex().px_1p5().gap_0p5().child(create_welcome_heading()).children(
provider.render_accept_terms( provider.render_accept_terms(
LanguageModelProviderTosView::ThreadEmptyState, LanguageModelProviderTosView::ThreadEmptyState,
cx, cx,
@@ -772,21 +781,40 @@ impl AssistantPanel {
}) })
.when(recent_history.is_empty() && no_error, |parent| { .when(recent_history.is_empty() && no_error, |parent| {
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child( parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child(
h_flex().w_full().justify_center().child( Label::new("Start typing to chat with your codebase").color(Color::Muted),
Label::new("Start typing to chat with your codebase").color(Color::Muted),
),
)) ))
}) })
.when(!recent_history.is_empty(), |parent| { .when(!recent_history.is_empty(), |parent| {
parent parent
.child( .child(
h_flex().w_full().justify_center().child( h_flex()
Label::new("Recent Threads:") .pl_1p5()
.size(LabelSize::Small) .pb_1()
.color(Color::Muted), .w_full()
), .justify_between()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(
Label::new("Past Interactions")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Button::new("view-history", "View All")
.style(ButtonStyle::Subtle)
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
}),
),
) )
.child(v_flex().mx_auto().w_4_5().gap_2().children( .child(v_flex().gap_1().children(
recent_history.into_iter().map(|entry| { recent_history.into_iter().map(|entry| {
// TODO: Add keyboard navigation. // TODO: Add keyboard navigation.
match entry { match entry {
@@ -801,22 +829,6 @@ impl AssistantPanel {
} }
}), }),
)) ))
.child(
h_flex().w_full().justify_center().child(
Button::new("view-all-past-threads", "View All Past Threads")
.style(ButtonStyle::Subtle)
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
}),
),
)
}) })
} }

View File

@@ -6,7 +6,7 @@ use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::terminal_codegen::TerminalCodegen; use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist}; use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector}; use crate::{RemoveAllContext, ToggleContextPicker};
use client::ErrorExt; use client::ErrorExt;
use collections::VecDeque; use collections::VecDeque;
use editor::{ use editor::{
@@ -20,7 +20,6 @@ use gpui::{
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window, EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
}; };
use language_model::{LanguageModel, LanguageModelRegistry}; use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
use parking_lot::Mutex; use parking_lot::Mutex;
use settings::Settings; use settings::Settings;
use std::cmp; use std::cmp;
@@ -40,7 +39,6 @@ pub struct PromptEditor<T> {
context_strip: Entity<ContextStrip>, context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool, edited_since_done: bool,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>, prompt_history_ix: Option<usize>,
@@ -104,7 +102,12 @@ impl<T: 'static> Render for PromptEditor<T> {
.items_start() .items_start()
.cursor(CursorStyle::Arrow) .cursor(CursorStyle::Arrow)
.on_action(cx.listener(Self::toggle_context_picker)) .on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::toggle_model_selector)) .on_action(cx.listener(|this, action, window, cx| {
let selector = this.model_selector.read(cx).selector.clone();
selector.update(cx, |selector, cx| {
selector.toggle_model_selector(action, window, cx);
})
}))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_up))
@@ -347,15 +350,6 @@ impl<T: 'static> PromptEditor<T> {
self.context_picker_menu_handle.toggle(window, cx); self.context_picker_menu_handle.toggle(window, cx);
} }
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx);
}
pub fn remove_all_context( pub fn remove_all_context(
&mut self, &mut self,
_: &RemoveAllContext, _: &RemoveAllContext,
@@ -864,7 +858,6 @@ impl PromptEditor<BufferCodegen> {
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
@@ -888,15 +881,8 @@ impl PromptEditor<BufferCodegen> {
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
}), }),
model_selector_menu_handle,
edited_since_done: false, edited_since_done: false,
prompt_history, prompt_history,
prompt_history_ix: None, prompt_history_ix: None,
@@ -1020,7 +1006,6 @@ impl PromptEditor<TerminalCodegen> {
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
@@ -1044,15 +1029,8 @@ impl PromptEditor<TerminalCodegen> {
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
}), }),
model_selector_menu_handle,
edited_since_done: false, edited_since_done: false,
prompt_history, prompt_history,
prompt_history_ix: None, prompt_history_ix: None,

View File

@@ -7,16 +7,17 @@ use gpui::{
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
TextStyle, WeakEntity, TextStyle, WeakEntity,
}; };
use language_model::{LanguageModelRegistry, LanguageModelRequestTool}; use language_model::LanguageModelRegistry;
use language_model_selector::LanguageModelSelector;
use rope::Point; use rope::Point;
use settings::Settings; use settings::Settings;
use std::time::Duration; use std::time::Duration;
use text::Bias; use text::Bias;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip, prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch,
TintColor, Tooltip,
}; };
use vim_mode_setting::VimModeSetting;
use workspace::Workspace; use workspace::Workspace;
use crate::assistant_model_selector::AssistantModelSelector; use crate::assistant_model_selector::AssistantModelSelector;
@@ -25,7 +26,7 @@ use crate::context_store::{refresh_context_store_text, ContextStore};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind}; use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::thread::{RequestKind, Thread}; use crate::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector}; use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker};
pub struct MessageEditor { pub struct MessageEditor {
thread: Entity<Thread>, thread: Entity<Thread>,
@@ -36,7 +37,6 @@ pub struct MessageEditor {
inline_context_picker: Entity<ContextPicker>, inline_context_picker: Entity<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool, use_tools: bool,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -53,7 +53,6 @@ impl MessageEditor {
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone())); let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default(); let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new(|cx| { let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx); let mut editor = Editor::auto_height(10, window, cx);
@@ -106,30 +105,13 @@ impl MessageEditor {
context_picker_menu_handle, context_picker_menu_handle,
inline_context_picker, inline_context_picker,
inline_context_picker_menu_handle, inline_context_picker_menu_handle,
model_selector: cx.new(|cx| { model_selector: cx
AssistantModelSelector::new( .new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)),
fs,
model_selector_menu_handle.clone(),
editor.focus_handle(cx),
window,
cx,
)
}),
model_selector_menu_handle,
use_tools: false, use_tools: false,
_subscriptions: subscriptions, _subscriptions: subscriptions,
} }
} }
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx)
}
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) { fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
self.use_tools = !self.use_tools; self.use_tools = !self.use_tools;
cx.notify(); cx.notify();
@@ -205,22 +187,7 @@ impl MessageEditor {
.update(&mut cx, |thread, cx| { .update(&mut cx, |thread, cx| {
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>(); let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
thread.insert_user_message(user_message, context, cx); thread.insert_user_message(user_message, context, cx);
let mut request = thread.to_completion_request(request_kind, cx); thread.send_to_model(model, request_kind, use_tools, cx);
if use_tools {
request.tools = thread
.tools()
.tools(cx)
.into_iter()
.map(|tool| LanguageModelRequestTool {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
})
.collect();
}
thread.stream_completion(request, model, cx)
}) })
.ok(); .ok();
}) })
@@ -309,7 +276,6 @@ impl Render for MessageEditor {
let inline_context_picker = self.inline_context_picker.clone(); let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background; let bg_color = cx.theme().colors().editor_background;
let is_streaming_completion = self.thread.read(cx).is_streaming(); let is_streaming_completion = self.thread.read(cx).is_streaming();
let button_width = px(64.);
let is_model_selected = self.is_model_selected(cx); let is_model_selected = self.is_model_selected(cx);
let is_editor_empty = self.is_editor_empty(cx); let is_editor_empty = self.is_editor_empty(cx);
let submit_label_color = if is_editor_empty { let submit_label_color = if is_editor_empty {
@@ -318,10 +284,25 @@ impl Render for MessageEditor {
Color::Default Color::Default
}; };
let vim_mode_enabled = VimModeSetting::get_global(cx).0;
let platform = PlatformStyle::platform();
let linux = platform == PlatformStyle::Linux;
let windows = platform == PlatformStyle::Windows;
let button_width = if linux || windows || vim_mode_enabled {
px(92.)
} else {
px(64.)
};
v_flex() v_flex()
.key_context("MessageEditor") .key_context("MessageEditor")
.on_action(cx.listener(Self::chat)) .on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::toggle_model_selector)) .on_action(cx.listener(|this, action, window, cx| {
let selector = this.model_selector.read(cx).selector.clone();
selector.update(cx, |this, cx| {
this.toggle_model_selector(action, window, cx);
})
}))
.on_action(cx.listener(Self::toggle_context_picker)) .on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::remove_all_context)) .on_action(cx.listener(Self::remove_all_context))
.on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_up))
@@ -333,7 +314,7 @@ impl Render for MessageEditor {
.child(self.context_strip.clone()) .child(self.context_strip.clone())
.child( .child(
v_flex() v_flex()
.gap_4() .gap_5()
.child({ .child({
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {

View File

@@ -155,7 +155,10 @@ pub enum CodegenEvent {
Finished, Finished,
} }
#[cfg(not(target_os = "windows"))]
pub const CLEAR_INPUT: &str = "\x15"; pub const CLEAR_INPUT: &str = "\x15";
#[cfg(target_os = "windows")]
pub const CLEAR_INPUT: &str = "\x03";
const CARRIAGE_RETURN: &str = "\x0d"; const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction { struct TerminalTransaction {

View File

@@ -4,14 +4,12 @@ use anyhow::Result;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use futures::future::Shared; use futures::StreamExt as _;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{App, Context, EventEmitter, SharedString, Task}; use gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{ use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId,
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason,
Role, StopReason,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::{post_inc, TryFutureExt as _}; use util::{post_inc, TryFutureExt as _};
@@ -19,10 +17,13 @@ use uuid::Uuid;
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot}; use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
use crate::thread_store::SavedThread; use crate::thread_store::SavedThread;
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum RequestKind { pub enum RequestKind {
Chat, Chat,
/// Used when summarizing a thread.
Summarize,
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
@@ -41,7 +42,7 @@ impl std::fmt::Display for ThreadId {
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct MessageId(usize); pub struct MessageId(pub(crate) usize);
impl MessageId { impl MessageId {
fn post_inc(&mut self) -> Self { fn post_inc(&mut self) -> Self {
@@ -70,9 +71,7 @@ pub struct Thread {
completion_count: usize, completion_count: usize,
pending_completions: Vec<PendingCompletion>, pending_completions: Vec<PendingCompletion>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
tool_uses_by_message: HashMap<MessageId, Vec<LanguageModelToolUse>>, tool_use: ToolUseState,
tool_results_by_message: HashMap<MessageId, Vec<LanguageModelToolResult>>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
} }
impl Thread { impl Thread {
@@ -89,9 +88,7 @@ impl Thread {
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
tools, tools,
tool_uses_by_message: HashMap::default(), tool_use: ToolUseState::default(),
tool_results_by_message: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
} }
} }
@@ -123,9 +120,7 @@ impl Thread {
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
tools, tools,
tool_uses_by_message: HashMap::default(), tool_use: ToolUseState::default(),
tool_results_by_message: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
} }
} }
@@ -187,7 +182,15 @@ impl Thread {
} }
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> { pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect() self.tool_use.pending_tool_uses()
}
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
self.tool_use.tool_uses_for_message(id)
}
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
self.tool_use.message_has_tool_results(message_id)
} }
pub fn insert_user_message( pub fn insert_user_message(
@@ -241,9 +244,34 @@ impl Thread {
text text
} }
pub fn send_to_model(
&mut self,
model: Arc<dyn LanguageModel>,
request_kind: RequestKind,
use_tools: bool,
cx: &mut Context<Self>,
) {
let mut request = self.to_completion_request(request_kind, cx);
if use_tools {
request.tools = self
.tools()
.tools(cx)
.into_iter()
.map(|tool| LanguageModelRequestTool {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
})
.collect();
}
self.stream_completion(request, model, cx);
}
pub fn to_completion_request( pub fn to_completion_request(
&self, &self,
_request_kind: RequestKind, request_kind: RequestKind,
_cx: &App, _cx: &App,
) -> LanguageModelRequest { ) -> LanguageModelRequest {
let mut request = LanguageModelRequest { let mut request = LanguageModelRequest {
@@ -265,12 +293,13 @@ impl Thread {
content: Vec::new(), content: Vec::new(),
cache: false, cache: false,
}; };
match request_kind {
if let Some(tool_results) = self.tool_results_by_message.get(&message.id) { RequestKind::Chat => {
for tool_result in tool_results { self.tool_use
request_message .attach_tool_results(message.id, &mut request_message);
.content }
.push(MessageContent::ToolResult(tool_result.clone())); RequestKind::Summarize => {
// We don't care about tool use during summarization.
} }
} }
@@ -280,11 +309,13 @@ impl Thread {
.push(MessageContent::Text(message.text.clone())); .push(MessageContent::Text(message.text.clone()));
} }
if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) { match request_kind {
for tool_use in tool_uses { RequestKind::Chat => {
request_message self.tool_use
.content .attach_tool_uses(message.id, &mut request_message);
.push(MessageContent::ToolUse(tool_use.clone())); }
RequestKind::Summarize => {
// We don't care about tool use during summarization.
} }
} }
@@ -360,21 +391,8 @@ impl Thread {
.rfind(|message| message.role == Role::Assistant) .rfind(|message| message.role == Role::Assistant)
{ {
thread thread
.tool_uses_by_message .tool_use
.entry(last_assistant_message.id) .request_tool_use(last_assistant_message.id, tool_use);
.or_default()
.push(tool_use.clone());
thread.pending_tool_uses_by_id.insert(
tool_use.id.clone(),
PendingToolUse {
assistant_message_id: last_assistant_message.id,
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
status: PendingToolUseStatus::Idle,
},
);
} }
} }
} }
@@ -451,7 +469,7 @@ impl Thread {
return; return;
} }
let mut request = self.to_completion_request(RequestKind::Chat, cx); let mut request = self.to_completion_request(RequestKind::Summarize, cx);
request.messages.push(LanguageModelRequestMessage { request.messages.push(LanguageModelRequestMessage {
role: Role::User, role: Role::User,
content: vec![ content: vec![
@@ -494,7 +512,6 @@ impl Thread {
pub fn insert_tool_output( pub fn insert_tool_output(
&mut self, &mut self,
assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>, output: Task<Result<String>>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
@@ -505,50 +522,18 @@ impl Thread {
let output = output.await; let output = output.await;
thread thread
.update(&mut cx, |thread, cx| { .update(&mut cx, |thread, cx| {
// The tool use was requested by an Assistant message, thread
// so we want to attach the tool results to the next .tool_use
// user message. .insert_tool_output(tool_use_id.clone(), output);
let next_user_message = MessageId(assistant_message_id.0 + 1);
let tool_results = thread cx.emit(ThreadEvent::ToolFinished { tool_use_id });
.tool_results_by_message
.entry(next_user_message)
.or_default();
match output {
Ok(output) => {
tool_results.push(LanguageModelToolResult {
tool_use_id: tool_use_id.clone(),
content: output,
is_error: false,
});
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
}
Err(err) => {
tool_results.push(LanguageModelToolResult {
tool_use_id: tool_use_id.clone(),
content: err.to_string(),
is_error: true,
});
if let Some(tool_use) =
thread.pending_tool_uses_by_id.get_mut(&tool_use_id)
{
tool_use.status = PendingToolUseStatus::Error(err.to_string());
}
}
}
}) })
.ok(); .ok();
} }
}); });
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) { self.tool_use
tool_use.status = PendingToolUseStatus::Running { .run_pending_tool(tool_use_id, insert_output_task);
_task: insert_output_task.shared(),
};
}
} }
/// Cancels the last pending completion, if there are any pending. /// Cancels the last pending completion, if there are any pending.
@@ -590,26 +575,3 @@ struct PendingCompletion {
id: usize, id: usize,
_task: Task<()>, _task: Task<()>,
} }
#[derive(Debug, Clone)]
pub struct PendingToolUse {
pub id: LanguageModelToolUseId,
/// The ID of the Assistant message in which the tool use was requested.
pub assistant_message_id: MessageId,
pub name: String,
pub input: serde_json::Value,
pub status: PendingToolUseStatus,
}
#[derive(Debug, Clone)]
pub enum PendingToolUseStatus {
Idle,
Running { _task: Shared<Task<()>> },
Error(#[allow(unused)] String),
}
impl PendingToolUseStatus {
pub fn is_idle(&self) -> bool {
matches!(self, PendingToolUseStatus::Idle)
}
}

View File

@@ -134,7 +134,13 @@ impl ThreadHistory {
}) })
.ok(); .ok();
} }
HistoryEntry::Context(_context) => {} HistoryEntry::Context(context) => {
self.assistant_panel
.update(cx, |this, cx| {
this.delete_context(context.path.clone(), cx);
})
.ok();
}
} }
cx.notify(); cx.notify();
@@ -248,18 +254,28 @@ impl RenderOnce for PastThread {
); );
ListItem::new(SharedString::from(self.thread.id.to_string())) ListItem::new(SharedString::from(self.thread.id.to_string()))
.outlined() .rounded()
.toggle_state(self.selected) .toggle_state(self.selected)
.start_slot(
Icon::new(IconName::MessageCircle)
.size(IconSize::Small)
.color(Color::Muted),
)
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()) .start_slot(
div()
.max_w_4_5()
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
)
.end_slot( .end_slot(
h_flex() h_flex()
.gap_1p5() .gap_1p5()
.child(
Label::new("Thread")
.color(Color::Muted)
.size(LabelSize::XSmall),
)
.child(
div()
.size(px(3.))
.rounded_full()
.bg(cx.theme().colors().text_disabled),
)
.child( .child(
Label::new(thread_timestamp) Label::new(thread_timestamp)
.color(Color::Muted) .color(Color::Muted)
@@ -334,21 +350,50 @@ impl RenderOnce for PastContext {
ListItem::new(SharedString::from( ListItem::new(SharedString::from(
self.context.path.to_string_lossy().to_string(), self.context.path.to_string_lossy().to_string(),
)) ))
.outlined() .rounded()
.toggle_state(self.selected) .toggle_state(self.selected)
.start_slot(
Icon::new(IconName::Code)
.size(IconSize::Small)
.color(Color::Muted),
)
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()) .start_slot(
div()
.max_w_4_5()
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
)
.end_slot( .end_slot(
h_flex().gap_1p5().child( h_flex()
Label::new(context_timestamp) .gap_1p5()
.color(Color::Muted) .child(
.size(LabelSize::XSmall), Label::new("Prompt Editor")
), .color(Color::Muted)
.size(LabelSize::XSmall),
)
.child(
div()
.size(px(3.))
.rounded_full()
.bg(cx.theme().colors().text_disabled),
)
.child(
Label::new(context_timestamp)
.color(Color::Muted)
.size(LabelSize::XSmall),
)
.child(
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.tooltip(Tooltip::text("Delete Prompt Editor"))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let path = self.context.path.clone();
move |_event, _window, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_context(path.clone(), cx);
})
.ok();
}
}),
),
) )
.on_click({ .on_click({
let assistant_panel = self.assistant_panel.clone(); let assistant_panel = self.assistant_panel.clone();

View File

@@ -0,0 +1,221 @@
use std::sync::Arc;
use anyhow::Result;
use collections::HashMap;
use futures::future::Shared;
use futures::FutureExt as _;
use gpui::{SharedString, Task};
use language_model::{
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent,
};
use crate::thread::MessageId;
#[derive(Debug)]
pub struct ToolUse {
pub id: LanguageModelToolUseId,
pub name: SharedString,
pub status: ToolUseStatus,
pub input: serde_json::Value,
}
#[derive(Debug, Clone)]
pub enum ToolUseStatus {
Pending,
Running,
Finished(SharedString),
Error(SharedString),
}
#[derive(Default)]
pub struct ToolUseState {
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
tool_results: HashMap<LanguageModelToolUseId, LanguageModelToolResult>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
}
impl ToolUseState {
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect()
}
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
let Some(tool_uses_for_message) = &self.tool_uses_by_assistant_message.get(&id) else {
return Vec::new();
};
let mut tool_uses = Vec::new();
for tool_use in tool_uses_for_message.iter() {
let tool_result = self.tool_results.get(&tool_use.id);
let status = (|| {
if let Some(tool_result) = tool_result {
return if tool_result.is_error {
ToolUseStatus::Error(tool_result.content.clone().into())
} else {
ToolUseStatus::Finished(tool_result.content.clone().into())
};
}
if let Some(pending_tool_use) = self.pending_tool_uses_by_id.get(&tool_use.id) {
return match pending_tool_use.status {
PendingToolUseStatus::Idle => ToolUseStatus::Pending,
PendingToolUseStatus::Running { .. } => ToolUseStatus::Running,
PendingToolUseStatus::Error(ref err) => {
ToolUseStatus::Error(err.clone().into())
}
};
}
ToolUseStatus::Pending
})();
tool_uses.push(ToolUse {
id: tool_use.id.clone(),
name: tool_use.name.clone().into(),
input: tool_use.input.clone(),
status,
})
}
tool_uses
}
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
self.tool_uses_by_user_message
.get(&message_id)
.map_or(false, |results| !results.is_empty())
}
pub fn request_tool_use(
&mut self,
assistant_message_id: MessageId,
tool_use: LanguageModelToolUse,
) {
self.tool_uses_by_assistant_message
.entry(assistant_message_id)
.or_default()
.push(tool_use.clone());
// The tool use is being requested by the Assistant, so we want to
// attach the tool results to the next user message.
let next_user_message_id = MessageId(assistant_message_id.0 + 1);
self.tool_uses_by_user_message
.entry(next_user_message_id)
.or_default()
.push(tool_use.id.clone());
self.pending_tool_uses_by_id.insert(
tool_use.id.clone(),
PendingToolUse {
assistant_message_id,
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
status: PendingToolUseStatus::Idle,
},
);
}
pub fn run_pending_tool(&mut self, tool_use_id: LanguageModelToolUseId, task: Task<()>) {
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Running {
_task: task.shared(),
};
}
}
pub fn insert_tool_output(
&mut self,
tool_use_id: LanguageModelToolUseId,
output: Result<String>,
) {
match output {
Ok(output) => {
self.tool_results.insert(
tool_use_id.clone(),
LanguageModelToolResult {
tool_use_id: tool_use_id.clone(),
content: output.into(),
is_error: false,
},
);
self.pending_tool_uses_by_id.remove(&tool_use_id);
}
Err(err) => {
self.tool_results.insert(
tool_use_id.clone(),
LanguageModelToolResult {
tool_use_id: tool_use_id.clone(),
content: err.to_string().into(),
is_error: true,
},
);
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Error(err.to_string().into());
}
}
}
}
pub fn attach_tool_uses(
&self,
message_id: MessageId,
request_message: &mut LanguageModelRequestMessage,
) {
if let Some(tool_uses) = self.tool_uses_by_assistant_message.get(&message_id) {
for tool_use in tool_uses {
request_message
.content
.push(MessageContent::ToolUse(tool_use.clone()));
}
}
}
pub fn attach_tool_results(
&self,
message_id: MessageId,
request_message: &mut LanguageModelRequestMessage,
) {
if let Some(tool_uses) = self.tool_uses_by_user_message.get(&message_id) {
for tool_use_id in tool_uses {
if let Some(tool_result) = self.tool_results.get(tool_use_id) {
request_message
.content
.push(MessageContent::ToolResult(tool_result.clone()));
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct PendingToolUse {
pub id: LanguageModelToolUseId,
/// The ID of the Assistant message in which the tool use was requested.
pub assistant_message_id: MessageId,
pub name: Arc<str>,
pub input: serde_json::Value,
pub status: PendingToolUseStatus,
}
#[derive(Debug, Clone)]
pub enum PendingToolUseStatus {
Idle,
Running { _task: Shared<Task<()>> },
Error(#[allow(unused)] Arc<str>),
}
impl PendingToolUseStatus {
pub fn is_idle(&self) -> bool {
matches!(self, PendingToolUseStatus::Idle)
}
pub fn is_error(&self) -> bool {
matches!(self, PendingToolUseStatus::Error(_))
}
}

View File

@@ -29,19 +29,22 @@ use gpui::{
WeakEntity, WeakEntity,
}; };
use indexed_docs::IndexedDocsStore; use indexed_docs::IndexedDocsStore;
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset}; use language::{
language_settings::{all_language_settings, SoftWrap},
BufferSnapshot, LspAdapterDelegate, ToOffset,
};
use language_model::{ use language_model::{
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry, LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
Role, Role,
}; };
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use picker::Picker; use picker::Picker;
use project::lsp_store::LocalLspAdapterDelegate; use project::lsp_store::LocalLspAdapterDelegate;
use project::{Project, Worktree}; use project::{Project, Worktree};
use rope::Point; use rope::Point;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings, SettingsStore};
use std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration}; use std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration};
use text::SelectionGoal; use text::SelectionGoal;
use ui::{ use ui::{
@@ -77,7 +80,6 @@ actions!(
InsertIntoEditor, InsertIntoEditor,
QuoteSelection, QuoteSelection,
Split, Split,
ToggleModelSelector,
] ]
); );
@@ -194,7 +196,6 @@ pub struct ContextEditor {
// context editor, we keep a reference here. // context editor, we keep a reference here.
dragged_file_worktrees: Vec<Entity<Worktree>>, dragged_file_worktrees: Vec<Entity<Worktree>>,
language_model_selector: Entity<LanguageModelSelector>, language_model_selector: Entity<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
} }
pub const DEFAULT_TAB_TITLE: &str = "New Chat"; pub const DEFAULT_TAB_TITLE: &str = "New Chat";
@@ -230,6 +231,13 @@ impl ContextEditor {
editor.set_completion_provider(Some(Box::new(completion_provider))); editor.set_completion_provider(Some(Box::new(completion_provider)));
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never); editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
editor.set_collaboration_hub(Box::new(project.clone())); editor.set_collaboration_hub(Box::new(project.clone()));
let show_edit_predictions = all_language_settings(None, cx)
.edit_predictions
.enabled_in_assistant;
editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
editor editor
}); });
@@ -238,6 +246,7 @@ impl ContextEditor {
cx.subscribe_in(&context, window, Self::handle_context_event), cx.subscribe_in(&context, window, Self::handle_context_event),
cx.subscribe_in(&editor, window, Self::handle_editor_event), cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(&editor, window, Self::handle_editor_search_event), cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
]; ];
let fs_clone = fs.clone(); let fs_clone = fs.clone();
@@ -255,7 +264,6 @@ impl ContextEditor {
) )
}); });
let language_model_selector_menu_handle = PopoverMenuHandle::default();
let sections = context.read(cx).slash_command_output_sections().to_vec(); let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>(); let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands().clone(); let slash_commands = context.read(cx).slash_commands().clone();
@@ -281,7 +289,6 @@ impl ContextEditor {
slash_menu_handle: Default::default(), slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(), dragged_file_worktrees: Vec::new(),
language_model_selector, language_model_selector,
language_model_selector_menu_handle,
}; };
this.update_message_headers(cx); this.update_message_headers(cx);
this.update_image_blocks(cx); this.update_image_blocks(cx);
@@ -290,6 +297,16 @@ impl ContextEditor {
this this
} }
fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.editor.update(cx, |editor, cx| {
let show_edit_predictions = all_language_settings(None, cx)
.edit_predictions
.enabled_in_assistant;
editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
});
}
pub fn context(&self) -> &Entity<AssistantContext> { pub fn context(&self) -> &Entity<AssistantContext> {
&self.context &self.context
} }
@@ -2024,15 +2041,6 @@ impl ContextEditor {
}); });
} }
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.language_model_selector_menu_handle.toggle(window, cx);
}
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) { fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| { self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx) context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
@@ -2380,46 +2388,6 @@ impl ContextEditor {
) )
} }
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.editor().focus_handle(cx).clone();
let model_name = match active_model {
Some(model) => model.name().0,
None => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
gpui::Corner::BottomLeft,
)
.with_handle(self.language_model_selector_menu_handle.clone())
}
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> { fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?; let last_error = self.last_error.as_ref()?;
@@ -2864,6 +2832,7 @@ impl Render for ContextEditor {
None None
}; };
let language_model_selector = self.language_model_selector.clone();
v_flex() v_flex()
.key_context("ContextEditor") .key_context("ContextEditor")
.capture_action(cx.listener(ContextEditor::cancel)) .capture_action(cx.listener(ContextEditor::cancel))
@@ -2876,7 +2845,11 @@ impl Render for ContextEditor {
.on_action(cx.listener(ContextEditor::edit)) .on_action(cx.listener(ContextEditor::edit))
.on_action(cx.listener(ContextEditor::assist)) .on_action(cx.listener(ContextEditor::assist))
.on_action(cx.listener(ContextEditor::split)) .on_action(cx.listener(ContextEditor::split))
.on_action(cx.listener(ContextEditor::toggle_model_selector)) .on_action(move |action, window, cx| {
language_model_selector.update(cx, |this, cx| {
this.toggle_model_selector(action, window, cx);
})
})
.size_full() .size_full()
.children(self.render_notice(cx)) .children(self.render_notice(cx))
.child( .child(
@@ -2914,11 +2887,14 @@ impl Render for ContextEditor {
.gap_1() .gap_1()
.child(self.render_inject_context_menu(cx)) .child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical()) .child(ui::Divider::vertical())
.child( .child(div().pl_0p5().child({
div() let focus_handle = self.editor().focus_handle(cx).clone();
.pl_0p5() AssistantLanguageModelSelector::new(
.child(self.render_language_model_selector(cx)), focus_handle,
), self.language_model_selector.clone(),
)
.render(window, cx)
})),
) )
.child( .child(
h_flex() h_flex()

View File

@@ -9,7 +9,7 @@ use clock::ReplicaId;
use collections::HashMap; use collections::HashMap;
use context_server::manager::ContextServerManager; use context_server::manager::ContextServerManager;
use context_server::ContextServerFactoryRegistry; use context_server::ContextServerFactoryRegistry;
use fs::Fs; use fs::{Fs, RemoveOptions};
use futures::StreamExt; use futures::StreamExt;
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
@@ -475,6 +475,38 @@ impl ContextStore {
}) })
} }
pub fn delete_local_context(
&mut self,
path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {
fs.remove_file(
&path,
RemoveOptions {
recursive: false,
ignore_if_not_exists: true,
},
)
.await?;
this.update(&mut cx, |this, cx| {
this.contexts.retain(|context| {
context
.upgrade()
.and_then(|context| context.read(cx).path())
!= Some(&path)
});
this.contexts_metadata
.retain(|context| context.path != path);
})?;
Ok(())
})
}
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> { fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| { self.contexts.iter().find_map(|context| {
let context = context.upgrade()?; let context = context.upgrade()?;

View File

@@ -21,11 +21,11 @@ impl SlashCommand for DefaultSlashCommand {
} }
fn description(&self) -> String { fn description(&self) -> String {
"insert default prompt".into() "Insert default prompt".into()
} }
fn menu_text(&self) -> String { fn menu_text(&self) -> String {
"Insert Default Prompt".into() self.description()
} }
fn requires_argument(&self) -> bool { fn requires_argument(&self) -> bool {

View File

@@ -109,7 +109,7 @@ impl sum_tree::Summary for DiffHunkSummary {
} }
} }
impl<'a> sum_tree::SeekTarget<'a, DiffHunkSummary, DiffHunkSummary> for Anchor { impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering { fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
if self if self
.cmp(&cursor_location.buffer_range.start, buffer) .cmp(&cursor_location.buffer_range.start, buffer)

View File

@@ -48,7 +48,7 @@ pub struct ChannelPathsInsertGuard<'a> {
channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>, channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
} }
impl<'a> ChannelPathsInsertGuard<'a> { impl ChannelPathsInsertGuard<'_> {
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool { pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
let mut ret = false; let mut ret = false;
let parent_path = channel_proto let parent_path = channel_proto
@@ -86,7 +86,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
} }
} }
impl<'a> Drop for ChannelPathsInsertGuard<'a> { impl Drop for ChannelPathsInsertGuard<'_> {
fn drop(&mut self) { fn drop(&mut self) {
self.channels_ordered.sort_by(|a, b| { self.channels_ordered.sort_by(|a, b| {
let a = channel_path_sorting_key(*a, self.channels_by_id); let a = channel_path_sorting_key(*a, self.channels_by_id);

View File

@@ -33,10 +33,13 @@ util.workspace = true
tempfile.workspace = true tempfile.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies] [target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
exec.workspace = true exec.workspace = true
fork.workspace = true fork.workspace = true
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true core-foundation.workspace = true
core-services = "0.2" core-services = "0.2"
plist = "1.3" plist = "1.3"
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true

View File

@@ -521,30 +521,108 @@ mod flatpak {
} }
} }
// todo("windows")
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod windows { mod windows {
use anyhow::Context;
use release_channel::app_identifier;
use windows::{
core::HSTRING,
Win32::{
Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, GENERIC_WRITE},
Storage::FileSystem::{
CreateFileW, WriteFile, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, OPEN_EXISTING,
},
System::Threading::CreateMutexW,
},
};
use crate::{Detect, InstalledApp}; use crate::{Detect, InstalledApp};
use std::io; use std::io;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process::ExitStatus; use std::process::ExitStatus;
struct App; fn check_single_instance() -> bool {
let mutex = unsafe {
CreateMutexW(
None,
false,
&HSTRING::from(format!("{}-Instance-Mutex", app_identifier())),
)
.expect("Unable to create instance sync event")
};
let last_err = unsafe { GetLastError() };
let _ = unsafe { CloseHandle(mutex) };
last_err != ERROR_ALREADY_EXISTS
}
struct App(PathBuf);
impl InstalledApp for App { impl InstalledApp for App {
fn zed_version_string(&self) -> String { fn zed_version_string(&self) -> String {
unimplemented!() format!(
"Zed {}{}{} {}",
if *release_channel::RELEASE_CHANNEL_NAME == "stable" {
"".to_string()
} else {
format!("{} ", *release_channel::RELEASE_CHANNEL_NAME)
},
option_env!("RELEASE_VERSION").unwrap_or_default(),
match option_env!("ZED_COMMIT_SHA") {
Some(commit_sha) => format!(" {commit_sha} "),
None => "".to_string(),
},
self.0.display(),
)
} }
fn launch(&self, _ipc_url: String) -> anyhow::Result<()> {
unimplemented!() fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
if check_single_instance() {
std::process::Command::new(self.0.clone())
.arg(ipc_url)
.spawn()?;
} else {
unsafe {
let pipe = CreateFileW(
&HSTRING::from(format!("\\\\.\\pipe\\{}-Named-Pipe", app_identifier())),
GENERIC_WRITE.0,
FILE_SHARE_MODE::default(),
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES::default(),
None,
)?;
let message = ipc_url.as_bytes();
let mut bytes_written = 0;
WriteFile(pipe, Some(message), Some(&mut bytes_written), None)?;
CloseHandle(pipe)?;
}
}
Ok(())
} }
fn run_foreground(&self, _ipc_url: String) -> io::Result<ExitStatus> {
unimplemented!() fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
std::process::Command::new(self.0.clone())
.arg(ipc_url)
.arg("--foreground")
.spawn()?
.wait()
} }
} }
impl Detect { impl Detect {
pub fn detect(_path: Option<&Path>) -> anyhow::Result<impl InstalledApp> { pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
Ok(App) let path = if let Some(path) = path {
path.to_path_buf().canonicalize()?
} else {
std::env::current_exe()?
.parent()
.context("no parent path for cli")?
.parent()
.context("no parent path for cli folder")?
.join("Zed.exe")
};
Ok(App(path))
} }
} }
} }

View File

@@ -202,7 +202,7 @@ impl Database {
.await .await
} }
pub async fn get_known_extension_versions<'a>(&self) -> Result<HashMap<String, Vec<String>>> { pub async fn get_known_extension_versions(&self) -> Result<HashMap<String, Vec<String>>> {
self.transaction(|tx| async move { self.transaction(|tx| async move {
let mut extension_external_ids_by_id = HashMap::default(); let mut extension_external_ids_by_id = HashMap::default();

View File

@@ -975,7 +975,7 @@ impl Server {
} }
} }
impl<'a> Deref for ConnectionPoolGuard<'a> { impl Deref for ConnectionPoolGuard<'_> {
type Target = ConnectionPool; type Target = ConnectionPool;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -983,13 +983,13 @@ impl<'a> Deref for ConnectionPoolGuard<'a> {
} }
} }
impl<'a> DerefMut for ConnectionPoolGuard<'a> { impl DerefMut for ConnectionPoolGuard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard &mut self.guard
} }
} }
impl<'a> Drop for ConnectionPoolGuard<'a> { impl Drop for ConnectionPoolGuard<'_> {
fn drop(&mut self) { fn drop(&mut self) {
#[cfg(test)] #[cfg(test)]
self.check_invariants(); self.check_invariants();

View File

@@ -1537,6 +1537,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
show_parameter_hints: false, show_parameter_hints: false,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
}); });
@@ -1552,6 +1553,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
show_parameter_hints: false, show_parameter_hints: false,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
}); });
@@ -1770,6 +1772,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
show_parameter_hints: false, show_parameter_hints: false,
show_other_hints: false, show_other_hints: false,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
}); });
@@ -1785,6 +1788,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
}); });

View File

@@ -6354,7 +6354,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 1 as preview // Open item 1 as preview
workspace workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, window, cx) workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@@ -6375,7 +6375,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview // Open item 2 as preview
workspace workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx) workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@@ -6507,7 +6507,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in right pane // Open item 2 as preview in right pane
workspace workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx) workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();
@@ -6545,7 +6545,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in left pane // Open item 2 as preview in left pane
workspace workspace
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx) workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
}) })
.await .await
.unwrap(); .unwrap();

View File

@@ -27,7 +27,7 @@ impl<'de> Deserialize<'de> for ChatPanelButton {
{ {
struct Visitor; struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor { impl serde::de::Visitor<'_> for Visitor {
type Value = ChatPanelButton; type Value = ChatPanelButton;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {

View File

@@ -15,7 +15,6 @@ path = "src/component.rs"
collections.workspace = true collections.workspace = true
gpui.workspace = true gpui.workspace = true
linkme.workspace = true linkme.workspace = true
once_cell.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
theme.workspace = true theme.workspace = true

View File

@@ -1,9 +1,9 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
use collections::HashMap; use collections::HashMap;
use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window}; use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
use linkme::distributed_slice; use linkme::distributed_slice;
use once_cell::sync::Lazy;
use parking_lot::RwLock; use parking_lot::RwLock;
use theme::ActiveTheme; use theme::ActiveTheme;
@@ -27,8 +27,8 @@ pub static __ALL_COMPONENTS: [fn()] = [..];
#[distributed_slice] #[distributed_slice]
pub static __ALL_PREVIEWS: [fn()] = [..]; pub static __ALL_PREVIEWS: [fn()] = [..];
pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> = pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
Lazy::new(|| RwLock::new(ComponentRegistry::new())); LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
pub struct ComponentRegistry { pub struct ComponentRegistry {
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>, components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,

View File

@@ -475,6 +475,7 @@ impl Copilot {
binary, binary,
root_path, root_path,
None, None,
Default::default(),
cx.clone(), cx.clone(),
)?; )?;

View File

@@ -411,7 +411,7 @@ async fn stream_completion(
match serde_json::from_str::<ResponseEvent>(line) { match serde_json::from_str::<ResponseEvent>(line) {
Ok(response) => { Ok(response) => {
if response.choices.first().is_none() if response.choices.is_empty()
|| response.choices.first().unwrap().finish_reason.is_some() || response.choices.first().unwrap().finish_reason.is_some()
{ {
None None

View File

@@ -192,7 +192,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
fn discard(&mut self, cx: &mut Context<Self>) { fn discard(&mut self, cx: &mut Context<Self>) {
let settings = AllLanguageSettings::get_global(cx); let settings = AllLanguageSettings::get_global(cx);
let copilot_enabled = settings.show_inline_completions(None, cx); let copilot_enabled = settings.show_edit_predictions(None, cx);
if !copilot_enabled { if !copilot_enabled {
return; return;

View File

@@ -562,9 +562,7 @@ impl ProjectDiagnosticsEditor {
)), )),
height: diagnostic.message.matches('\n').count() as u32 + 1, height: diagnostic.message.matches('\n').count() as u32 + 1,
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
render: diagnostic_block_renderer( render: diagnostic_block_renderer(diagnostic, None, true),
diagnostic, None, true, true,
),
priority: 0, priority: 0,
}); });
} }

View File

@@ -330,7 +330,11 @@ impl DisplayMap {
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive); block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
} }
pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) { pub fn fold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner(); let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx); let tab_size = Self::tab_size(&self.buffer, cx);
@@ -341,10 +345,14 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); let mut block_map = self.block_map.write(snapshot, edits);
block_map.fold_buffer(buffer_id, self.buffer.read(cx), cx) block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx)
} }
pub fn unfold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) { pub fn unfold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner(); let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx); let tab_size = Self::tab_size(&self.buffer, cx);
@@ -355,7 +363,7 @@ impl DisplayMap {
.wrap_map .wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx)); .update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits); let mut block_map = self.block_map.write(snapshot, edits);
block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx) block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx)
} }
pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool { pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {

View File

@@ -999,7 +999,7 @@ impl std::ops::DerefMut for BlockPoint {
} }
} }
impl<'a> Deref for BlockMapReader<'a> { impl Deref for BlockMapReader<'_> {
type Target = BlockSnapshot; type Target = BlockSnapshot;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -1007,13 +1007,13 @@ impl<'a> Deref for BlockMapReader<'a> {
} }
} }
impl<'a> DerefMut for BlockMapReader<'a> { impl DerefMut for BlockMapReader<'_> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.snapshot &mut self.snapshot
} }
} }
impl<'a> BlockMapReader<'a> { impl BlockMapReader<'_> {
pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> { pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
let block = self.blocks.iter().find(|block| block.id == block_id)?; let block = self.blocks.iter().find(|block| block.id == block_id)?;
let buffer_row = block let buffer_row = block
@@ -1053,7 +1053,7 @@ impl<'a> BlockMapReader<'a> {
} }
} }
impl<'a> BlockMapWriter<'a> { impl BlockMapWriter<'_> {
pub fn insert( pub fn insert(
&mut self, &mut self,
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>, blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
@@ -1239,26 +1239,45 @@ impl<'a> BlockMapWriter<'a> {
self.remove(blocks_to_remove); self.remove(blocks_to_remove);
} }
pub fn fold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) { pub fn fold_buffers(
self.0.folded_buffers.insert(buffer_id);
self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
}
pub fn unfold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
self.0.folded_buffers.remove(&buffer_id);
self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
}
fn recompute_blocks_for_buffer(
&mut self, &mut self,
buffer_id: BufferId, buffer_ids: impl IntoIterator<Item = BufferId>,
multi_buffer: &MultiBuffer, multi_buffer: &MultiBuffer,
cx: &App, cx: &App,
) { ) {
let wrap_snapshot = self.0.wrap_snapshot.borrow().clone(); self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
}
pub fn unfold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = BufferId>,
multi_buffer: &MultiBuffer,
cx: &App,
) {
self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
}
fn fold_or_unfold_buffers(
&mut self,
fold: bool,
buffer_ids: impl IntoIterator<Item = BufferId>,
multi_buffer: &MultiBuffer,
cx: &App,
) {
let mut ranges = Vec::new();
for buffer_id in buffer_ids {
if fold {
self.0.folded_buffers.insert(buffer_id);
} else {
self.0.folded_buffers.remove(&buffer_id);
}
ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
}
ranges.sort_unstable_by_key(|range| range.start);
let mut edits = Patch::default(); let mut edits = Patch::default();
for range in multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx) { let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
for range in ranges {
let last_edit_row = cmp::min( let last_edit_row = cmp::min(
wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1, wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
wrap_snapshot.max_point().row(), wrap_snapshot.max_point().row(),
@@ -1721,7 +1740,7 @@ impl BlockSnapshot {
} }
} }
impl<'a> BlockChunks<'a> { impl BlockChunks<'_> {
/// Go to the next transform /// Go to the next transform
fn advance(&mut self) { fn advance(&mut self) {
self.input_chunk = Chunk::default(); self.input_chunk = Chunk::default();
@@ -1837,7 +1856,7 @@ impl<'a> Iterator for BlockChunks<'a> {
} }
} }
impl<'a> Iterator for BlockRows<'a> { impl Iterator for BlockRows<'_> {
type Item = RowInfo; type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@@ -1933,7 +1952,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
} }
} }
impl<'a> Deref for BlockContext<'a, '_> { impl Deref for BlockContext<'_, '_> {
type Target = App; type Target = App;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -2730,7 +2749,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { buffer.read_with(cx, |buffer, cx| {
writer.fold_buffer(buffer_id_1, buffer, cx); writer.fold_buffers([buffer_id_1], buffer, cx);
}); });
let excerpt_blocks_1 = writer.insert(vec![BlockProperties { let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
@@ -2805,7 +2824,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { buffer.read_with(cx, |buffer, cx| {
writer.fold_buffer(buffer_id_2, buffer, cx); writer.fold_buffers([buffer_id_2], buffer, cx);
}); });
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@@ -2861,7 +2880,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { buffer.read_with(cx, |buffer, cx| {
writer.unfold_buffer(buffer_id_1, buffer, cx); writer.unfold_buffers([buffer_id_1], buffer, cx);
}); });
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@@ -2922,7 +2941,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { buffer.read_with(cx, |buffer, cx| {
writer.fold_buffer(buffer_id_3, buffer, cx); writer.fold_buffers([buffer_id_3], buffer, cx);
}); });
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@@ -3000,7 +3019,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default()); let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| { buffer.read_with(cx, |buffer, cx| {
writer.fold_buffer(buffer_id, buffer, cx); writer.fold_buffers([buffer_id], buffer, cx);
}); });
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
let blocks = blocks_snapshot let blocks = blocks_snapshot
@@ -3250,7 +3269,7 @@ mod tests {
); );
folded_count += 1; folded_count += 1;
unfolded_count -= 1; unfolded_count -= 1;
block_map.fold_buffer(buffer_to_fold, buffer, cx); block_map.fold_buffers([buffer_to_fold], buffer, cx);
} }
if unfold { if unfold {
let buffer_to_unfold = let buffer_to_unfold =
@@ -3258,7 +3277,7 @@ mod tests {
log::info!("Unfolding {buffer_to_unfold:?}"); log::info!("Unfolding {buffer_to_unfold:?}");
unfolded_count += 1; unfolded_count += 1;
folded_count -= 1; folded_count -= 1;
block_map.unfold_buffer(buffer_to_unfold, buffer, cx); block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
} }
log::info!( log::info!(
"Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}" "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"

View File

@@ -132,7 +132,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap); pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
impl<'a> FoldMapWriter<'a> { impl FoldMapWriter<'_> {
pub(crate) fn fold<T: ToOffset>( pub(crate) fn fold<T: ToOffset>(
&mut self, &mut self,
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>, ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
@@ -1121,7 +1121,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
} }
} }
impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange { impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering { fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
AnchorRangeExt::cmp(&self.0, &other.0, buffer) AnchorRangeExt::cmp(&self.0, &other.0, buffer)
} }
@@ -1144,7 +1144,7 @@ pub struct FoldRows<'a> {
fold_point: FoldPoint, fold_point: FoldPoint,
} }
impl<'a> FoldRows<'a> { impl FoldRows<'_> {
pub(crate) fn seek(&mut self, row: u32) { pub(crate) fn seek(&mut self, row: u32) {
let fold_point = FoldPoint::new(row, 0); let fold_point = FoldPoint::new(row, 0);
self.cursor.seek(&fold_point, Bias::Left, &()); self.cursor.seek(&fold_point, Bias::Left, &());
@@ -1155,7 +1155,7 @@ impl<'a> FoldRows<'a> {
} }
} }
impl<'a> Iterator for FoldRows<'a> { impl Iterator for FoldRows<'_> {
type Item = RowInfo; type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@@ -1190,7 +1190,7 @@ pub struct FoldChunks<'a> {
max_output_offset: FoldOffset, max_output_offset: FoldOffset,
} }
impl<'a> FoldChunks<'a> { impl FoldChunks<'_> {
pub(crate) fn seek(&mut self, range: Range<FoldOffset>) { pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
self.transform_cursor.seek(&range.start, Bias::Right, &()); self.transform_cursor.seek(&range.start, Bias::Right, &());

View File

@@ -215,7 +215,7 @@ pub struct InlayChunks<'a> {
snapshot: &'a InlaySnapshot, snapshot: &'a InlaySnapshot,
} }
impl<'a> InlayChunks<'a> { impl InlayChunks<'_> {
pub fn seek(&mut self, new_range: Range<InlayOffset>) { pub fn seek(&mut self, new_range: Range<InlayOffset>) {
self.transforms.seek(&new_range.start, Bias::Right, &()); self.transforms.seek(&new_range.start, Bias::Right, &());
@@ -341,7 +341,7 @@ impl<'a> Iterator for InlayChunks<'a> {
} }
} }
impl<'a> InlayBufferRows<'a> { impl InlayBufferRows<'_> {
pub fn seek(&mut self, row: u32) { pub fn seek(&mut self, row: u32) {
let inlay_point = InlayPoint::new(row, 0); let inlay_point = InlayPoint::new(row, 0);
self.transforms.seek(&inlay_point, Bias::Left, &()); self.transforms.seek(&inlay_point, Bias::Left, &());
@@ -363,7 +363,7 @@ impl<'a> InlayBufferRows<'a> {
} }
} }
impl<'a> Iterator for InlayBufferRows<'a> { impl Iterator for InlayBufferRows<'_> {
type Item = RowInfo; type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {

View File

@@ -498,7 +498,7 @@ pub struct TabChunks<'a> {
inside_leading_tab: bool, inside_leading_tab: bool,
} }
impl<'a> TabChunks<'a> { impl TabChunks<'_> {
pub(crate) fn seek(&mut self, range: Range<TabPoint>) { pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
let (input_start, expanded_char_column, to_next_stop) = let (input_start, expanded_char_column, to_next_stop) =
self.snapshot.to_fold_point(range.start, Bias::Left); self.snapshot.to_fold_point(range.start, Bias::Left);

View File

@@ -69,7 +69,7 @@ pub struct WrapRows<'a> {
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
} }
impl<'a> WrapRows<'a> { impl WrapRows<'_> {
pub(crate) fn seek(&mut self, start_row: u32) { pub(crate) fn seek(&mut self, start_row: u32) {
self.transforms self.transforms
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &()); .seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
@@ -872,7 +872,7 @@ impl WrapSnapshot {
} }
} }
impl<'a> WrapChunks<'a> { impl WrapChunks<'_> {
pub(crate) fn seek(&mut self, rows: Range<u32>) { pub(crate) fn seek(&mut self, rows: Range<u32>) {
let output_start = WrapPoint::new(rows.start, 0); let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0); let output_end = WrapPoint::new(rows.end, 0);
@@ -955,7 +955,7 @@ impl<'a> Iterator for WrapChunks<'a> {
} }
} }
impl<'a> Iterator for WrapRows<'a> { impl Iterator for WrapRows<'_> {
type Item = RowInfo; type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@@ -1120,7 +1120,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
} }
} }
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint { impl sum_tree::SeekTarget<'_, TransformSummary, TransformSummary> for TabPoint {
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering { fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
Ord::cmp(&self.0, &cursor_location.input.lines) Ord::cmp(&self.0, &cursor_location.input.lines)
} }

View File

@@ -994,7 +994,7 @@ struct RegisteredInlineCompletionProvider {
_subscription: Subscription, _subscription: Subscription,
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Eq)]
struct ActiveDiagnosticGroup { struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>, primary_range: Range<Anchor>,
primary_message: String, primary_message: String,
@@ -1031,6 +1031,7 @@ pub enum GotoDefinitionKind {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum InlayHintRefreshReason { enum InlayHintRefreshReason {
ModifiersChanged(bool),
Toggle(bool), Toggle(bool),
SettingsChange(InlayHintSettings), SettingsChange(InlayHintSettings),
NewLinesShown, NewLinesShown,
@@ -1042,6 +1043,7 @@ enum InlayHintRefreshReason {
impl InlayHintRefreshReason { impl InlayHintRefreshReason {
fn description(&self) -> &'static str { fn description(&self) -> &'static str {
match self { match self {
Self::ModifiersChanged(_) => "modifiers changed",
Self::Toggle(_) => "toggle", Self::Toggle(_) => "toggle",
Self::SettingsChange(_) => "settings change", Self::SettingsChange(_) => "settings change",
Self::NewLinesShown => "new lines shown", Self::NewLinesShown => "new lines shown",
@@ -3668,7 +3670,7 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.refresh_inlay_hints( self.refresh_inlay_hints(
InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled), InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
cx, cx,
); );
} }
@@ -3690,21 +3692,44 @@ impl Editor {
| InlayHintRefreshReason::ExcerptsRemoved(_) | InlayHintRefreshReason::ExcerptsRemoved(_)
); );
let (invalidate_cache, required_languages) = match reason { let (invalidate_cache, required_languages) = match reason {
InlayHintRefreshReason::ModifiersChanged(enabled) => {
match self.inlay_hint_cache.modifiers_override(enabled) {
Some(enabled) => {
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
self.splice_inlays(
&self
.visible_inlay_hints(cx)
.iter()
.map(|inlay| inlay.id)
.collect::<Vec<InlayId>>(),
Vec::new(),
cx,
);
return;
}
}
None => return,
}
}
InlayHintRefreshReason::Toggle(enabled) => { InlayHintRefreshReason::Toggle(enabled) => {
self.inlay_hint_cache.enabled = enabled; if self.inlay_hint_cache.toggle(enabled) {
if enabled { if enabled {
(InvalidationStrategy::RefreshRequested, None) (InvalidationStrategy::RefreshRequested, None)
} else {
self.splice_inlays(
&self
.visible_inlay_hints(cx)
.iter()
.map(|inlay| inlay.id)
.collect::<Vec<InlayId>>(),
Vec::new(),
cx,
);
return;
}
} else { } else {
self.inlay_hint_cache.clear();
self.splice_inlays(
&self
.visible_inlay_hints(cx)
.iter()
.map(|inlay| inlay.id)
.collect::<Vec<InlayId>>(),
Vec::new(),
cx,
);
return; return;
} }
} }
@@ -4959,7 +4984,7 @@ impl Editor {
}); });
let preview_requires_modifier = let preview_requires_modifier =
all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Auto; all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
EditPredictionSettings::Enabled { EditPredictionSettings::Enabled {
show_in_menu, show_in_menu,
@@ -5005,7 +5030,7 @@ impl Editor {
return Some(true); return Some(true);
}; };
let settings = all_language_settings(Some(file), cx); let settings = all_language_settings(Some(file), cx);
Some(settings.inline_completions_enabled_for_path(file.path())) Some(settings.edit_predictions_enabled_for_file(file, cx))
}) })
.unwrap_or(false) .unwrap_or(false)
} }
@@ -7709,12 +7734,20 @@ impl Editor {
for hunk in &hunks { for hunk in &hunks {
self.prepare_restore_change(&mut revert_changes, hunk, cx); self.prepare_restore_change(&mut revert_changes, hunk, cx);
} }
Self::do_stage_or_unstage(project, false, buffer_id, hunks.into_iter(), &snapshot, cx); Self::do_stage_or_unstage(
project,
false,
buffer_id,
hunks.into_iter(),
&snapshot,
window,
cx,
);
} }
drop(chunk_by); drop(chunk_by);
if !revert_changes.is_empty() { if !revert_changes.is_empty() {
self.transact(window, cx, |editor, window, cx| { self.transact(window, cx, |editor, window, cx| {
editor.revert(revert_changes, window, cx); editor.restore(revert_changes, window, cx);
}); });
} }
} }
@@ -8770,6 +8803,13 @@ impl Editor {
self.change_selections(None, window, cx, |s| { self.change_selections(None, window, cx, |s| {
s.select_anchors(selections.to_vec()); s.select_anchors(selections.to_vec());
}); });
} else {
log::error!(
"No entry in selection_history found for undo. \
This may correspond to a bug where undo does not update the selection. \
If this is occurring, please add details to \
https://github.com/zed-industries/zed/issues/22692"
);
} }
self.request_autoscroll(Autoscroll::fit(), cx); self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(window, cx); self.unmark_text(window, cx);
@@ -8791,6 +8831,13 @@ impl Editor {
self.change_selections(None, window, cx, |s| { self.change_selections(None, window, cx, |s| {
s.select_anchors(selections.to_vec()); s.select_anchors(selections.to_vec());
}); });
} else {
log::error!(
"No entry in selection_history found for redo. \
This may correspond to a bug where undo does not update the selection. \
If this is occurring, please add details to \
https://github.com/zed-industries/zed/issues/22692"
);
} }
self.request_autoscroll(Autoscroll::fit(), cx); self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(window, cx); self.unmark_text(window, cx);
@@ -12517,16 +12564,20 @@ impl Editor {
if is_valid != active_diagnostics.is_valid { if is_valid != active_diagnostics.is_valid {
active_diagnostics.is_valid = is_valid; active_diagnostics.is_valid = is_valid;
let mut new_styles = HashMap::default(); if is_valid {
for (block_id, diagnostic) in &active_diagnostics.blocks { let mut new_styles = HashMap::default();
new_styles.insert( for (block_id, diagnostic) in &active_diagnostics.blocks {
*block_id, new_styles.insert(
diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid), *block_id,
); diagnostic_block_renderer(diagnostic.clone(), None, true),
);
}
self.display_map.update(cx, |display_map, _cx| {
display_map.replace_blocks(new_styles);
});
} else {
self.dismiss_diagnostics(cx);
} }
self.display_map.update(cx, |display_map, _cx| {
display_map.replace_blocks(new_styles)
});
} }
} }
} }
@@ -12577,7 +12628,7 @@ impl Editor {
buffer.anchor_after(entry.range.start), buffer.anchor_after(entry.range.start),
), ),
height: message_height, height: message_height,
render: diagnostic_block_renderer(diagnostic, None, true, true), render: diagnostic_block_renderer(diagnostic, None, true),
priority: 0, priority: 0,
} }
}), }),
@@ -13288,8 +13339,9 @@ impl Editor {
return; return;
} }
let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
self.display_map self.display_map.update(cx, |display_map, cx| {
.update(cx, |display_map, cx| display_map.fold_buffer(buffer_id, cx)); display_map.fold_buffers([buffer_id], cx)
});
cx.emit(EditorEvent::BufferFoldToggled { cx.emit(EditorEvent::BufferFoldToggled {
ids: folded_excerpts.iter().map(|&(id, _)| id).collect(), ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
folded: true, folded: true,
@@ -13303,7 +13355,7 @@ impl Editor {
} }
let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
self.display_map.update(cx, |display_map, cx| { self.display_map.update(cx, |display_map, cx| {
display_map.unfold_buffer(buffer_id, cx); display_map.unfold_buffers([buffer_id], cx);
}); });
cx.emit(EditorEvent::BufferFoldToggled { cx.emit(EditorEvent::BufferFoldToggled {
ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(), ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
@@ -13424,13 +13476,13 @@ impl Editor {
pub fn toggle_staged_selected_diff_hunks( pub fn toggle_staged_selected_diff_hunks(
&mut self, &mut self,
_: &::git::ToggleStaged, _: &::git::ToggleStaged,
_window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect(); let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot); let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
self.stage_or_unstage_diff_hunks(stage, &ranges, cx); self.stage_or_unstage_diff_hunks(stage, &ranges, window, cx);
} }
pub fn stage_and_next( pub fn stage_and_next(
@@ -13455,6 +13507,7 @@ impl Editor {
&mut self, &mut self,
stage: bool, stage: bool,
ranges: &[Range<Anchor>], ranges: &[Range<Anchor>],
window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -13466,7 +13519,7 @@ impl Editor {
.diff_hunks_in_ranges(&ranges, &snapshot) .diff_hunks_in_ranges(&ranges, &snapshot)
.chunk_by(|hunk| hunk.buffer_id); .chunk_by(|hunk| hunk.buffer_id);
for (buffer_id, hunks) in &chunk_by { for (buffer_id, hunks) in &chunk_by {
Self::do_stage_or_unstage(project, stage, buffer_id, hunks, &snapshot, cx); Self::do_stage_or_unstage(project, stage, buffer_id, hunks, &snapshot, window, cx);
} }
} }
@@ -13478,7 +13531,7 @@ impl Editor {
) { ) {
let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>(); let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
if ranges.iter().any(|range| range.start != range.end) { if ranges.iter().any(|range| range.start != range.end) {
self.stage_or_unstage_diff_hunks(stage, &ranges[..], cx); self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
return; return;
} }
@@ -13521,7 +13574,7 @@ impl Editor {
buffer.read(cx).remote_id(), buffer.read(cx).remote_id(),
range, range,
)]; )];
self.stage_or_unstage_diff_hunks(stage, &ranges[..], cx); self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
let snapshot = self.buffer().read(cx).snapshot(cx); let snapshot = self.buffer().read(cx).snapshot(cx);
let mut point = ranges.last().unwrap().end.to_point(&snapshot); let mut point = ranges.last().unwrap().end.to_point(&snapshot);
if point.row < snapshot.max_row().0 { if point.row < snapshot.max_row().0 {
@@ -13535,7 +13588,7 @@ impl Editor {
return; return;
} }
} }
self.stage_or_unstage_diff_hunks(stage, &ranges[..], cx); self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
self.go_to_next_hunk(&Default::default(), window, cx); self.go_to_next_hunk(&Default::default(), window, cx);
} }
@@ -13545,6 +13598,7 @@ impl Editor {
buffer_id: BufferId, buffer_id: BufferId,
hunks: impl Iterator<Item = MultiBufferDiffHunk>, hunks: impl Iterator<Item = MultiBufferDiffHunk>,
snapshot: &MultiBufferSnapshot, snapshot: &MultiBufferSnapshot,
window: &mut Window,
cx: &mut App, cx: &mut App,
) { ) {
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else { let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
@@ -13587,20 +13641,18 @@ impl Editor {
cx, cx,
) )
}; };
if file_exists { if file_exists {
let buffer_store = project.read(cx).buffer_store().clone(); let buffer_store = project.read(cx).buffer_store().clone();
buffer_store buffer_store
.update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx)) .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
let recv = repo
.read(cx)
.set_index_text(&path, new_index_text.map(|rope| rope.to_string()));
cx.background_spawn( cx.background_spawn(async move { recv.await? })
repo.read(cx) .detach_and_notify_err(window, cx);
.set_index_text(&path, new_index_text.map(|rope| rope.to_string()))
.log_err(),
)
.detach();
} }
pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) { pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
@@ -15186,8 +15238,16 @@ impl Editor {
.retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some()); .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() }) cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
} }
multi_buffer::Event::ExcerptsEdited { ids } => { multi_buffer::Event::ExcerptsEdited {
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() }) excerpt_ids,
buffer_ids,
} => {
self.display_map.update(cx, |map, cx| {
map.unfold_buffers(buffer_ids.iter().copied(), cx)
});
cx.emit(EditorEvent::ExcerptsEdited {
ids: excerpt_ids.clone(),
})
} }
multi_buffer::Event::ExcerptsExpanded { ids } => { multi_buffer::Event::ExcerptsExpanded { ids } => {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
@@ -15804,11 +15864,12 @@ impl Editor {
&mut self, &mut self,
event: FocusOutEvent, event: FocusOutEvent,
_window: &mut Window, _window: &mut Window,
_cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if event.blurred != self.focus_handle { if event.blurred != self.focus_handle {
self.last_focused_descendant = Some(event.blurred); self.last_focused_descendant = Some(event.blurred);
} }
self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
} }
pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) { pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -15864,13 +15925,16 @@ impl Editor {
FILE_HEADER_HEIGHT FILE_HEADER_HEIGHT
} }
pub fn revert( pub fn restore(
&mut self, &mut self,
revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>, revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.buffer().update(cx, |multi_buffer, cx| { let workspace = self.workspace();
let project = self.project.as_ref();
let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
let mut tasks = Vec::new();
for (buffer_id, changes) in revert_changes { for (buffer_id, changes) in revert_changes {
if let Some(buffer) = multi_buffer.buffer(buffer_id) { if let Some(buffer) = multi_buffer.buffer(buffer_id) {
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
@@ -15882,9 +15946,44 @@ impl Editor {
cx, cx,
); );
}); });
if let Some(project) =
project.filter(|_| multi_buffer.all_diff_hunks_expanded())
{
project.update(cx, |project, cx| {
tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
})
}
} }
} }
tasks
}); });
cx.spawn_in(window, |_, mut cx| async move {
for (buffer, task) in save_tasks {
let result = task.await;
if result.is_err() {
let Some(path) = buffer
.read_with(&cx, |buffer, cx| buffer.project_path(cx))
.ok()
else {
continue;
};
if let Some((workspace, path)) = workspace.as_ref().zip(path) {
let Some(task) = cx
.update_window_entity(&workspace, |workspace, window, cx| {
workspace
.open_path_preview(path, None, false, false, false, window, cx)
})
.ok()
else {
continue;
};
task.await.log_err();
}
}
}
})
.detach();
self.change_selections(None, window, cx, |selections| selections.refresh()); self.change_selections(None, window, cx, |selections| selections.refresh());
} }
@@ -17294,7 +17393,7 @@ impl Focusable for Editor {
} }
impl Render for Editor { impl Render for Editor {
fn render<'a>(&mut self, _: &mut Window, cx: &mut Context<'a, Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let mut text_style = match self.mode { let mut text_style = match self.mode {
@@ -17735,7 +17834,6 @@ pub fn diagnostic_block_renderer(
diagnostic: Diagnostic, diagnostic: Diagnostic,
max_message_rows: Option<u8>, max_message_rows: Option<u8>,
allow_closing: bool, allow_closing: bool,
_is_valid: bool,
) -> RenderBlock { ) -> RenderBlock {
let (text_without_backticks, code_ranges) = let (text_without_backticks, code_ranges) =
highlight_diagnostic_message(&diagnostic, max_message_rows); highlight_diagnostic_message(&diagnostic, max_message_rows);

View File

@@ -177,7 +177,7 @@ impl<'de> Deserialize<'de> for ScrollbarDiagnostics {
{ {
struct Visitor; struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor { impl serde::de::Visitor<'_> for Visitor {
type Value = ScrollbarDiagnostics; type Value = ScrollbarDiagnostics;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {

View File

@@ -10970,6 +10970,106 @@ async fn cycle_through_same_place_diagnostics(
"}); "});
} }
#[gpui::test]
async fn active_diagnostics_dismiss_after_invalidation(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let lsp_store =
cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
cx.set_state(indoc! {"
ˇfn func(abc def: i32) -> u32 {
}
"});
let message = "Something's wrong!";
cx.update(|_, cx| {
lsp_store.update(cx, |lsp_store, cx| {
lsp_store
.update_diagnostics(
LanguageServerId(0),
lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
range: lsp::Range::new(
lsp::Position::new(0, 11),
lsp::Position::new(0, 12),
),
severity: Some(lsp::DiagnosticSeverity::ERROR),
message: message.to_string(),
..Default::default()
}],
},
&[],
cx,
)
.unwrap()
});
});
executor.run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
assert_eq!(
editor
.active_diagnostics
.as_ref()
.map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
Some(message),
"Should have a diagnostics group activated"
);
});
cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 {
}
"});
cx.update(|_, cx| {
lsp_store.update(cx, |lsp_store, cx| {
lsp_store
.update_diagnostics(
LanguageServerId(0),
lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
version: None,
diagnostics: Vec::new(),
},
&[],
cx,
)
.unwrap()
});
});
executor.run_until_parked();
cx.update_editor(|editor, _, _| {
assert_eq!(
editor.active_diagnostics, None,
"After no diagnostics set to the editor, no diagnostics should be active"
);
});
cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 {
}
"});
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
assert_eq!(
editor.active_diagnostics, None,
"Should be no diagnostics to go to and activate"
);
});
cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 {
}
"});
}
#[gpui::test] #[gpui::test]
async fn test_diagnostics_with_links(cx: &mut TestAppContext) { async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
@@ -14984,6 +15084,78 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
); );
} }
#[gpui::test]
async fn test_toggle_deletion_hunk_at_start_of_file(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let diff_base = r#"
a
b
c
"#
.unindent();
cx.set_state(
&r#"
ˇb
c
"#
.unindent(),
);
cx.set_head_text(&diff_base);
cx.update_editor(|editor, window, cx| {
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
});
executor.run_until_parked();
let hunk_expanded = r#"
- a
ˇb
c
"#
.unindent();
cx.assert_state_with_diff(hunk_expanded.clone());
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
.collect::<Vec<_>>();
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
let buffer_id = hunks[0].buffer_id;
hunks
.into_iter()
.map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
.collect::<Vec<_>>()
});
assert_eq!(hunk_ranges.len(), 1);
cx.update_editor(|editor, _, cx| {
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
});
executor.run_until_parked();
let hunk_collapsed = r#"
ˇb
c
"#
.unindent();
cx.assert_state_with_diff(hunk_collapsed);
cx.update_editor(|editor, _, cx| {
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(hunk_expanded.clone());
}
#[gpui::test] #[gpui::test]
async fn test_display_diff_hunks(cx: &mut TestAppContext) { async fn test_display_diff_hunks(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
@@ -15544,7 +15716,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
} }
#[gpui::test] #[gpui::test]
async fn test_multi_buffer_folding(cx: &mut TestAppContext) { async fn test_folding_buffers(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string(); let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
@@ -15651,7 +15823,7 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
let multi_buffer_editor = cx.new_window_entity(|window, cx| { let multi_buffer_editor = cx.new_window_entity(|window, cx| {
Editor::new( Editor::new(
EditorMode::Full, EditorMode::Full,
multi_buffer, multi_buffer.clone(),
Some(project.clone()), Some(project.clone()),
true, true,
window, window,
@@ -15659,10 +15831,9 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
) )
}); });
let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
assert_eq!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
full_text, "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
); );
multi_buffer_editor.update(cx, |editor, cx| { multi_buffer_editor.update(cx, |editor, cx| {
@@ -15708,12 +15879,25 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
"After unfolding the second buffer, its text should be displayed" "After unfolding the second buffer, its text should be displayed"
); );
multi_buffer_editor.update(cx, |editor, cx| { // Typing inside of buffer 1 causes that buffer to be unfolded.
editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx) multi_buffer_editor.update_in(cx, |editor, window, cx| {
assert_eq!(
multi_buffer
.read(cx)
.snapshot(cx)
.text_for_range(Point::new(1, 0)..Point::new(1, 4))
.collect::<String>(),
"bbbb"
);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
});
editor.handle_input("B", window, cx);
}); });
assert_eq!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
"\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n", "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
"After unfolding the first buffer, its and 2nd buffer's text should be displayed" "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
); );
@@ -15722,13 +15906,13 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
}); });
assert_eq!( assert_eq!(
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)), multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
full_text, "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
"After unfolding the all buffers, all original text should be displayed" "After unfolding the all buffers, all original text should be displayed"
); );
} }
#[gpui::test] #[gpui::test]
async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) { async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let sample_text_1 = "1111\n2222\n3333".to_string(); let sample_text_1 = "1111\n2222\n3333".to_string();
@@ -15877,7 +16061,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
} }
#[gpui::test] #[gpui::test]
async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) { async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string(); let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();

View File

@@ -12,6 +12,7 @@ use crate::{
hover_popover::{ hover_popover::{
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
}, },
inlay_hint_settings,
items::BufferSearchHighlights, items::BufferSearchHighlights,
mouse_context_menu::{self, MenuPosition, MouseContextMenu}, mouse_context_menu::{self, MenuPosition, MouseContextMenu},
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair}, scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
@@ -19,16 +20,17 @@ use crate::{
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown,
RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
}; };
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind}; use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
use client::ParticipantIndex; use client::ParticipantIndex;
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use file_icons::FileIcons; use file_icons::FileIcons;
use git::{blame::BlameEntry, Oid}; use git::{blame::BlameEntry, status::FileStatus, Oid};
use gpui::{ use gpui::{
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds, relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
@@ -73,7 +75,7 @@ use ui::{
POPOVER_Y_PADDING, POPOVER_Y_PADDING,
}; };
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use util::{debug_panic, RangeExt, ResultExt}; use util::{debug_panic, maybe, RangeExt, ResultExt};
use workspace::{item::Item, notifications::NotifyTaskExt}; use workspace::{item::Item, notifications::NotifyTaskExt};
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.; const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
@@ -505,6 +507,25 @@ impl EditorElement {
return; return;
} }
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
let inlay_hint_settings = inlay_hint_settings(
editor.selections.newest_anchor().head(),
&editor.buffer.read(cx).snapshot(cx),
cx,
);
if let Some(inlay_modifiers) = inlay_hint_settings
.toggle_on_modifiers_press
.as_ref()
.filter(|modifiers| modifiers.modified())
{
editor.refresh_inlay_hints(
InlayHintRefreshReason::ModifiersChanged(
inlay_modifiers == &event.modifiers,
),
cx,
);
}
if editor.hover_state.focused(window, cx) { if editor.hover_state.focused(window, cx) {
return; return;
} }
@@ -2646,6 +2667,21 @@ impl EditorElement {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> Div { ) -> Div {
let file_status = maybe!({
let project = self.editor.read(cx).project.as_ref()?.read(cx);
let (repo, path) =
project.repository_and_path_for_buffer_id(for_excerpt.buffer_id, cx)?;
let status = repo.read(cx).repository_entry.status_for_path(&path)?;
Some(status.status)
})
.filter(|_| {
self.editor
.read(cx)
.buffer
.read(cx)
.all_diff_hunks_expanded()
});
let include_root = self let include_root = self
.editor .editor
.read(cx) .read(cx)
@@ -2749,12 +2785,36 @@ impl EditorElement {
h_flex() h_flex()
.gap_2() .gap_2()
.child( .child(
filename Label::new(
.map(SharedString::from) filename
.unwrap_or_else(|| "untitled".into()), .map(SharedString::from)
.unwrap_or_else(|| "untitled".into()),
)
.single_line()
.when_some(
file_status,
|el, status| {
el.color(if status.is_conflicted() {
Color::Conflict
} else if status.is_modified() {
Color::Modified
} else if status.is_deleted() {
Color::Disabled
} else {
Color::Created
})
.when(status.is_deleted(), |el| el.strikethrough())
},
),
) )
.when_some(parent_path, |then, path| { .when_some(parent_path, |then, path| {
then.child(div().child(path).text_color(colors.text_muted)) then.child(div().child(path).text_color(
if file_status.is_some_and(FileStatus::is_deleted) {
colors.text_disabled
} else {
colors.text_muted
},
))
}), }),
) )
.when(is_selected, |el| { .when(is_selected, |el| {
@@ -8720,11 +8780,12 @@ fn diff_hunk_controls(
}) })
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();
move |_event, _, cx| { move |_event, window, cx| {
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks( editor.stage_or_unstage_diff_hunks(
false, false,
&[hunk_range.start..hunk_range.start], &[hunk_range.start..hunk_range.start],
window,
cx, cx,
); );
}); });
@@ -8749,11 +8810,12 @@ fn diff_hunk_controls(
}) })
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();
move |_event, _, cx| { move |_event, window, cx| {
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks( editor.stage_or_unstage_diff_hunks(
true, true,
&[hunk_range.start..hunk_range.start], &[hunk_range.start..hunk_range.start],
window,
cx, cx,
); );
}); });

View File

@@ -1271,6 +1271,7 @@ mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });

View File

@@ -1536,6 +1536,7 @@ mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });

View File

@@ -36,6 +36,7 @@ pub struct InlayHintCache {
allowed_hint_kinds: HashSet<Option<InlayHintKind>>, allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
version: usize, version: usize,
pub(super) enabled: bool, pub(super) enabled: bool,
modifiers_override: bool,
enabled_in_settings: bool, enabled_in_settings: bool,
update_tasks: HashMap<ExcerptId, TasksForRanges>, update_tasks: HashMap<ExcerptId, TasksForRanges>,
refresh_task: Task<()>, refresh_task: Task<()>,
@@ -265,6 +266,7 @@ impl InlayHintCache {
Self { Self {
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(), allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
enabled: inlay_hint_settings.enabled, enabled: inlay_hint_settings.enabled,
modifiers_override: false,
enabled_in_settings: inlay_hint_settings.enabled, enabled_in_settings: inlay_hint_settings.enabled,
hints: HashMap::default(), hints: HashMap::default(),
update_tasks: HashMap::default(), update_tasks: HashMap::default(),
@@ -295,8 +297,9 @@ impl InlayHintCache {
// visibility would not change when updating the setting if they were ever toggled. // visibility would not change when updating the setting if they were ever toggled.
if new_hint_settings.enabled != self.enabled_in_settings { if new_hint_settings.enabled != self.enabled_in_settings {
self.enabled = new_hint_settings.enabled; self.enabled = new_hint_settings.enabled;
self.enabled_in_settings = new_hint_settings.enabled;
self.modifiers_override = false;
}; };
self.enabled_in_settings = new_hint_settings.enabled;
self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms); self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms);
self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms); self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
@@ -323,6 +326,7 @@ impl InlayHintCache {
} }
} }
(true, false) => { (true, false) => {
self.modifiers_override = false;
self.allowed_hint_kinds = new_allowed_hint_kinds; self.allowed_hint_kinds = new_allowed_hint_kinds;
if self.hints.is_empty() { if self.hints.is_empty() {
ControlFlow::Break(None) ControlFlow::Break(None)
@@ -335,12 +339,39 @@ impl InlayHintCache {
} }
} }
(false, true) => { (false, true) => {
self.modifiers_override = false;
self.allowed_hint_kinds = new_allowed_hint_kinds; self.allowed_hint_kinds = new_allowed_hint_kinds;
ControlFlow::Continue(()) ControlFlow::Continue(())
} }
} }
} }
pub(super) fn modifiers_override(&mut self, new_override: bool) -> Option<bool> {
if self.modifiers_override == new_override {
return None;
}
self.modifiers_override = new_override;
if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
{
self.clear();
Some(false)
} else {
Some(true)
}
}
pub(super) fn toggle(&mut self, enabled: bool) -> bool {
if self.enabled == enabled {
return false;
}
self.enabled = enabled;
self.modifiers_override = false;
if !enabled {
self.clear();
}
true
}
/// If needed, queries LSP for new inlay hints, using the invalidation strategy given. /// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
/// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first, /// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
/// followed by the delayed queries of the same range above and below the visible one. /// followed by the delayed queries of the same range above and below the visible one.
@@ -353,7 +384,8 @@ impl InlayHintCache {
ignore_debounce: bool, ignore_debounce: bool,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) -> Option<InlaySplice> { ) -> Option<InlaySplice> {
if !self.enabled { if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
{
return None; return None;
} }
let mut invalidated_hints = Vec::new(); let mut invalidated_hints = Vec::new();
@@ -1288,6 +1320,7 @@ pub mod tests {
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: allowed_hint_kinds.contains(&None), show_other_hints: allowed_hint_kinds.contains(&None),
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| { let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
@@ -1391,6 +1424,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -1493,6 +1527,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -1712,6 +1747,7 @@ pub mod tests {
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: allowed_hint_kinds.contains(&None), show_other_hints: allowed_hint_kinds.contains(&None),
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -1871,6 +1907,7 @@ pub mod tests {
.contains(&Some(InlayHintKind::Parameter)), .contains(&Some(InlayHintKind::Parameter)),
show_other_hints: new_allowed_hint_kinds.contains(&None), show_other_hints: new_allowed_hint_kinds.contains(&None),
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
cx.executor().run_until_parked(); cx.executor().run_until_parked();
@@ -1913,6 +1950,7 @@ pub mod tests {
.contains(&Some(InlayHintKind::Parameter)), .contains(&Some(InlayHintKind::Parameter)),
show_other_hints: another_allowed_hint_kinds.contains(&None), show_other_hints: another_allowed_hint_kinds.contains(&None),
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
cx.executor().run_until_parked(); cx.executor().run_until_parked();
@@ -1967,6 +2005,7 @@ pub mod tests {
.contains(&Some(InlayHintKind::Parameter)), .contains(&Some(InlayHintKind::Parameter)),
show_other_hints: final_allowed_hint_kinds.contains(&None), show_other_hints: final_allowed_hint_kinds.contains(&None),
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
cx.executor().run_until_parked(); cx.executor().run_until_parked();
@@ -2038,6 +2077,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -2169,6 +2209,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -2467,6 +2508,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -2811,6 +2853,7 @@ pub mod tests {
show_parameter_hints: false, show_parameter_hints: false,
show_other_hints: false, show_other_hints: false,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -2992,6 +3035,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
cx.executor().run_until_parked(); cx.executor().run_until_parked();
@@ -3023,6 +3067,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -3114,6 +3159,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
@@ -3187,6 +3233,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });
cx.executor().run_until_parked(); cx.executor().run_until_parked();
@@ -3246,6 +3293,7 @@ pub mod tests {
show_parameter_hints: true, show_parameter_hints: true,
show_other_hints: true, show_other_hints: true,
show_background: false, show_background: false,
toggle_on_modifiers_press: None,
}) })
}); });

View File

@@ -1739,6 +1739,7 @@ mod tests {
let file = TestFile { let file = TestFile {
path: Path::new("").into(), path: Path::new("").into(),
root_name: String::new(), root_name: String::new(),
local_root: None,
}; };
assert_eq!(path_for_file(&file, 0, false, cx), None); assert_eq!(path_for_file(&file, 0, false, cx), None);
} }

View File

@@ -238,7 +238,7 @@ pub fn indented_line_beginning(
} }
/// Returns a position of the end of line. /// Returns a position of the end of line.
///
/// If `stop_at_soft_boundaries` is true, the returned position is that of the /// If `stop_at_soft_boundaries` is true, the returned position is that of the
/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped). /// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped).
/// Otherwise it's always going to be the end of a logical line. /// Otherwise it's always going to be the end of a logical line.

View File

@@ -843,14 +843,14 @@ impl<'a> MutableSelectionsCollection<'a> {
} }
} }
impl<'a> Deref for MutableSelectionsCollection<'a> { impl Deref for MutableSelectionsCollection<'_> {
type Target = SelectionsCollection; type Target = SelectionsCollection;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.collection self.collection
} }
} }
impl<'a> DerefMut for MutableSelectionsCollection<'a> { impl DerefMut for MutableSelectionsCollection<'_> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.collection self.collection
} }

View File

@@ -1,93 +1,73 @@
use crate::Editor; use crate::Editor;
use gpui::{App, AppContext as _, Task as AsyncTask, Window}; use gpui::{App, Task, Window};
use project::Location; use project::Location;
use task::{TaskContext, TaskVariables, VariableName}; use task::{TaskContext, TaskVariables, VariableName};
use text::{ToOffset, ToPoint}; use text::{ToOffset, ToPoint};
use workspace::Workspace;
fn task_context_with_editor( impl Editor {
editor: &mut Editor, pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
window: &mut Window, let Some(project) = self.project.clone() else {
cx: &mut App, return Task::ready(None);
) -> AsyncTask<Option<TaskContext>> {
let Some(project) = editor.project.clone() else {
return AsyncTask::ready(None);
};
let (selection, buffer, editor_snapshot) = {
let selection = editor.selections.newest_adjusted(cx);
let Some((buffer, _)) = editor
.buffer()
.read(cx)
.point_to_buffer_offset(selection.start, cx)
else {
return AsyncTask::ready(None);
}; };
let snapshot = editor.snapshot(window, cx); let (selection, buffer, editor_snapshot) = {
(selection, buffer, snapshot) let selection = self.selections.newest_adjusted(cx);
}; let Some((buffer, _)) = self
let selection_range = selection.range(); .buffer()
let start = editor_snapshot .read(cx)
.display_snapshot .point_to_buffer_offset(selection.start, cx)
.buffer_snapshot else {
.anchor_after(selection_range.start) return Task::ready(None);
.text_anchor; };
let end = editor_snapshot let snapshot = self.snapshot(window, cx);
.display_snapshot (selection, buffer, snapshot)
.buffer_snapshot };
.anchor_after(selection_range.end) let selection_range = selection.range();
.text_anchor; let start = editor_snapshot
let location = Location { .display_snapshot
buffer, .buffer_snapshot
range: start..end, .anchor_after(selection_range.start)
}; .text_anchor;
let captured_variables = { let end = editor_snapshot
let mut variables = TaskVariables::default(); .display_snapshot
let buffer = location.buffer.read(cx); .buffer_snapshot
let buffer_id = buffer.remote_id(); .anchor_after(selection_range.end)
let snapshot = buffer.snapshot(); .text_anchor;
let starting_point = location.range.start.to_point(&snapshot); let location = Location {
let starting_offset = starting_point.to_offset(&snapshot); buffer,
for (_, tasks) in editor range: start..end,
.tasks };
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1)) let captured_variables = {
{ let mut variables = TaskVariables::default();
if !tasks let buffer = location.buffer.read(cx);
.context_range let buffer_id = buffer.remote_id();
.contains(&crate::BufferOffset(starting_offset)) let snapshot = buffer.snapshot();
let starting_point = location.range.start.to_point(&snapshot);
let starting_offset = starting_point.to_offset(&snapshot);
for (_, tasks) in self
.tasks
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
{ {
continue; if !tasks
.context_range
.contains(&crate::BufferOffset(starting_offset))
{
continue;
}
for (capture_name, value) in tasks.extra_variables.iter() {
variables.insert(
VariableName::Custom(capture_name.to_owned().into()),
value.clone(),
);
}
} }
for (capture_name, value) in tasks.extra_variables.iter() { variables
variables.insert( };
VariableName::Custom(capture_name.to_owned().into()),
value.clone(),
);
}
}
variables
};
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
project.task_store().update(cx, |task_store, cx| { project.task_store().update(cx, |task_store, cx| {
task_store.task_context_for_location(captured_variables, location, cx) task_store.task_context_for_location(captured_variables, location, cx)
})
}) })
}) }
}
pub fn task_context(
workspace: &Workspace,
window: &mut Window,
cx: &mut App,
) -> AsyncTask<TaskContext> {
let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
else {
return AsyncTask::ready(TaskContext::default());
};
editor.update(cx, |editor, cx| {
let context_task = task_context_with_editor(editor, window, cx);
cx.background_spawn(async move { context_task.await.unwrap_or_default() })
})
} }

View File

@@ -275,7 +275,11 @@ async fn run_evaluation(
let db_path = Path::new(EVAL_DB_PATH); let db_path = Path::new(EVAL_DB_PATH);
let api_key = std::env::var("OPENAI_API_KEY").unwrap(); let api_key = std::env::var("OPENAI_API_KEY").unwrap();
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new()); let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>; let fs = Arc::new(RealFs::new(
git_hosting_provider_registry,
None,
PathBuf::from("/non/existent/askpass"),
)) as Arc<dyn Fs>;
let clock = Arc::new(RealSystemClock); let clock = Arc::new(RealSystemClock);
let client = cx let client = cx
.update(|cx| { .update(|cx| {

View File

@@ -181,7 +181,10 @@ pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
} }
fn extension() -> &'static mut dyn Extension { fn extension() -> &'static mut dyn Extension {
unsafe { EXTENSION.as_deref_mut().unwrap() } #[expect(static_mut_refs)]
unsafe {
EXTENSION.as_deref_mut().unwrap()
}
} }
static mut EXTENSION: Option<Box<dyn Extension>> = None; static mut EXTENSION: Option<Box<dyn Extension>> = None;

View File

@@ -78,10 +78,9 @@ impl HeadlessExtensionStore {
if e.dev { if e.dev {
return true; return true;
} }
!self self.loaded_extensions
.loaded_extensions
.get(e.id.as_str()) .get(e.id.as_str())
.is_some_and(|loaded| loaded.as_ref() == e.version.as_str()) .is_none_or(|loaded| loaded.as_ref() != e.version.as_str())
}) })
.collect(); .collect();

View File

@@ -348,7 +348,7 @@ impl Extension {
.call_labels_for_completions( .call_labels_for_completions(
store, store,
&language_server_id.0, &language_server_id.0,
&completions.into_iter().map(Into::into).collect::<Vec<_>>(), &completions.into_iter().collect::<Vec<_>>(),
) )
.await? .await?
.map(|labels| { .map(|labels| {
@@ -402,7 +402,7 @@ impl Extension {
.call_labels_for_symbols( .call_labels_for_symbols(
store, store,
&language_server_id.0, &language_server_id.0,
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(), &symbols.into_iter().collect::<Vec<_>>(),
) )
.await? .await?
.map(|labels| { .map(|labels| {

View File

@@ -1195,6 +1195,7 @@ impl PickerDelegate for FileFinderDelegate {
None, None,
true, true,
allow_preview, allow_preview,
true,
window, window,
cx, cx,
) )
@@ -1450,9 +1451,9 @@ impl<'a> PathComponentSlice<'a> {
matches.next(); matches.next();
} }
if is_first_normal || is_last || !is_normal || contains_match { if is_first_normal || is_last || !is_normal || contains_match {
if !longest if longest
.as_ref() .as_ref()
.is_some_and(|old| old.end - old.start > cur.end - cur.start) .is_none_or(|old| old.end - old.start <= cur.end - cur.start)
{ {
longest = Some(cur); longest = Some(cur);
} }
@@ -1461,9 +1462,9 @@ impl<'a> PathComponentSlice<'a> {
cur.end = i + 1; cur.end = i + 1;
} }
} }
if !longest if longest
.as_ref() .as_ref()
.is_some_and(|old| old.end - old.start > cur.end - cur.start) .is_none_or(|old| old.end - old.start <= cur.end - cur.start)
{ {
longest = Some(cur); longest = Some(cur);
} }

View File

@@ -108,7 +108,7 @@ impl Match {
fn styled_text(&self, project: &Project, window: &Window, cx: &App) -> StyledText { fn styled_text(&self, project: &Project, window: &Window, cx: &App) -> StyledText {
let mut text = "./".to_string(); let mut text = "./".to_string();
let mut highlights = Vec::new(); let mut highlights = Vec::new();
let mut offset = text.as_bytes().len(); let mut offset = text.len();
let separator = '/'; let separator = '/';
let dir_indicator = "[…]"; let dir_indicator = "[…]";
@@ -125,7 +125,7 @@ impl Match {
highlights.push((range.start + offset..range.end + offset, style)) highlights.push((range.start + offset..range.end + offset, style))
} }
text.push(separator); text.push(separator);
offset = text.as_bytes().len(); offset = text.len();
if let Some(suffix) = &self.suffix { if let Some(suffix) = &self.suffix {
text.push_str(suffix); text.push_str(suffix);
@@ -140,10 +140,10 @@ impl Match {
Color::Created Color::Created
}; };
highlights.push(( highlights.push((
offset..offset + suffix.as_bytes().len(), offset..offset + suffix.len(),
HighlightStyle::color(color.color(cx)), HighlightStyle::color(color.color(cx)),
)); ));
offset += suffix.as_bytes().len(); offset += suffix.len();
if entry.is_some_and(|e| e.is_dir()) { if entry.is_some_and(|e| e.is_dir()) {
text.push(separator); text.push(separator);
offset += separator.len_utf8(); offset += separator.len_utf8();
@@ -165,7 +165,7 @@ impl Match {
text.push_str(suffix); text.push_str(suffix);
let existing_prefix_len = self let existing_prefix_len = self
.existing_prefix(project, cx) .existing_prefix(project, cx)
.map(|prefix| prefix.to_string_lossy().as_bytes().len()) .map(|prefix| prefix.to_string_lossy().len())
.unwrap_or(0); .unwrap_or(0);
if existing_prefix_len > 0 { if existing_prefix_len > 0 {
@@ -175,14 +175,14 @@ impl Match {
)); ));
} }
highlights.push(( highlights.push((
offset + existing_prefix_len..offset + suffix.as_bytes().len(), offset + existing_prefix_len..offset + suffix.len(),
HighlightStyle::color(if self.entry(project, cx).is_some() { HighlightStyle::color(if self.entry(project, cx).is_some() {
Color::Conflict.color(cx) Color::Conflict.color(cx)
} else { } else {
Color::Created.color(cx) Color::Created.color(cx)
}), }),
)); ));
offset += suffix.as_bytes().len(); offset += suffix.len();
if suffix.ends_with('/') { if suffix.ends_with('/') {
text.push_str(dir_indicator); text.push_str(dir_indicator);
highlights.push(( highlights.push((

View File

@@ -248,6 +248,7 @@ impl From<MTime> for proto::Timestamp {
pub struct RealFs { pub struct RealFs {
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>, git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
git_binary_path: Option<PathBuf>, git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
} }
pub trait FileHandle: Send + Sync + std::fmt::Debug { pub trait FileHandle: Send + Sync + std::fmt::Debug {
@@ -302,10 +303,12 @@ impl RealFs {
pub fn new( pub fn new(
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>, git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
git_binary_path: Option<PathBuf>, git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
) -> Self { ) -> Self {
Self { Self {
git_hosting_provider_registry, git_hosting_provider_registry,
git_binary_path, git_binary_path,
askpass_path,
} }
} }
} }
@@ -769,6 +772,7 @@ impl Fs for RealFs {
Some(Arc::new(RealGitRepository::new( Some(Arc::new(RealGitRepository::new(
repo, repo,
self.git_binary_path.clone(), self.git_binary_path.clone(),
self.askpass_path.to_owned(),
self.git_hosting_provider_registry.clone(), self.git_hosting_provider_registry.clone(),
))) )))
} }
@@ -1261,7 +1265,7 @@ impl FakeFs {
self.with_git_state(dot_git, true, |state| { self.with_git_state(dot_git, true, |state| {
let branch = branch.map(Into::into); let branch = branch.map(Into::into);
state.branches.extend(branch.clone()); state.branches.extend(branch.clone());
state.current_branch_name = branch.map(Into::into) state.current_branch_name = branch
}) })
} }

View File

@@ -140,7 +140,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
let query_char_bag = CharBag::from(&lowercase_query[..]); let query_char_bag = CharBag::from(&lowercase_query[..]);
let num_cpus = executor.num_cpus().min(path_count); let num_cpus = executor.num_cpus().min(path_count);
let segment_size = (path_count + num_cpus - 1) / num_cpus; let segment_size = path_count.div_ceil(num_cpus);
let mut segment_results = (0..num_cpus) let mut segment_results = (0..num_cpus)
.map(|_| Vec::with_capacity(max_results)) .map(|_| Vec::with_capacity(max_results))
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -145,7 +145,7 @@ pub async fn match_strings(
let query_char_bag = CharBag::from(&lowercase_query[..]); let query_char_bag = CharBag::from(&lowercase_query[..]);
let num_cpus = executor.num_cpus().min(candidates.len()); let num_cpus = executor.num_cpus().min(candidates.len());
let segment_size = (candidates.len() + num_cpus - 1) / num_cpus; let segment_size = candidates.len().div_ceil(num_cpus);
let mut segment_results = (0..num_cpus) let mut segment_results = (0..num_cpus)
.map(|_| Vec::with_capacity(max_results.min(candidates.len()))) .map(|_| Vec::with_capacity(max_results.min(candidates.len())))
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -30,6 +30,7 @@ schemars.workspace = true
serde.workspace = true serde.workspace = true
smol.workspace = true smol.workspace = true
sum_tree.workspace = true sum_tree.workspace = true
tempfile.workspace = true
text.workspace = true text.workspace = true
time.workspace = true time.workspace = true
url.workspace = true url.workspace = true

View File

@@ -344,7 +344,7 @@ mod tests {
have_json.push('\n'); have_json.push('\n');
let update = std::env::var("UPDATE_GOLDEN") let update = std::env::var("UPDATE_GOLDEN")
.map(|val| val.to_ascii_lowercase() == "true") .map(|val| val.eq_ignore_ascii_case("true"))
.unwrap_or(false); .unwrap_or(false);
if update { if update {

View File

@@ -10,8 +10,11 @@ use rope::Rope;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::env::temp_dir;
use std::io::Write as _; use std::io::Write as _;
use std::process::Stdio; use std::os::unix::fs::PermissionsExt as _;
use std::os::unix::net::UnixListener;
use std::process::{Command, Stdio};
use std::sync::LazyLock; use std::sync::LazyLock;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
@@ -200,6 +203,7 @@ impl std::fmt::Debug for dyn GitRepository {
pub struct RealGitRepository { pub struct RealGitRepository {
pub repository: Mutex<git2::Repository>, pub repository: Mutex<git2::Repository>,
pub git_binary_path: PathBuf, pub git_binary_path: PathBuf,
pub askpass_path: PathBuf,
hosting_provider_registry: Arc<GitHostingProviderRegistry>, hosting_provider_registry: Arc<GitHostingProviderRegistry>,
} }
@@ -207,11 +211,13 @@ impl RealGitRepository {
pub fn new( pub fn new(
repository: git2::Repository, repository: git2::Repository,
git_binary_path: Option<PathBuf>, git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
hosting_provider_registry: Arc<GitHostingProviderRegistry>, hosting_provider_registry: Arc<GitHostingProviderRegistry>,
) -> Self { ) -> Self {
Self { Self {
repository: Mutex::new(repository), repository: Mutex::new(repository),
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")), git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
askpass_path,
hosting_provider_registry, hosting_provider_registry,
} }
} }
@@ -358,24 +364,30 @@ impl GitRepository for RealGitRepository {
log::debug!("indexing SHA: {sha}, path {path:?}"); log::debug!("indexing SHA: {sha}, path {path:?}");
let status = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["update-index", "--add", "--cacheinfo", "100644", &sha]) .args(["update-index", "--add", "--cacheinfo", "100644", &sha])
.arg(path.as_ref()) .arg(path.as_ref())
.status()?; .output()?;
if !status.success() { if !output.status.success() {
return Err(anyhow!("Failed to add to index: {status:?}")); return Err(anyhow!(
"Failed to stage:\n{}",
String::from_utf8_lossy(&output.stderr)
));
} }
} else { } else {
let status = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["update-index", "--force-remove"]) .args(["update-index", "--force-remove"])
.arg(path.as_ref()) .arg(path.as_ref())
.status()?; .output()?;
if !status.success() { if !output.status.success() {
return Err(anyhow!("Failed to remove from index: {status:?}")); return Err(anyhow!(
"Failed to unstage:\n{}",
String::from_utf8_lossy(&output.stderr)
));
} }
} }
@@ -602,7 +614,10 @@ impl GitRepository for RealGitRepository {
) -> Result<()> { ) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let output = new_std_command(&self.git_binary_path) // We don't use the bundled git, so we can ensure that system
// credential management and transfer mechanisms are respected
let output = new_std_command("git")
.env("GIT_ASKPASS", &self.askpass_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["push", "--quiet"]) .args(["push", "--quiet"])
.args(options.map(|option| match option { .args(options.map(|option| match option {
@@ -618,18 +633,20 @@ impl GitRepository for RealGitRepository {
"Failed to push:\n{}", "Failed to push:\n{}",
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
)); ));
} else {
Ok(())
} }
// TODO: Get remote response out of this and show it to the user
Ok(())
} }
fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> { fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let output = new_std_command(&self.git_binary_path) // We don't use the bundled git, so we can ensure that system
// credential management and transfer mechanisms are respected
let output = new_std_command("git")
.env("GIT_ASKPASS", &self.askpass_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["pull", "--quiet"]) .args(["pull"])
.arg(remote_name) .arg(remote_name)
.arg(branch_name) .arg(branch_name)
.output()?; .output()?;
@@ -639,16 +656,18 @@ impl GitRepository for RealGitRepository {
"Failed to pull:\n{}", "Failed to pull:\n{}",
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
)); ));
} else {
return Ok(());
} }
// TODO: Get remote response out of this and show it to the user
Ok(())
} }
fn fetch(&self) -> Result<()> { fn fetch(&self) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let output = new_std_command(&self.git_binary_path) // We don't use the bundled git, so we can ensure that system
// credential management and transfer mechanisms are respected
let output = new_std_command("git")
.env("GIT_ASKPASS", &self.askpass_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["fetch", "--quiet", "--all"]) .args(["fetch", "--quiet", "--all"])
.output()?; .output()?;
@@ -658,10 +677,9 @@ impl GitRepository for RealGitRepository {
"Failed to fetch:\n{}", "Failed to fetch:\n{}",
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
)); ));
} else {
return Ok(());
} }
// TODO: Get remote response out of this and show it to the user
Ok(())
} }
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>> { fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>> {
@@ -1016,7 +1034,7 @@ impl Borrow<Path> for RepoPath {
#[derive(Debug)] #[derive(Debug)]
pub struct RepoPathDescendants<'a>(pub &'a Path); pub struct RepoPathDescendants<'a>(pub &'a Path);
impl<'a> MapSeekTarget<RepoPath> for RepoPathDescendants<'a> { impl MapSeekTarget<RepoPath> for RepoPathDescendants<'_> {
fn cmp_cursor(&self, key: &RepoPath) -> Ordering { fn cmp_cursor(&self, key: &RepoPath) -> Ordering {
if key.starts_with(self.0) { if key.starts_with(self.0) {
Ordering::Greater Ordering::Greater

View File

@@ -12,6 +12,10 @@ workspace = true
name = "git_ui" name = "git_ui"
path = "src/git_ui.rs" path = "src/git_ui.rs"
[features]
default = []
test-support = ["multi_buffer/test-support"]
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
buffer_diff.workspace = true buffer_diff.workspace = true
@@ -47,6 +51,10 @@ zed_actions.workspace = true
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows.workspace = true windows.workspace = true
[features] [dev-dependencies]
default = [] editor = { workspace = true, features = ["test-support"] }
test-support = ["multi_buffer/test-support"] gpui = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] }
settings = { workspace = true, features = ["test-support"] }
unindent.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -1,16 +1,18 @@
use anyhow::{anyhow, Context as _, Result}; use anyhow::{Context as _, Result};
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use git::repository::Branch; use git::repository::Branch;
use gpui::{ use gpui::{
rems, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, rems, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
Task, WeakEntity, Window, Task, Window,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::ProjectPath; use project::{Project, ProjectPath};
use std::sync::Arc; use std::sync::Arc;
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing}; use ui::{
prelude::*, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenuHandle, TriggerablePopover,
};
use util::ResultExt; use util::ResultExt;
use workspace::notifications::DetachAndPromptErr; use workspace::notifications::DetachAndPromptErr;
use workspace::{ModalView, Workspace}; use workspace::{ModalView, Workspace};
@@ -23,19 +25,29 @@ pub fn init(cx: &mut App) {
} }
pub fn open( pub fn open(
_: &mut Workspace, workspace: &mut Workspace,
_: &zed_actions::git::Branch, _: &zed_actions::git::Branch,
window: &mut Window, window: &mut Window,
cx: &mut Context<Workspace>, cx: &mut Context<Workspace>,
) { ) {
let this = cx.entity().clone(); let project = workspace.project().clone();
let this = cx.entity();
let style = BranchListStyle::Modal;
cx.spawn_in(window, |_, mut cx| async move { cx.spawn_in(window, |_, mut cx| async move {
// Modal branch picker has a longer trailoff than a popover one. // Modal branch picker has a longer trailoff than a popover one.
let delegate = BranchListDelegate::new(this.clone(), 70, &cx).await?; let delegate = BranchListDelegate::new(project.clone(), style, 70, &cx).await?;
this.update_in(&mut cx, |workspace, window, cx| { this.update_in(&mut cx, move |workspace, window, cx| {
workspace.toggle_modal(window, cx, |window, cx| { workspace.toggle_modal(window, cx, |window, cx| {
BranchList::new(delegate, 34., window, cx) let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
cx.emit(DismissEvent);
});
let mut list = BranchList::new(project, style, 34., cx);
list._subscription = Some(_subscription);
list.picker = Some(picker);
list
}) })
})?; })?;
@@ -44,34 +56,86 @@ pub fn open(
.detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None) .detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None)
} }
pub fn popover(project: Entity<Project>, window: &mut Window, cx: &mut App) -> Entity<BranchList> {
cx.new(|cx| {
let mut list = BranchList::new(project, BranchListStyle::Popover, 15., cx);
list.reload_branches(window, cx);
list
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum BranchListStyle {
Modal,
Popover,
}
pub struct BranchList { pub struct BranchList {
pub picker: Entity<Picker<BranchListDelegate>>,
rem_width: f32, rem_width: f32,
_subscription: Subscription, popover_handle: PopoverMenuHandle<Self>,
default_focus_handle: FocusHandle,
project: Entity<Project>,
style: BranchListStyle,
pub picker: Option<Entity<Picker<BranchListDelegate>>>,
_subscription: Option<Subscription>,
}
impl TriggerablePopover for BranchList {
fn menu_handle(
&mut self,
_window: &mut Window,
_cx: &mut gpui::Context<Self>,
) -> PopoverMenuHandle<Self> {
self.popover_handle.clone()
}
} }
impl BranchList { impl BranchList {
pub fn new( fn new(project: Entity<Project>, style: BranchListStyle, rem_width: f32, cx: &mut App) -> Self {
delegate: BranchListDelegate, let popover_handle = PopoverMenuHandle::default();
rem_width: f32,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
Self { Self {
picker, project,
picker: None,
rem_width, rem_width,
_subscription, popover_handle,
default_focus_handle: cx.focus_handle(),
style,
_subscription: None,
} }
} }
fn reload_branches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let project = self.project.clone();
let style = self.style;
cx.spawn_in(window, |this, mut cx| async move {
let delegate = BranchListDelegate::new(project, style, 20, &cx).await?;
let picker =
cx.new_window_entity(|window, cx| Picker::uniform_list(delegate, window, cx))?;
this.update(&mut cx, |branch_list, cx| {
let subscription =
cx.subscribe(&picker, |_, _, _: &DismissEvent, cx| cx.emit(DismissEvent));
branch_list.picker = Some(picker);
branch_list._subscription = Some(subscription);
cx.notify();
})?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
} }
impl ModalView for BranchList {} impl ModalView for BranchList {}
impl EventEmitter<DismissEvent> for BranchList {} impl EventEmitter<DismissEvent> for BranchList {}
impl Focusable for BranchList { impl Focusable for BranchList {
fn focus_handle(&self, cx: &App) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx) self.picker
.as_ref()
.map(|picker| picker.focus_handle(cx))
.unwrap_or_else(|| self.default_focus_handle.clone())
} }
} }
@@ -79,12 +143,25 @@ impl Render for BranchList {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.w(rems(self.rem_width)) .w(rems(self.rem_width))
.child(self.picker.clone()) .map(|parent| match self.picker.as_ref() {
.on_mouse_down_out(cx.listener(|this, _, window, cx| { Some(picker) => parent.child(picker.clone()).on_mouse_down_out({
this.picker.update(cx, |this, cx| { let picker = picker.clone();
this.cancel(&Default::default(), window, cx); cx.listener(move |_, _, window, cx| {
}) picker.update(cx, |this, cx| {
})) this.cancel(&Default::default(), window, cx);
})
})
}),
None => parent.child(
h_flex()
.id("branch-picker-error")
.on_click(
cx.listener(|this, _, window, cx| this.reload_branches(window, cx)),
)
.child("Could not load branches.")
.child("Click to retry"),
),
})
} }
} }
@@ -108,7 +185,8 @@ impl BranchEntry {
pub struct BranchListDelegate { pub struct BranchListDelegate {
matches: Vec<BranchEntry>, matches: Vec<BranchEntry>,
all_branches: Vec<Branch>, all_branches: Vec<Branch>,
workspace: WeakEntity<Workspace>, project: Entity<Project>,
style: BranchListStyle,
selected_index: usize, selected_index: usize,
last_query: String, last_query: String,
/// Max length of branch name before we truncate it and add a trailing `...`. /// Max length of branch name before we truncate it and add a trailing `...`.
@@ -116,13 +194,14 @@ pub struct BranchListDelegate {
} }
impl BranchListDelegate { impl BranchListDelegate {
pub async fn new( async fn new(
workspace: Entity<Workspace>, project: Entity<Project>,
style: BranchListStyle,
branch_name_trailoff_after: usize, branch_name_trailoff_after: usize,
cx: &AsyncApp, cx: &AsyncApp,
) -> Result<Self> { ) -> Result<Self> {
let all_branches_request = cx.update(|cx| { let all_branches_request = cx.update(|cx| {
let project = workspace.read(cx).project().read(cx); let project = project.read(cx);
let first_worktree = project let first_worktree = project
.visible_worktrees(cx) .visible_worktrees(cx)
.next() .next()
@@ -135,7 +214,8 @@ impl BranchListDelegate {
Ok(Self { Ok(Self {
matches: vec![], matches: vec![],
workspace: workspace.downgrade(), project,
style,
all_branches, all_branches,
selected_index: 0, selected_index: 0,
last_query: Default::default(), last_query: Default::default(),
@@ -254,18 +334,12 @@ impl PickerDelegate for BranchListDelegate {
return; return;
}; };
let current_branch = self let current_branch = self.project.update(cx, |project, cx| {
.workspace project
.update(cx, |workspace, cx| { .active_repository(cx)
workspace .and_then(|repo| repo.read(cx).current_branch())
.project() .map(|branch| branch.name.to_string())
.read(cx) });
.active_repository(cx)
.and_then(|repo| repo.read(cx).current_branch())
.map(|branch| branch.name.to_string())
})
.ok()
.flatten();
if current_branch == Some(branch.name().to_string()) { if current_branch == Some(branch.name().to_string()) {
cx.emit(DismissEvent); cx.emit(DismissEvent);
@@ -276,13 +350,7 @@ impl PickerDelegate for BranchListDelegate {
let branch = branch.clone(); let branch = branch.clone();
|picker, mut cx| async move { |picker, mut cx| async move {
let branch_change_task = picker.update(&mut cx, |this, cx| { let branch_change_task = picker.update(&mut cx, |this, cx| {
let workspace = this let project = this.delegate.project.read(cx);
.delegate
.workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was dropped"))?;
let project = workspace.read(cx).project().read(cx);
let branch_to_checkout = match branch { let branch_to_checkout = match branch {
BranchEntry::Branch(branch) => branch.string, BranchEntry::Branch(branch) => branch.string,
BranchEntry::History(string) => string, BranchEntry::History(string) => string,
@@ -327,6 +395,10 @@ impl PickerDelegate for BranchListDelegate {
Some( Some(
ListItem::new(SharedString::from(format!("vcs-menu-{ix}"))) ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
.inset(true) .inset(true)
.spacing(match self.style {
BranchListStyle::Modal => ListItemSpacing::default(),
BranchListStyle::Popover => ListItemSpacing::ExtraDense,
})
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.toggle_state(selected) .toggle_state(selected)
.when(matches!(hit, BranchEntry::History(_)), |el| { .when(matches!(hit, BranchEntry::History(_)), |el| {

View File

@@ -1,9 +1,11 @@
// #![allow(unused, dead_code)] // #![allow(unused, dead_code)]
use crate::branch_picker::{self, BranchList};
use crate::git_panel::{commit_message_editor, GitPanel}; use crate::git_panel::{commit_message_editor, GitPanel};
use git::Commit; use git::Commit;
use panel::{panel_button, panel_editor_style, panel_filled_button}; use panel::{panel_button, panel_editor_style, panel_filled_button};
use ui::{prelude::*, KeybindingHint, Tooltip}; use project::Project;
use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover};
use editor::{Editor, EditorElement}; use editor::{Editor, EditorElement};
use gpui::*; use gpui::*;
@@ -64,6 +66,7 @@ pub fn init(cx: &mut App) {
} }
pub struct CommitModal { pub struct CommitModal {
branch_list: Entity<BranchList>,
git_panel: Entity<GitPanel>, git_panel: Entity<GitPanel>,
commit_editor: Entity<Editor>, commit_editor: Entity<Editor>,
restore_dock: RestoreDock, restore_dock: RestoreDock,
@@ -139,9 +142,11 @@ impl CommitModal {
is_open, is_open,
active_index, active_index,
}; };
let project = workspace.project().clone();
workspace.open_panel::<GitPanel>(window, cx); workspace.open_panel::<GitPanel>(window, cx);
workspace.toggle_modal(window, cx, move |window, cx| { workspace.toggle_modal(window, cx, move |window, cx| {
CommitModal::new(git_panel, restore_dock_position, window, cx) CommitModal::new(git_panel, restore_dock_position, project, window, cx)
}) })
}); });
} }
@@ -149,6 +154,7 @@ impl CommitModal {
fn new( fn new(
git_panel: Entity<GitPanel>, git_panel: Entity<GitPanel>,
restore_dock: RestoreDock, restore_dock: RestoreDock,
project: Entity<Project>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@@ -159,7 +165,7 @@ impl CommitModal {
git_panel.set_modal_open(true, cx); git_panel.set_modal_open(true, cx);
let buffer = git_panel.commit_message_buffer(cx).clone(); let buffer = git_panel.commit_message_buffer(cx).clone();
let project = git_panel.project.clone(); let project = git_panel.project.clone();
cx.new(|cx| commit_message_editor(buffer, project.clone(), false, window, cx)) cx.new(|cx| commit_message_editor(buffer, None, project.clone(), false, window, cx))
}); });
let commit_message = commit_editor.read(cx).text(cx); let commit_message = commit_editor.read(cx).text(cx);
@@ -182,14 +188,21 @@ impl CommitModal {
let focus_handle = commit_editor.focus_handle(cx); let focus_handle = commit_editor.focus_handle(cx);
cx.on_focus_out(&focus_handle, window, |_, _, _, cx| { cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
cx.emit(DismissEvent); if !this
.branch_list
.focus_handle(cx)
.contains_focused(window, cx)
{
cx.emit(DismissEvent);
}
}) })
.detach(); .detach();
let properties = ModalContainerProperties::new(window, 50); let properties = ModalContainerProperties::new(window, 50);
Self { Self {
branch_list: branch_picker::popover(project.clone(), window, cx),
git_panel, git_panel,
commit_editor, commit_editor,
restore_dock, restore_dock,
@@ -230,7 +243,7 @@ impl CommitModal {
) )
} }
fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let git_panel = self.git_panel.clone(); let git_panel = self.git_panel.clone();
let (branch, tooltip, commit_label, co_authors) = let (branch, tooltip, commit_label, co_authors) =
@@ -238,7 +251,12 @@ impl CommitModal {
let branch = git_panel let branch = git_panel
.active_repository .active_repository
.as_ref() .as_ref()
.and_then(|repo| repo.read(cx).current_branch().map(|b| b.name.clone())) .and_then(|repo| {
repo.read(cx)
.repository_entry
.branch()
.map(|b| b.name.clone())
})
.unwrap_or_else(|| "<no branch>".into()); .unwrap_or_else(|| "<no branch>".into());
let tooltip = if git_panel.has_staged_changes() { let tooltip = if git_panel.has_staged_changes() {
"Commit staged changes" "Commit staged changes"
@@ -248,13 +266,13 @@ impl CommitModal {
let title = if git_panel.has_staged_changes() { let title = if git_panel.has_staged_changes() {
"Commit" "Commit"
} else { } else {
"Commit Tracked" "Commit All"
}; };
let co_authors = git_panel.render_co_authors(cx); let co_authors = git_panel.render_co_authors(cx);
(branch, tooltip, title, co_authors) (branch, tooltip, title, co_authors)
}); });
let branch_selector = panel_button(branch) let branch_picker_button = panel_button(branch)
.icon(IconName::GitBranch) .icon(IconName::GitBranch)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Placeholder) .icon_color(Color::Placeholder)
@@ -269,6 +287,13 @@ impl CommitModal {
})) }))
.style(ButtonStyle::Transparent); .style(ButtonStyle::Transparent);
let branch_picker = PopoverButton::new(
self.branch_list.clone(),
Corner::BottomLeft,
branch_picker_button,
Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch),
);
let close_kb_hint = let close_kb_hint =
if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) { if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) {
Some( Some(
@@ -303,7 +328,12 @@ impl CommitModal {
.w_full() .w_full()
.h(px(self.properties.footer_height)) .h(px(self.properties.footer_height))
.gap_1() .gap_1()
.child(h_flex().gap_1().child(branch_selector).children(co_authors)) .child(
h_flex()
.gap_1()
.child(branch_picker.render(window, cx))
.children(co_authors),
)
.child(div().flex_1()) .child(div().flex_1())
.child( .child(
h_flex() h_flex()
@@ -340,6 +370,13 @@ impl Render for CommitModal {
.key_context("GitCommit") .key_context("GitCommit")
.on_action(cx.listener(Self::dismiss)) .on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::commit)) .on_action(cx.listener(Self::commit))
.on_action(
cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
this.branch_list.update(cx, |branch_list, cx| {
branch_list.menu_handle(window, cx).toggle(window, cx);
})
}),
)
.elevation_3(cx) .elevation_3(cx)
.overflow_hidden() .overflow_hidden()
.flex_none() .flex_none()

View File

@@ -170,7 +170,7 @@ pub struct GitPanel {
pending_remote_operations: RemoteOperations, pending_remote_operations: RemoteOperations,
pub(crate) active_repository: Option<Entity<Repository>>, pub(crate) active_repository: Option<Entity<Repository>>,
commit_editor: Entity<Editor>, commit_editor: Entity<Editor>,
suggested_commit_message: Option<String>, pub(crate) suggested_commit_message: Option<String>,
conflicted_count: usize, conflicted_count: usize,
conflicted_staged_count: usize, conflicted_staged_count: usize,
current_modifiers: Modifiers, current_modifiers: Modifiers,
@@ -212,6 +212,7 @@ impl Drop for RemoteOperationGuard {
pub(crate) fn commit_message_editor( pub(crate) fn commit_message_editor(
commit_message_buffer: Entity<Buffer>, commit_message_buffer: Entity<Buffer>,
placeholder: Option<&str>,
project: Entity<Project>, project: Entity<Project>,
in_panel: bool, in_panel: bool,
window: &mut Window, window: &mut Window,
@@ -232,7 +233,8 @@ pub(crate) fn commit_message_editor(
commit_editor.set_show_gutter(false, cx); commit_editor.set_show_gutter(false, cx);
commit_editor.set_show_wrap_guides(false, cx); commit_editor.set_show_wrap_guides(false, cx);
commit_editor.set_show_indent_guides(false, cx); commit_editor.set_show_indent_guides(false, cx);
commit_editor.set_placeholder_text("Enter commit message", cx); let placeholder = placeholder.unwrap_or("Enter commit message");
commit_editor.set_placeholder_text(placeholder, cx);
commit_editor commit_editor
} }
@@ -260,7 +262,7 @@ impl GitPanel {
// Once the active git repo is set, this buffer will be replaced. // Once the active git repo is set, this buffer will be replaced.
let temporary_buffer = cx.new(|cx| Buffer::local("", cx)); let temporary_buffer = cx.new(|cx| Buffer::local("", cx));
let commit_editor = cx.new(|cx| { let commit_editor = cx.new(|cx| {
commit_message_editor(temporary_buffer, project.clone(), true, window, cx) commit_message_editor(temporary_buffer, None, project.clone(), true, window, cx)
}); });
commit_editor.update(cx, |editor, cx| { commit_editor.update(cx, |editor, cx| {
editor.clear(window, cx); editor.clear(window, cx);
@@ -551,7 +553,7 @@ impl GitPanel {
} }
fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) { fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) {
if self.entries.first().is_some() { if !self.entries.is_empty() {
self.selected_entry = Some(1); self.selected_entry = Some(1);
self.scroll_to_selected_entry(cx); self.scroll_to_selected_entry(cx);
} }
@@ -693,7 +695,7 @@ impl GitPanel {
self.workspace self.workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
workspace workspace
.open_path_preview(path, None, false, false, window, cx) .open_path_preview(path, None, false, false, true, window, cx)
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| { .detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
Some(format!("{e}")) Some(format!("{e}"))
}); });
@@ -840,7 +842,7 @@ impl GitPanel {
.detach(); .detach();
} }
fn discard_tracked_changes( fn restore_tracked_files(
&mut self, &mut self,
_: &RestoreTrackedFiles, _: &RestoreTrackedFiles,
window: &mut Window, window: &mut Window,
@@ -870,8 +872,8 @@ impl GitPanel {
#[derive(strum::EnumIter, strum::VariantNames)] #[derive(strum::EnumIter, strum::VariantNames)]
#[strum(serialize_all = "title_case")] #[strum(serialize_all = "title_case")]
enum DiscardCancel { enum RestoreCancel {
DiscardTrackedChanges, RestoreTrackedFiles,
Cancel, Cancel,
} }
let prompt = prompt( let prompt = prompt(
@@ -882,7 +884,7 @@ impl GitPanel {
); );
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
match prompt.await { match prompt.await {
Ok(DiscardCancel::DiscardTrackedChanges) => { Ok(RestoreCancel::RestoreTrackedFiles) => {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
let repo_paths = entries.into_iter().map(|entry| entry.repo_path).collect(); let repo_paths = entries.into_iter().map(|entry| entry.repo_path).collect();
this.perform_checkout(repo_paths, cx); this.perform_checkout(repo_paths, cx);
@@ -1255,51 +1257,50 @@ impl GitPanel {
/// Suggests a commit message based on the changed files and their statuses /// Suggests a commit message based on the changed files and their statuses
pub fn suggest_commit_message(&self) -> Option<String> { pub fn suggest_commit_message(&self) -> Option<String> {
let entries = self if self.total_staged_count() != 1 {
return None;
}
let entry = self
.entries .entries
.iter() .iter()
.filter_map(|entry| { .find(|entry| match entry.status_entry() {
if let GitListEntry::GitStatusEntry(status_entry) = entry { Some(entry) => entry.is_staged.unwrap_or(false),
Some(status_entry) _ => false,
} else { })?;
None
}
})
.collect::<Vec<&GitStatusEntry>>();
if entries.is_empty() { let GitListEntry::GitStatusEntry(git_status_entry) = entry.clone() else {
None return None;
} else if entries.len() == 1 { };
let entry = &entries[0];
let file_name = entry
.repo_path
.file_name()
.unwrap_or_default()
.to_string_lossy();
if entry.status.is_deleted() { let action_text = if git_status_entry.status.is_deleted() {
Some(format!("Delete {}", file_name)) Some("Delete")
} else if entry.status.is_created() { } else if git_status_entry.status.is_created() {
Some(format!("Create {}", file_name)) Some("Create")
} else if entry.status.is_modified() { } else if git_status_entry.status.is_modified() {
Some(format!("Update {}", file_name)) Some("Update")
} else {
None
}
} else { } else {
None None
} };
let file_name = git_status_entry
.repo_path
.file_name()
.unwrap_or_default()
.to_string_lossy();
Some(format!("{} {}", action_text?, file_name))
} }
fn update_editor_placeholder(&mut self, cx: &mut Context<Self>) { fn update_editor_placeholder(&mut self, cx: &mut Context<Self>) {
let suggested_commit_message = self.suggest_commit_message(); let suggested_commit_message = self.suggest_commit_message();
self.suggested_commit_message = suggested_commit_message.clone(); let suggested_commit_message = suggested_commit_message
.as_deref()
.unwrap_or("Enter commit message");
if let Some(suggested_commit_message) = suggested_commit_message { self.commit_editor.update(cx, |editor, cx| {
self.commit_editor.update(cx, |editor, cx| { editor.set_placeholder_text(Arc::from(suggested_commit_message), cx)
editor.set_placeholder_text(Arc::from(suggested_commit_message), cx) });
});
}
cx.notify(); cx.notify();
} }
@@ -1386,14 +1387,14 @@ impl GitPanel {
}; };
let mut current_remotes: Vec<Remote> = repo let mut current_remotes: Vec<Remote> = repo
.update(&mut cx, |repo, cx| { .update(&mut cx, |repo, _| {
let Some(current_branch) = repo.current_branch() else { let Some(current_branch) = repo.current_branch() else {
return Err(anyhow::anyhow!("No active branch")); return Err(anyhow::anyhow!("No active branch"));
}; };
Ok(repo.get_remotes(Some(current_branch.name.to_string()), cx)) Ok(repo.get_remotes(Some(current_branch.name.to_string())))
})?? })??
.await?; .await??;
if current_remotes.len() == 0 { if current_remotes.len() == 0 {
return Err(anyhow::anyhow!("No active remote")); return Err(anyhow::anyhow!("No active remote"));
@@ -1581,7 +1582,14 @@ impl GitPanel {
!= Some(&buffer) != Some(&buffer)
{ {
git_panel.commit_editor = cx.new(|cx| { git_panel.commit_editor = cx.new(|cx| {
commit_message_editor(buffer, git_panel.project.clone(), true, window, cx) commit_message_editor(
buffer,
git_panel.suggested_commit_message.as_deref(),
git_panel.project.clone(),
true,
window,
cx,
)
}); });
} }
}) })
@@ -2357,7 +2365,9 @@ impl GitPanel {
let Some(repo) = self.active_repository.clone() else { let Some(repo) = self.active_repository.clone() else {
return Task::ready(Err(anyhow::anyhow!("no active repo"))); return Task::ready(Err(anyhow::anyhow!("no active repo")));
}; };
repo.update(cx, |repo, cx| repo.show(sha, cx))
let show = repo.read(cx).show(sha);
cx.spawn(|_, _| async move { show.await? })
} }
fn deploy_entry_context_menu( fn deploy_entry_context_menu(
@@ -2375,17 +2385,15 @@ impl GitPanel {
} else { } else {
"Stage File" "Stage File"
}; };
let revert_title = if entry.status.is_deleted() { let restore_title = if entry.status.is_created() {
"Restore file" "Trash File"
} else if entry.status.is_created() {
"Trash file"
} else { } else {
"Discard changes" "Restore File"
}; };
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| { let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu context_menu
.action(stage_title, ToggleStaged.boxed_clone()) .action(stage_title, ToggleStaged.boxed_clone())
.action(revert_title, git::RestoreFile.boxed_clone()) .action(restore_title, git::RestoreFile.boxed_clone())
.separator() .separator()
.action("Open Diff", Confirm.boxed_clone()) .action("Open Diff", Confirm.boxed_clone())
.action("Open File", SecondaryConfirm.boxed_clone()) .action("Open File", SecondaryConfirm.boxed_clone())
@@ -2402,7 +2410,7 @@ impl GitPanel {
.separator() .separator()
.action("Open Diff", project_diff::Diff.boxed_clone()) .action("Open Diff", project_diff::Diff.boxed_clone())
.separator() .separator()
.action("Discard Tracked Changes", RestoreTrackedFiles.boxed_clone()) .action("Restore Tracked Files", RestoreTrackedFiles.boxed_clone())
.action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone()) .action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone())
}) })
} }
@@ -2486,6 +2494,7 @@ impl GitPanel {
let id: ElementId = ElementId::Name(format!("entry_{}", display_name).into()); let id: ElementId = ElementId::Name(format!("entry_{}", display_name).into());
let is_entry_staged = self.entry_is_staged(entry);
let mut is_staged: ToggleState = self.entry_is_staged(entry).into(); let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
if !self.has_staged_changes() && !self.has_conflicts() && !entry.status.is_created() { if !self.has_staged_changes() && !self.has_conflicts() && !entry.status.is_created() {
@@ -2509,18 +2518,23 @@ impl GitPanel {
}) })
}); });
let start_slot = let start_slot = h_flex()
h_flex() .id(("start-slot", ix))
.id(("start-slot", ix)) .gap(DynamicSpacing::Base04.rems(cx))
.gap(DynamicSpacing::Base04.rems(cx)) .child(checkbox.tooltip(move |window, cx| {
.child(checkbox.tooltip(|window, cx| { let tooltip_name = if is_entry_staged.unwrap_or(false) {
Tooltip::for_action("Stage File", &ToggleStaged, window, cx) "Unstage"
})) } else {
.child(git_status_icon(status, cx)) "Stage"
.on_mouse_down(MouseButton::Left, |_, _, cx| { };
// prevent the list item active state triggering when toggling checkbox
cx.stop_propagation(); Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx)
}); }))
.child(git_status_icon(status, cx))
.on_mouse_down(MouseButton::Left, |_, _, cx| {
// prevent the list item active state triggering when toggling checkbox
cx.stop_propagation();
});
div() div()
.w_full() .w_full()
@@ -2680,7 +2694,7 @@ impl Render for GitPanel {
.on_action(cx.listener(Self::toggle_staged_for_selected)) .on_action(cx.listener(Self::toggle_staged_for_selected))
.on_action(cx.listener(Self::stage_all)) .on_action(cx.listener(Self::stage_all))
.on_action(cx.listener(Self::unstage_all)) .on_action(cx.listener(Self::unstage_all))
.on_action(cx.listener(Self::discard_tracked_changes)) .on_action(cx.listener(Self::restore_tracked_files))
.on_action(cx.listener(Self::clean_all)) .on_action(cx.listener(Self::clean_all))
.on_action(cx.listener(Self::fetch)) .on_action(cx.listener(Self::fetch))
.on_action(cx.listener(Self::pull)) .on_action(cx.listener(Self::pull))

View File

@@ -47,6 +47,7 @@ pub struct ProjectDiff {
_subscription: Subscription, _subscription: Subscription,
} }
#[derive(Debug)]
struct DiffBuffer { struct DiffBuffer {
path_key: PathKey, path_key: PathKey,
buffer: Entity<Buffer>, buffer: Entity<Buffer>,
@@ -105,7 +106,7 @@ impl ProjectDiff {
}; };
if let Some(entry) = entry { if let Some(entry) = entry {
project_diff.update(cx, |project_diff, cx| { project_diff.update(cx, |project_diff, cx| {
project_diff.scroll_to(entry, window, cx); project_diff.move_to_entry(entry, window, cx);
}) })
} }
} }
@@ -167,7 +168,7 @@ impl ProjectDiff {
} }
} }
pub fn scroll_to( pub fn move_to_entry(
&mut self, &mut self,
entry: GitStatusEntry, entry: GitStatusEntry,
window: &mut Window, window: &mut Window,
@@ -188,16 +189,16 @@ impl ProjectDiff {
let path_key = PathKey::namespaced(namespace, entry.repo_path.0.clone()); let path_key = PathKey::namespaced(namespace, entry.repo_path.0.clone());
self.scroll_to_path(path_key, window, cx) self.move_to_path(path_key, window, cx)
} }
fn scroll_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) { fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) { if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
s.select_ranges([position..position]); s.select_ranges([position..position]);
}) })
}) });
} else { } else {
self.pending_scroll = Some(path_key); self.pending_scroll = Some(path_key);
} }
@@ -280,6 +281,7 @@ impl ProjectDiff {
let snapshot = multibuffer.snapshot(cx); let snapshot = multibuffer.snapshot(cx);
let mut point = anchor.to_point(&snapshot); let mut point = anchor.to_point(&snapshot);
point.row = (point.row + 1).min(snapshot.max_row().0); point.row = (point.row + 1).min(snapshot.max_row().0);
point.column = 0;
let Some((_, buffer, _)) = self.multibuffer.read(cx).excerpt_containing(point, cx) let Some((_, buffer, _)) = self.multibuffer.read(cx).excerpt_containing(point, cx)
else { else {
@@ -384,21 +386,28 @@ impl ProjectDiff {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
let is_excerpt_newly_added = self.multibuffer.update(cx, |multibuffer, cx| { let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_excerpts_for_path( let was_empty = multibuffer.is_empty();
let is_newly_added = multibuffer.set_excerpts_for_path(
path_key.clone(), path_key.clone(),
buffer, buffer,
diff_hunk_ranges, diff_hunk_ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT, editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx, cx,
) );
(was_empty, is_newly_added)
}); });
if is_excerpt_newly_added && diff_buffer.file_status.is_deleted() { self.editor.update(cx, |editor, cx| {
self.editor.update(cx, |editor, cx| { if was_empty {
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges([0..0])
});
}
if is_excerpt_newly_added && diff_buffer.file_status.is_deleted() {
editor.fold_buffer(snapshot.text.remote_id(), cx) editor.fold_buffer(snapshot.text.remote_id(), cx)
}); }
} });
if self.multibuffer.read(cx).is_empty() if self.multibuffer.read(cx).is_empty()
&& self && self
@@ -414,7 +423,7 @@ impl ProjectDiff {
}); });
} }
if self.pending_scroll.as_ref() == Some(&path_key) { if self.pending_scroll.as_ref() == Some(&path_key) {
self.scroll_to_path(path_key, window, cx); self.move_to_path(path_key, window, cx);
} }
} }
@@ -923,3 +932,189 @@ impl Render for ProjectDiffToolbar {
) )
} }
} }
#[cfg(test)]
mod tests {
use std::path::Path;
use collections::HashMap;
use editor::test::editor_test_context::assert_state_with_diff;
use git::status::{StatusCode, TrackedStatus};
use gpui::TestAppContext;
use project::FakeFs;
use serde_json::json;
use settings::SettingsStore;
use unindent::Unindent as _;
use util::path;
use super::*;
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
editor::init(cx);
crate::init(cx);
});
}
#[gpui::test]
async fn test_save_after_restore(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"foo": "FOO\n",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let diff = cx.new_window_entity(|window, cx| {
ProjectDiff::new(project.clone(), workspace, window, cx)
});
cx.run_until_parked();
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[("foo".into(), "foo\n".into())],
);
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
state.statuses = HashMap::from_iter([(
"foo".into(),
TrackedStatus {
index_status: StatusCode::Unmodified,
worktree_status: StatusCode::Modified,
}
.into(),
)]);
});
cx.run_until_parked();
let editor = diff.update(cx, |diff, _| diff.editor.clone());
assert_state_with_diff(
&editor,
cx,
&"
- foo
+ ˇFOO
"
.unindent(),
);
editor.update_in(cx, |editor, window, cx| {
editor.git_restore(&Default::default(), window, cx);
});
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
state.statuses = HashMap::default();
});
cx.run_until_parked();
assert_state_with_diff(&editor, cx, &"ˇ".unindent());
let text = String::from_utf8(fs.read_file_sync("/project/foo").unwrap()).unwrap();
assert_eq!(text, "foo\n");
}
#[gpui::test]
async fn test_scroll_to_beginning_with_deletion(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"bar": "BAR\n",
"foo": "FOO\n",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let diff = cx.new_window_entity(|window, cx| {
ProjectDiff::new(project.clone(), workspace, window, cx)
});
cx.run_until_parked();
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[
("bar".into(), "bar\n".into()),
("foo".into(), "foo\n".into()),
],
);
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
state.statuses = HashMap::from_iter([
(
"bar".into(),
TrackedStatus {
index_status: StatusCode::Unmodified,
worktree_status: StatusCode::Modified,
}
.into(),
),
(
"foo".into(),
TrackedStatus {
index_status: StatusCode::Unmodified,
worktree_status: StatusCode::Modified,
}
.into(),
),
]);
});
cx.run_until_parked();
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
diff.move_to_path(
PathKey::namespaced(TRACKED_NAMESPACE, Path::new("foo").into()),
window,
cx,
);
diff.editor.clone()
});
assert_state_with_diff(
&editor,
cx,
&"
- bar
+ BAR
- ˇfoo
+ FOO
"
.unindent(),
);
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
diff.move_to_path(
PathKey::namespaced(TRACKED_NAMESPACE, Path::new("bar").into()),
window,
cx,
);
diff.editor.clone()
});
assert_state_with_diff(
&editor,
cx,
&"
- ˇbar
+ BAR
- foo
+ FOO
"
.unindent(),
);
}
}

View File

@@ -82,7 +82,7 @@ impl AppCell {
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct AppRef<'a>(Ref<'a, App>); pub struct AppRef<'a>(Ref<'a, App>);
impl<'a> Drop for AppRef<'a> { impl Drop for AppRef<'_> {
fn drop(&mut self) { fn drop(&mut self) {
if option_env!("TRACK_THREAD_BORROWS").is_some() { if option_env!("TRACK_THREAD_BORROWS").is_some() {
let thread_id = std::thread::current().id(); let thread_id = std::thread::current().id();
@@ -95,7 +95,7 @@ impl<'a> Drop for AppRef<'a> {
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct AppRefMut<'a>(RefMut<'a, App>); pub struct AppRefMut<'a>(RefMut<'a, App>);
impl<'a> Drop for AppRefMut<'a> { impl Drop for AppRefMut<'_> {
fn drop(&mut self) { fn drop(&mut self) {
if option_env!("TRACK_THREAD_BORROWS").is_some() { if option_env!("TRACK_THREAD_BORROWS").is_some() {
let thread_id = std::thread::current().id(); let thread_id = std::thread::current().id();

View File

@@ -649,7 +649,7 @@ impl<'a, T: 'static> Context<'a, T> {
} }
} }
impl<'a, T> Context<'a, T> { impl<T> Context<'_, T> {
/// Emit an event of the specified type, which can be handled by other entities that have subscribed via `subscribe` methods on their respective contexts. /// Emit an event of the specified type, which can be handled by other entities that have subscribed via `subscribe` methods on their respective contexts.
pub fn emit<Evt>(&mut self, event: Evt) pub fn emit<Evt>(&mut self, event: Evt)
where where
@@ -664,7 +664,7 @@ impl<'a, T> Context<'a, T> {
} }
} }
impl<'a, T> AppContext for Context<'a, T> { impl<T> AppContext for Context<'_, T> {
type Result<U> = U; type Result<U> = U;
fn new<U: 'static>( fn new<U: 'static>(

View File

@@ -191,7 +191,7 @@ pub(crate) struct Lease<'a, T> {
entity_type: PhantomData<T>, entity_type: PhantomData<T>,
} }
impl<'a, T: 'static> core::ops::Deref for Lease<'a, T> { impl<T: 'static> core::ops::Deref for Lease<'_, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -199,13 +199,13 @@ impl<'a, T: 'static> core::ops::Deref for Lease<'a, T> {
} }
} }
impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> { impl<T: 'static> core::ops::DerefMut for Lease<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.entity.as_mut().unwrap().downcast_mut().unwrap() self.entity.as_mut().unwrap().downcast_mut().unwrap()
} }
} }
impl<'a, T> Drop for Lease<'a, T> { impl<T> Drop for Lease<'_, T> {
fn drop(&mut self) { fn drop(&mut self) {
if self.entity.is_some() && !panicking() { if self.entity.is_some() && !panicking() {
panic!("Leases must be ended with EntityMap::end_lease") panic!("Leases must be ended with EntityMap::end_lease")

View File

@@ -82,7 +82,7 @@ impl From<Rgba> for u32 {
struct RgbaVisitor; struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor { impl Visitor<'_> for RgbaVisitor {
type Value = Rgba; type Value = Rgba;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@@ -180,7 +180,7 @@ impl TryFrom<&'_ str> for Rgba {
/// Duplicates a given hex digit. /// Duplicates a given hex digit.
/// E.g., `0xf` -> `0xff`. /// E.g., `0xf` -> `0xff`.
const fn duplicate(value: u8) -> u8 { const fn duplicate(value: u8) -> u8 {
value << 4 | value (value << 4) | value
} }
(duplicate(r), duplicate(g), duplicate(b), duplicate(a)) (duplicate(r), duplicate(g), duplicate(b), duplicate(a))

View File

@@ -946,13 +946,13 @@ impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
} }
} }
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count { impl sum_tree::SeekTarget<'_, ListItemSummary, ListItemSummary> for Count {
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering { fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
self.0.partial_cmp(&other.count).unwrap() self.0.partial_cmp(&other.count).unwrap()
} }
} }
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { impl sum_tree::SeekTarget<'_, ListItemSummary, ListItemSummary> for Height {
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering { fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
self.0.partial_cmp(&other.height).unwrap() self.0.partial_cmp(&other.height).unwrap()
} }

View File

@@ -587,7 +587,7 @@ impl<'a> Scope<'a> {
} }
} }
impl<'a> Drop for Scope<'a> { impl Drop for Scope<'_> {
fn drop(&mut self) { fn drop(&mut self) {
self.tx.take().unwrap(); self.tx.take().unwrap();

View File

@@ -1,4 +1,5 @@
use serde::Deserialize; use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{ use std::{
error::Error, error::Error,
fmt::{Display, Write}, fmt::{Display, Write},
@@ -306,24 +307,29 @@ impl std::fmt::Display for Keystroke {
} }
/// The state of the modifier keys at some point in time /// The state of the modifier keys at some point in time
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
pub struct Modifiers { pub struct Modifiers {
/// The control key /// The control key
#[serde(default)]
pub control: bool, pub control: bool,
/// The alt key /// The alt key
/// Sometimes also known as the 'meta' key /// Sometimes also known as the 'meta' key
#[serde(default)]
pub alt: bool, pub alt: bool,
/// The shift key /// The shift key
#[serde(default)]
pub shift: bool, pub shift: bool,
/// The command key, on macos /// The command key, on macos
/// the windows key, on windows /// the windows key, on windows
/// the super key, on linux /// the super key, on linux
#[serde(default)]
pub platform: bool, pub platform: bool,
/// The function key /// The function key
#[serde(default)]
pub function: bool, pub function: bool,
} }

View File

@@ -5,7 +5,6 @@ use objc::{class, msg_send, sel, sel_impl};
/// The `cocoa` crate does not define NSAttributedString (and related Cocoa classes), /// The `cocoa` crate does not define NSAttributedString (and related Cocoa classes),
/// which are needed for copying rich text (that is, text intermingled with images) /// which are needed for copying rich text (that is, text intermingled with images)
/// to the clipboard. This adds access to those APIs. /// to the clipboard. This adds access to those APIs.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub trait NSAttributedString: Sized { pub trait NSAttributedString: Sized {
unsafe fn alloc(_: Self) -> id { unsafe fn alloc(_: Self) -> id {

View File

@@ -26,7 +26,7 @@ pub(crate) mod dispatch_sys {
use dispatch_sys::*; use dispatch_sys::*;
pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t { pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
unsafe { addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t } addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t
} }
pub(crate) struct MacDispatcher { pub(crate) struct MacDispatcher {

View File

@@ -1221,10 +1221,10 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
unsafe { unsafe {
type SetWindowCompositionAttributeType = type SetWindowCompositionAttributeType =
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
let module_name = PCSTR::from_raw("user32.dll\0".as_ptr()); let module_name = PCSTR::from_raw(c"user32.dll".as_ptr() as *const u8);
let user32 = GetModuleHandleA(module_name); let user32 = GetModuleHandleA(module_name);
if user32.is_ok() { if user32.is_ok() {
let func_name = PCSTR::from_raw("SetWindowCompositionAttribute\0".as_ptr()); let func_name = PCSTR::from_raw(c"SetWindowCompositionAttribute".as_ptr() as *const u8);
let set_window_composition_attribute: SetWindowCompositionAttributeType = let set_window_composition_attribute: SetWindowCompositionAttributeType =
std::mem::transmute(GetProcAddress(user32.unwrap(), func_name)); std::mem::transmute(GetProcAddress(user32.unwrap(), func_name));
let mut color = color.unwrap_or_default(); let mut color = color.unwrap_or_default();
@@ -1238,7 +1238,7 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
gradient_color: (color.0 as u32) gradient_color: (color.0 as u32)
| ((color.1 as u32) << 8) | ((color.1 as u32) << 8)
| ((color.2 as u32) << 16) | ((color.2 as u32) << 16)
| (color.3 as u32) << 24, | ((color.3 as u32) << 24),
animation_id: 0, animation_id: 0,
}; };
let mut data = WINDOWCOMPOSITIONATTRIBDATA { let mut data = WINDOWCOMPOSITIONATTRIBDATA {

View File

@@ -670,6 +670,15 @@ pub struct TextRun {
pub strikethrough: Option<StrikethroughStyle>, pub strikethrough: Option<StrikethroughStyle>,
} }
#[cfg(all(target_os = "macos", test))]
impl TextRun {
fn with_len(&self, len: usize) -> Self {
let mut this = self.clone();
this.len = len;
this
}
}
/// An identifier for a specific glyph, as returned by [`TextSystem::layout_line`]. /// An identifier for a specific glyph, as returned by [`TextSystem::layout_line`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)] #[repr(C)]

View File

@@ -601,15 +601,15 @@ struct CacheKeyRef<'a> {
wrap_width: Option<Pixels>, wrap_width: Option<Pixels>,
} }
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) { impl PartialEq for (dyn AsCacheKeyRef + '_) {
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool { fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
self.as_cache_key_ref() == other.as_cache_key_ref() self.as_cache_key_ref() == other.as_cache_key_ref()
} }
} }
impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {} impl Eq for (dyn AsCacheKeyRef + '_) {}
impl<'a> Hash for (dyn AsCacheKeyRef + 'a) { impl Hash for (dyn AsCacheKeyRef + '_) {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.as_cache_key_ref().hash(state) self.as_cache_key_ref().hash(state)
} }
@@ -644,7 +644,7 @@ impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
} }
} }
impl<'a> AsCacheKeyRef for CacheKeyRef<'a> { impl AsCacheKeyRef for CacheKeyRef<'_> {
fn as_cache_key_ref(&self) -> CacheKeyRef { fn as_cache_key_ref(&self) -> CacheKeyRef {
*self *self
} }

View File

@@ -543,14 +543,6 @@ mod tests {
background_color: None, background_color: None,
}; };
impl TextRun {
fn with_len(&self, len: usize) -> Self {
let mut this = self.clone();
this.len = len;
this
}
}
let text = "aa bbb cccc ddddd eeee".into(); let text = "aa bbb cccc ddddd eeee".into();
let lines = text_system let lines = text_system
.shape_text( .shape_text(

View File

@@ -40,6 +40,19 @@ pub trait FluentBuilder {
} }
}) })
} }
/// Conditionally unwrap and modify self with the given closure, if the given option is Some.
fn when_none<T>(self, option: &Option<T>, then: impl FnOnce(Self) -> Self) -> Self
where
Self: Sized,
{
self.map(|this| {
if let Some(_) = option {
this
} else {
then(this)
}
})
}
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@@ -59,7 +72,7 @@ where
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace); pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
impl<'a> std::fmt::Debug for CwdBacktrace<'a> { impl std::fmt::Debug for CwdBacktrace<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use backtrace::{BacktraceFmt, BytesOrWideString}; use backtrace::{BacktraceFmt, BytesOrWideString};

View File

@@ -3689,8 +3689,6 @@ impl Window {
dispatch_tree.bindings_for_action(action, &[context]) dispatch_tree.bindings_for_action(action, &[context])
} }
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle. /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
pub fn listener_for<V: Render, E>( pub fn listener_for<V: Render, E>(
&self, &self,

View File

@@ -79,7 +79,7 @@ pub trait HttpClient: 'static + Send + Sync {
.body(body); .body(body);
match request { match request {
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }), Ok(request) => Box::pin(async move { self.send(request).await }),
Err(e) => Box::pin(async move { Err(e.into()) }), Err(e) => Box::pin(async move { Err(e.into()) }),
} }
} }
@@ -96,7 +96,7 @@ pub trait HttpClient: 'static + Send + Sync {
.body(body); .body(body);
match request { match request {
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }), Ok(request) => Box::pin(async move { self.send(request).await }),
Err(e) => Box::pin(async move { Err(e.into()) }), Err(e) => Box::pin(async move { Err(e.into()) }),
} }
} }

View File

@@ -456,7 +456,7 @@ impl InlineCompletionButton {
} }
let settings = AllLanguageSettings::get_global(cx); let settings = AllLanguageSettings::get_global(cx);
let globally_enabled = settings.show_inline_completions(None, cx); let globally_enabled = settings.show_edit_predictions(None, cx);
menu = menu.toggleable_entry("All Files", globally_enabled, IconPosition::Start, None, { menu = menu.toggleable_entry("All Files", globally_enabled, IconPosition::Start, None, {
let fs = fs.clone(); let fs = fs.clone();
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx) move |_, cx| toggle_inline_completions_globally(fs.clone(), cx)
@@ -592,8 +592,8 @@ impl InlineCompletionButton {
if cx.has_flag::<feature_flags::PredictEditsNonEagerModeFeatureFlag>() { if cx.has_flag::<feature_flags::PredictEditsNonEagerModeFeatureFlag>() {
let is_eager_preview_enabled = match settings.edit_predictions_mode() { let is_eager_preview_enabled = match settings.edit_predictions_mode() {
language::EditPredictionsMode::Auto => false, language::EditPredictionsMode::Subtle => false,
language::EditPredictionsMode::EagerPreview => true, language::EditPredictionsMode::Eager => true,
}; };
menu = menu.separator().toggleable_entry( menu = menu.separator().toggleable_entry(
"Eager Preview Mode", "Eager Preview Mode",
@@ -608,8 +608,8 @@ impl InlineCompletionButton {
cx, cx,
move |settings, _cx| { move |settings, _cx| {
let new_mode = match is_eager_preview_enabled { let new_mode = match is_eager_preview_enabled {
true => language::EditPredictionsMode::Auto, true => language::EditPredictionsMode::Subtle,
false => language::EditPredictionsMode::EagerPreview, false => language::EditPredictionsMode::Eager,
}; };
if let Some(edit_predictions) = settings.edit_predictions.as_mut() { if let Some(edit_predictions) = settings.edit_predictions.as_mut() {
@@ -702,7 +702,7 @@ impl InlineCompletionButton {
Some( Some(
file.map(|file| { file.map(|file| {
all_language_settings(Some(file), cx) all_language_settings(Some(file), cx)
.inline_completions_enabled_for_path(file.path()) .edit_predictions_enabled_for_file(file, cx)
}) })
.unwrap_or(true), .unwrap_or(true),
) )
@@ -825,7 +825,7 @@ async fn open_disabled_globs_setting_in_editor(
} }
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) { fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx); let show_edit_predictions = all_language_settings(None, cx).show_edit_predictions(None, cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| { update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_edit_predictions = Some(!show_edit_predictions) file.defaults.show_edit_predictions = Some(!show_edit_predictions)
}); });
@@ -845,7 +845,7 @@ fn toggle_show_inline_completions_for_language(
cx: &mut App, cx: &mut App,
) { ) {
let show_edit_predictions = let show_edit_predictions =
all_language_settings(None, cx).show_inline_completions(Some(&language), cx); all_language_settings(None, cx).show_edit_predictions(Some(&language), cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| { update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages file.languages
.entry(language.name()) .entry(language.name())

View File

@@ -509,7 +509,7 @@ impl fmt::Debug for ChunkRenderer {
} }
} }
impl<'a, 'b> Deref for ChunkRendererContext<'a, 'b> { impl Deref for ChunkRendererContext<'_, '_> {
type Target = App; type Target = App;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -517,7 +517,7 @@ impl<'a, 'b> Deref for ChunkRendererContext<'a, 'b> {
} }
} }
impl<'a, 'b> DerefMut for ChunkRendererContext<'a, 'b> { impl DerefMut for ChunkRendererContext<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.context self.context
} }
@@ -3507,24 +3507,13 @@ impl BufferSnapshot {
true, true,
); );
let mut last_buffer_range_end = 0; let mut last_buffer_range_end = 0;
for (buffer_range, is_name) in buffer_ranges { for (buffer_range, is_name) in buffer_ranges {
if !text.is_empty() && buffer_range.start > last_buffer_range_end { let space_added = !text.is_empty() && buffer_range.start > last_buffer_range_end;
if space_added {
text.push(' '); text.push(' ');
} }
last_buffer_range_end = buffer_range.end; let before_append_len = text.len();
if is_name {
let mut start = text.len();
let end = start + buffer_range.len();
// When multiple names are captured, then the matchable text
// includes the whitespace in between the names.
if !name_ranges.is_empty() {
start -= 1;
}
name_ranges.push(start..end);
}
let mut offset = buffer_range.start; let mut offset = buffer_range.start;
chunks.seek(buffer_range.clone()); chunks.seek(buffer_range.clone());
for mut chunk in chunks.by_ref() { for mut chunk in chunks.by_ref() {
@@ -3548,6 +3537,16 @@ impl BufferSnapshot {
break; break;
} }
} }
if is_name {
let after_append_len = text.len();
let start = if space_added && !name_ranges.is_empty() {
before_append_len - 1
} else {
before_append_len
};
name_ranges.push(start..after_append_len);
}
last_buffer_range_end = buffer_range.end;
} }
Some(OutlineItem { Some(OutlineItem {
@@ -4138,7 +4137,7 @@ impl Deref for BufferSnapshot {
} }
} }
unsafe impl<'a> Send for BufferChunks<'a> {} unsafe impl Send for BufferChunks<'_> {}
impl<'a> BufferChunks<'a> { impl<'a> BufferChunks<'a> {
pub(crate) fn new( pub(crate) fn new(
@@ -4478,6 +4477,7 @@ impl IndentSize {
pub struct TestFile { pub struct TestFile {
pub path: Arc<Path>, pub path: Arc<Path>,
pub root_name: String, pub root_name: String,
pub local_root: Option<PathBuf>,
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@@ -4491,7 +4491,11 @@ impl File for TestFile {
} }
fn as_local(&self) -> Option<&dyn LocalFile> { fn as_local(&self) -> Option<&dyn LocalFile> {
None if self.local_root.is_some() {
Some(self)
} else {
None
}
} }
fn disk_state(&self) -> DiskState { fn disk_state(&self) -> DiskState {
@@ -4519,6 +4523,23 @@ impl File for TestFile {
} }
} }
#[cfg(any(test, feature = "test-support"))]
impl LocalFile for TestFile {
fn abs_path(&self, _cx: &App) -> PathBuf {
PathBuf::from(self.local_root.as_ref().unwrap())
.join(&self.root_name)
.join(self.path.as_ref())
}
fn load(&self, _cx: &App) -> Task<Result<String>> {
unimplemented!()
}
fn load_bytes(&self, _cx: &App) -> Task<Result<Vec<u8>>> {
unimplemented!()
}
}
pub(crate) fn contiguous_ranges( pub(crate) fn contiguous_ranges(
values: impl Iterator<Item = u32>, values: impl Iterator<Item = u32>,
max_len: usize, max_len: usize,

View File

@@ -254,6 +254,7 @@ fn file(path: &str) -> Arc<dyn File> {
Arc::new(TestFile { Arc::new(TestFile {
path: Path::new(path).into(), path: Path::new(path).into(),
root_name: "zed".into(), root_name: "zed".into(),
local_root: None,
}) })
} }

View File

@@ -9,7 +9,7 @@ use ec4rs::{
Properties as EditorconfigProperties, Properties as EditorconfigProperties,
}; };
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::App; use gpui::{App, Modifiers};
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
use schemars::{ use schemars::{
schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec}, schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
@@ -231,11 +231,34 @@ pub struct EditPredictionSettings {
/// A list of globs representing files that edit predictions should be disabled for. /// A list of globs representing files that edit predictions should be disabled for.
/// This list adds to a pre-existing, sensible default set of globs. /// This list adds to a pre-existing, sensible default set of globs.
/// Any additional ones you add are combined with them. /// Any additional ones you add are combined with them.
pub disabled_globs: Vec<GlobMatcher>, pub disabled_globs: Vec<DisabledGlob>,
/// Configures how edit predictions are displayed in the buffer. /// Configures how edit predictions are displayed in the buffer.
pub mode: EditPredictionsMode, pub mode: EditPredictionsMode,
/// Settings specific to GitHub Copilot. /// Settings specific to GitHub Copilot.
pub copilot: CopilotSettings, pub copilot: CopilotSettings,
/// Whether edit predictions are enabled in the assistant panel.
/// This setting has no effect if globally disabled.
pub enabled_in_assistant: bool,
}
impl EditPredictionSettings {
/// Returns whether edit predictions are enabled for the given path.
pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
!self.disabled_globs.iter().any(|glob| {
if glob.is_absolute {
file.as_local()
.map_or(false, |local| glob.matcher.is_match(local.abs_path(cx)))
} else {
glob.matcher.is_match(file.path())
}
})
}
}
#[derive(Clone, Debug)]
pub struct DisabledGlob {
matcher: GlobMatcher,
is_absolute: bool,
} }
/// The mode in which edit predictions should be displayed. /// The mode in which edit predictions should be displayed.
@@ -244,10 +267,12 @@ pub struct EditPredictionSettings {
pub enum EditPredictionsMode { pub enum EditPredictionsMode {
/// If provider supports it, display inline when holding modifier key (e.g., alt). /// If provider supports it, display inline when holding modifier key (e.g., alt).
/// Otherwise, eager preview is used. /// Otherwise, eager preview is used.
Auto, #[serde(alias = "auto")]
Subtle,
/// Display inline when there are no language server completions available. /// Display inline when there are no language server completions available.
#[default] #[default]
EagerPreview, #[serde(alias = "eager_preview")]
Eager,
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@@ -478,6 +503,10 @@ pub struct EditPredictionSettingsContent {
/// Settings specific to GitHub Copilot. /// Settings specific to GitHub Copilot.
#[serde(default)] #[serde(default)]
pub copilot: CopilotSettingsContent, pub copilot: CopilotSettingsContent,
/// Whether edit predictions are enabled in the assistant prompt editor.
/// This has no effect if globally disabled.
#[serde(default = "default_true")]
pub enabled_in_assistant: bool,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
@@ -903,6 +932,13 @@ pub struct InlayHintSettings {
/// Default: 50 /// Default: 50
#[serde(default = "scroll_debounce_ms")] #[serde(default = "scroll_debounce_ms")]
pub scroll_debounce_ms: u64, pub scroll_debounce_ms: u64,
/// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
/// If only a subset of the modifiers specified is pressed, hints are not toggled.
/// If no modifiers are specified, this is equivalent to `None`.
///
/// Default: None
#[serde(default)]
pub toggle_on_modifiers_press: Option<Modifiers>,
} }
fn edit_debounce_ms() -> u64 { fn edit_debounce_ms() -> u64 {
@@ -963,16 +999,12 @@ impl AllLanguageSettings {
} }
/// Returns whether edit predictions are enabled for the given path. /// Returns whether edit predictions are enabled for the given path.
pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool { pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
!self self.edit_predictions.enabled_for_file(file, cx)
.edit_predictions
.disabled_globs
.iter()
.any(|glob| glob.is_match(path))
} }
/// Returns whether edit predictions are enabled for the given language and path. /// Returns whether edit predictions are enabled for the given language and path.
pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool { pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
self.language(None, language.map(|l| l.name()).as_ref(), cx) self.language(None, language.map(|l| l.name()).as_ref(), cx)
.show_edit_predictions .show_edit_predictions
} }
@@ -1101,6 +1133,12 @@ impl settings::Settings for AllLanguageSettings {
}) })
.unwrap_or_default(); .unwrap_or_default();
let mut edit_predictions_enabled_in_assistant = default_value
.edit_predictions
.as_ref()
.map(|settings| settings.enabled_in_assistant)
.unwrap_or(true);
let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default(); let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
for (language, suffixes) in &default_value.file_types { for (language, suffixes) in &default_value.file_types {
@@ -1127,6 +1165,7 @@ impl settings::Settings for AllLanguageSettings {
if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() { if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
edit_predictions_mode = edit_predictions.mode; edit_predictions_mode = edit_predictions.mode;
edit_predictions_enabled_in_assistant = edit_predictions.enabled_in_assistant;
if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() { if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
completion_globs.extend(disabled_globs.iter()); completion_globs.extend(disabled_globs.iter());
@@ -1197,10 +1236,16 @@ impl settings::Settings for AllLanguageSettings {
}, },
disabled_globs: completion_globs disabled_globs: completion_globs
.iter() .iter()
.filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher())) .filter_map(|g| {
Some(DisabledGlob {
matcher: globset::Glob::new(g).ok()?.compile_matcher(),
is_absolute: Path::new(g).is_absolute(),
})
})
.collect(), .collect(),
mode: edit_predictions_mode, mode: edit_predictions_mode,
copilot: copilot_settings, copilot: copilot_settings,
enabled_in_assistant: edit_predictions_enabled_in_assistant,
}, },
defaults, defaults,
languages, languages,
@@ -1355,6 +1400,8 @@ pub struct PrettierSettings {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use gpui::TestAppContext;
use super::*; use super::*;
#[test] #[test]
@@ -1399,6 +1446,132 @@ mod tests {
assert!(result.is_err()); assert!(result.is_err());
} }
#[gpui::test]
fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
use crate::TestFile;
use std::path::PathBuf;
let cx = cx.app.borrow_mut();
let build_settings = |globs: &[&str]| -> EditPredictionSettings {
EditPredictionSettings {
disabled_globs: globs
.iter()
.map(|glob_str| {
#[cfg(windows)]
let glob_str = {
let mut g = String::new();
if glob_str.starts_with('/') {
g.push_str("C:");
}
g.push_str(&glob_str.replace('/', "\\"));
g
};
#[cfg(windows)]
let glob_str = glob_str.as_str();
DisabledGlob {
matcher: globset::Glob::new(glob_str).unwrap().compile_matcher(),
is_absolute: Path::new(glob_str).is_absolute(),
}
})
.collect(),
..Default::default()
}
};
const WORKTREE_NAME: &str = "project";
let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
let mut path_buf = PathBuf::new();
path_buf.extend(segments);
Arc::new(TestFile {
path: path_buf.as_path().into(),
root_name: WORKTREE_NAME.to_string(),
local_root: Some(PathBuf::from(if cfg!(windows) {
"C:\\absolute\\"
} else {
"/absolute/"
})),
})
};
let test_file = make_test_file(&["src", "test", "file.rs"]);
// Test relative globs
let settings = build_settings(&["*.rs"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["*.txt"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test absolute globs
let settings = build_settings(&["/absolute/**/*.rs"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["/other/**/*.rs"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test exact path match relative
let settings = build_settings(&["src/test/file.rs"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["src/test/otherfile.rs"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test exact path match absolute
let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["/other/test/otherfile.rs"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test * glob
let settings = build_settings(&["*"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["*.txt"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test **/* glob
let settings = build_settings(&["**/*"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["other/**/*"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test directory/** glob
let settings = build_settings(&["src/**"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let test_file_root: Arc<dyn File> = Arc::new(TestFile {
path: PathBuf::from("file.rs").as_path().into(),
root_name: WORKTREE_NAME.to_string(),
local_root: Some(PathBuf::from("/absolute/")),
});
assert!(settings.enabled_for_file(&test_file_root, &cx));
let settings = build_settings(&["other/**"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test **/directory/* glob
let settings = build_settings(&["**/test/*"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["**/other/*"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test multiple globs
let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
assert!(!settings.enabled_for_file(&test_file, &cx));
let settings = build_settings(&["*.txt", "*.md", "other/**"]);
assert!(settings.enabled_for_file(&test_file, &cx));
// Test dot files
let dot_file = make_test_file(&[".config", "settings.json"]);
let settings = build_settings(&[".*/**"]);
assert!(!settings.enabled_for_file(&dot_file, &cx));
let dot_env_file = make_test_file(&[".env"]);
let settings = build_settings(&[".env"]);
assert!(!settings.enabled_for_file(&dot_env_file, &cx));
}
#[test] #[test]
pub fn test_resolve_language_servers() { pub fn test_resolve_language_servers() {
fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> { fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {

View File

@@ -836,7 +836,7 @@ impl SyntaxSnapshot {
} }
#[cfg(test)] #[cfg(test)]
pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer> { pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer<'a>> {
self.layers_for_range(0..buffer.len(), buffer, true) self.layers_for_range(0..buffer.len(), buffer, true)
.collect() .collect()
} }
@@ -1142,7 +1142,7 @@ impl<'a> SyntaxMapMatches<'a> {
} }
} }
impl<'a> SyntaxMapCapturesLayer<'a> { impl SyntaxMapCapturesLayer<'_> {
fn advance(&mut self) { fn advance(&mut self) {
self.next_capture = self.captures.next().map(|(mat, ix)| mat.captures[*ix]); self.next_capture = self.captures.next().map(|(mat, ix)| mat.captures[*ix]);
} }
@@ -1157,7 +1157,7 @@ impl<'a> SyntaxMapCapturesLayer<'a> {
} }
} }
impl<'a> SyntaxMapMatchesLayer<'a> { impl SyntaxMapMatchesLayer<'_> {
fn advance(&mut self) { fn advance(&mut self) {
if let Some(mat) = self.matches.next() { if let Some(mat) = self.matches.next() {
self.next_captures.clear(); self.next_captures.clear();
@@ -1740,7 +1740,7 @@ impl sum_tree::Summary for SyntaxLayerSummary {
} }
} }
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosition { impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosition {
fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering { fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
Ord::cmp(&self.depth, &cursor_location.max_depth) Ord::cmp(&self.depth, &cursor_location.max_depth)
.then_with(|| { .then_with(|| {
@@ -1758,16 +1758,14 @@ impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerP
} }
} }
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for ChangeStartPosition { impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for ChangeStartPosition {
fn cmp(&self, cursor_location: &SyntaxLayerSummary, text: &BufferSnapshot) -> Ordering { fn cmp(&self, cursor_location: &SyntaxLayerSummary, text: &BufferSnapshot) -> Ordering {
Ord::cmp(&self.depth, &cursor_location.max_depth) Ord::cmp(&self.depth, &cursor_location.max_depth)
.then_with(|| self.position.cmp(&cursor_location.range.end, text)) .then_with(|| self.position.cmp(&cursor_location.range.end, text))
} }
} }
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPositionBeforeChange {
for SyntaxLayerPositionBeforeChange
{
fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering { fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
if self.change.cmp(cursor_location, buffer).is_le() { if self.change.cmp(cursor_location, buffer).is_le() {
Ordering::Less Ordering::Less
@@ -1869,7 +1867,7 @@ struct LogPoint(Point);
struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot); struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot); struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
impl<'a> fmt::Debug for LogIncludedRanges<'a> { impl fmt::Debug for LogIncludedRanges<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list() f.debug_list()
.entries(self.0.iter().map(|range| { .entries(self.0.iter().map(|range| {
@@ -1881,14 +1879,14 @@ impl<'a> fmt::Debug for LogIncludedRanges<'a> {
} }
} }
impl<'a> fmt::Debug for LogAnchorRange<'a> { impl fmt::Debug for LogAnchorRange<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let range = self.0.to_point(self.1); let range = self.0.to_point(self.1);
(LogPoint(range.start)..LogPoint(range.end)).fmt(f) (LogPoint(range.start)..LogPoint(range.end)).fmt(f)
} }
} }
impl<'a> fmt::Debug for LogChangedRegions<'a> { impl fmt::Debug for LogChangedRegions<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list() f.debug_list()
.entries( .entries(

View File

@@ -305,13 +305,7 @@ impl LspAdapter for ExtensionLspAdapter {
.labels_for_symbols(self.language_server_id.clone(), symbols) .labels_for_symbols(self.language_server_id.clone(), symbols)
.await?; .await?;
Ok(labels_from_extension( Ok(labels_from_extension(labels, language))
labels
.into_iter()
.map(|label| label.map(Into::into))
.collect(),
language,
))
} }
} }

View File

@@ -90,7 +90,7 @@ where
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct LanguageModelToolUse { pub struct LanguageModelToolUse {
pub id: LanguageModelToolUseId, pub id: LanguageModelToolUseId,
pub name: String, pub name: Arc<str>,
pub input: serde_json::Value, pub input: serde_json::Value,
} }

View File

@@ -153,8 +153,8 @@ impl LlmApiToken {
Self::fetch(self.0.write().await, client).await Self::fetch(self.0.write().await, client).await
} }
async fn fetch<'a>( async fn fetch(
mut lock: RwLockWriteGuard<'a, Option<String>>, mut lock: RwLockWriteGuard<'_, Option<String>>,
client: &Arc<Client>, client: &Arc<Client>,
) -> Result<String> { ) -> Result<String> {
let response = client.request(proto::GetLlmToken {}).await?; let response = client.request(proto::GetLlmToken {}).await?;

View File

@@ -1,4 +1,5 @@
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
use std::sync::Arc;
use crate::role::Role; use crate::role::Role;
use crate::{LanguageModelToolUse, LanguageModelToolUseId}; use crate::{LanguageModelToolUse, LanguageModelToolUseId};
@@ -167,7 +168,7 @@ impl LanguageModelImage {
pub struct LanguageModelToolResult { pub struct LanguageModelToolResult {
pub tool_use_id: LanguageModelToolUseId, pub tool_use_id: LanguageModelToolUseId,
pub is_error: bool, pub is_error: bool,
pub content: String, pub content: Arc<str>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
@@ -199,15 +200,16 @@ pub struct LanguageModelRequestMessage {
impl LanguageModelRequestMessage { impl LanguageModelRequestMessage {
pub fn string_contents(&self) -> String { pub fn string_contents(&self) -> String {
let mut string_buffer = String::new(); let mut buffer = String::new();
for string in self.content.iter().filter_map(|content| match content { for string in self.content.iter().filter_map(|content| match content {
MessageContent::Text(text) => Some(text), MessageContent::Text(text) => Some(text.as_str()),
MessageContent::ToolResult(tool_result) => Some(&tool_result.content), MessageContent::ToolResult(tool_result) => Some(tool_result.content.as_ref()),
MessageContent::ToolUse(_) | MessageContent::Image(_) => None, MessageContent::ToolUse(_) | MessageContent::Image(_) => None,
}) { }) {
string_buffer.push_str(string.as_str()) buffer.push_str(string);
} }
string_buffer
buffer
} }
pub fn contents_empty(&self) -> bool { pub fn contents_empty(&self) -> bool {

View File

@@ -2,17 +2,26 @@ use std::sync::Arc;
use feature_flags::ZedPro; use feature_flags::ZedPro;
use gpui::{ use gpui::{
Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, action_with_deprecated_aliases, Action, AnyElement, App, Corner, DismissEvent, Entity,
Focusable, Subscription, Task, WeakEntity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
}; };
use language_model::{ use language_model::{
AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry, AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use proto::Plan; use proto::Plan;
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; use ui::{
prelude::*, ButtonLike, IconButtonShape, ListItem, ListItemSpacing, PopoverButton,
PopoverMenuHandle, Tooltip, TriggerablePopover,
};
use workspace::ShowConfiguration; use workspace::ShowConfiguration;
action_with_deprecated_aliases!(
assistant,
ToggleModelSelector,
["assistant2::ToggleModelSelector"]
);
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro"; const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &App) + 'static>; type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &App) + 'static>;
@@ -22,6 +31,7 @@ pub struct LanguageModelSelector {
/// The task used to update the picker's matches when there is a change to /// The task used to update the picker's matches when there is a change to
/// the language model registry. /// the language model registry.
update_matches_task: Option<Task<()>>, update_matches_task: Option<Task<()>>,
popover_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
_authenticate_all_providers_task: Task<()>, _authenticate_all_providers_task: Task<()>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@@ -53,6 +63,7 @@ impl LanguageModelSelector {
LanguageModelSelector { LanguageModelSelector {
picker, picker,
update_matches_task: None, update_matches_task: None,
popover_menu_handle: PopoverMenuHandle::default(),
_authenticate_all_providers_task: Self::authenticate_all_providers(cx), _authenticate_all_providers_task: Self::authenticate_all_providers(cx),
_subscriptions: vec![cx.subscribe_in( _subscriptions: vec![cx.subscribe_in(
&LanguageModelRegistry::global(cx), &LanguageModelRegistry::global(cx),
@@ -62,6 +73,15 @@ impl LanguageModelSelector {
} }
} }
pub fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.popover_menu_handle.toggle(window, cx);
}
fn handle_language_model_registry_event( fn handle_language_model_registry_event(
&mut self, &mut self,
_registry: &Entity<LanguageModelRegistry>, _registry: &Entity<LanguageModelRegistry>,
@@ -181,62 +201,13 @@ impl Render for LanguageModelSelector {
} }
} }
#[derive(IntoElement)] impl TriggerablePopover for LanguageModelSelector {
pub struct LanguageModelSelectorPopoverMenu<T, TT> fn menu_handle(
where &mut self,
T: PopoverTrigger + ButtonCommon, _window: &mut Window,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static, _cx: &mut gpui::Context<Self>,
{ ) -> PopoverMenuHandle<Self> {
language_model_selector: Entity<LanguageModelSelector>, self.popover_menu_handle.clone()
trigger: T,
tooltip: TT,
handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
anchor: Corner,
}
impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub fn new(
language_model_selector: Entity<LanguageModelSelector>,
trigger: T,
tooltip: TT,
anchor: Corner,
) -> Self {
Self {
language_model_selector,
trigger,
tooltip,
handle: None,
anchor,
}
}
pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
self.handle = Some(handle);
self
}
}
impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let language_model_selector = self.language_model_selector.clone();
PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(language_model_selector.clone()))
.trigger_with_tooltip(self.trigger, self.tooltip)
.anchor(self.anchor)
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
.offset(gpui::Point {
x: px(0.0),
y: px(-2.0),
})
} }
} }
@@ -521,3 +492,98 @@ impl PickerDelegate for LanguageModelPickerDelegate {
) )
} }
} }
pub struct InlineLanguageModelSelector {
selector: Entity<LanguageModelSelector>,
}
impl InlineLanguageModelSelector {
pub fn new(selector: Entity<LanguageModelSelector>) -> Self {
Self { selector }
}
}
impl RenderOnce for InlineLanguageModelSelector {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
PopoverButton::new(
self.selector,
gpui::Corner::TopRight,
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
)
.render(window, cx)
}
}
pub struct AssistantLanguageModelSelector {
focus_handle: FocusHandle,
selector: Entity<LanguageModelSelector>,
}
impl AssistantLanguageModelSelector {
pub fn new(focus_handle: FocusHandle, selector: Entity<LanguageModelSelector>) -> Self {
Self {
focus_handle,
selector,
}
}
}
impl RenderOnce for AssistantLanguageModelSelector {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.focus_handle.clone();
let model_name = match active_model {
Some(model) => model.name().0,
_ => SharedString::from("No model selected"),
};
PopoverButton::new(
self.selector.clone(),
Corner::BottomRight,
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
)
.render(window, cx)
}
}

View File

@@ -8,6 +8,7 @@ use provider::deepseek::DeepSeekLanguageModelProvider;
pub mod provider; pub mod provider;
mod settings; mod settings;
pub mod ui;
use crate::provider::anthropic::AnthropicLanguageModelProvider; use crate::provider::anthropic::AnthropicLanguageModelProvider;
use crate::provider::bedrock::BedrockLanguageModelProvider; use crate::provider::bedrock::BedrockLanguageModelProvider;

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