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

191
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

@@ -176,8 +176,8 @@
"show_completion_documentation": true,
// Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false,
/// 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.
// 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.
"show_signature_help_after_edits": false,
// Whether to show wrap guides (vertical rulers) in the editor.
// 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
// - "all" or true: show all diagnostics
"diagnostics": "all",
/// Forcefully enable or disable the scrollbar for each axis
// Forcefully enable or disable the scrollbar for each axis
"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,
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
"vertical": true
}
},
@@ -328,24 +328,24 @@
"folds": true
},
"indent_guides": {
/// Whether to show indent guides in the editor.
// Whether to show indent guides in the editor.
"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,
/// 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,
/// Determines how indent guides are colored.
/// This setting can take the following three values:
// Determines how indent guides are colored.
// This setting can take the following three values:
///
/// 1. "disabled"
/// 2. "fixed"
/// 3. "indent_aware"
// 1. "disabled"
// 2. "fixed"
// 3. "indent_aware"
"coloring": "fixed",
/// Determines how indent guide backgrounds are colored.
/// This setting can take the following two values:
// Determines how indent guide backgrounds are colored.
// This setting can take the following two values:
///
/// 1. "disabled"
/// 2. "indent_aware"
// 1. "disabled"
// 2. "indent_aware"
"background_coloring": "disabled"
},
// Whether the editor will scroll beyond the last line.
@@ -379,6 +379,9 @@
// 3. Never populate the search query
// "never"
"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,
// Inlay hint related settings
"inlay_hints": {
@@ -398,7 +401,16 @@
"edit_debounce_ms": 700,
// Time to wait after scrolling the buffer, before requesting the hints,
// 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": {
// Whether to show the project panel button in the status bar
@@ -424,32 +436,32 @@
// Whether to fold directories automatically and show compact folders
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
"auto_fold_dirs": true,
/// Scrollbar-related settings
// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take five values:
// When to show the scrollbar in the project panel.
// This setting can take five values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default):
/// "auto"
/// 3. Match the system's configured behavior:
/// "system"
/// 4. Always show the scrollbar:
/// "always"
/// 5. Never show the scrollbar:
/// "never"
// 1. null (default): Inherit editor settings
// 2. Show the scrollbar if there's important information or
// follow the system's configured behavior (default):
// "auto"
// 3. Match the system's configured behavior:
// "system"
// 4. Always show the scrollbar:
// "always"
// 5. Never show the scrollbar:
// "never"
"show": null
},
/// Which files containing diagnostic errors/warnings to mark in the project panel.
/// This setting can take the following three values:
// Which files containing diagnostic errors/warnings to mark in the project panel.
// This setting can take the following three values:
///
/// 1. Do not mark any files:
/// "off"
/// 2. Only mark files with errors:
/// "errors"
/// 3. Mark files with errors and warnings:
/// "all"
// 1. Do not mark any files:
// "off"
// 2. Only mark files with errors:
// "errors"
// 3. Mark files with errors and warnings:
// "all"
"show_diagnostics": "all",
// Settings related to indent guides in the project panel.
"indent_guides": {
@@ -482,8 +494,8 @@
// when a corresponding outline entry becomes active.
// Gitignored entries are never auto revealed.
"auto_reveal_entries": true,
/// Whether to fold directories automatically
/// when a directory has only one directory inside.
// Whether to fold directories automatically
// when a directory has only one directory inside.
"auto_fold_dirs": true,
// Settings related to indent guides in the outline panel.
"indent_guides": {
@@ -496,21 +508,21 @@
// "never"
"show": "always"
},
/// Scrollbar-related settings
// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take five values:
// When to show the scrollbar in the project panel.
// This setting can take five values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default):
/// "auto"
/// 3. Match the system's configured behavior:
/// "system"
/// 4. Always show the scrollbar:
/// "always"
/// 5. Never show the scrollbar:
/// "never"
// 1. null (default): Inherit editor settings
// 2. Show the scrollbar if there's important information or
// follow the system's configured behavior (default):
// "auto"
// 3. Match the system's configured behavior:
// "system"
// 4. Always show the scrollbar:
// "always"
// 5. Never show the scrollbar:
// "never"
"show": null
}
},
@@ -628,7 +640,7 @@
"show": true,
// Whether or not to show the navigation history buttons.
"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
},
// Settings related to the editor's tabs
@@ -650,16 +662,16 @@
// 3. Activate the left neighbour tab if present
// "left_neighbour"
"activate_on_close": "history",
/// Which files containing diagnostic errors/warnings to mark in the tabs.
/// Diagnostics are only shown when file icons are also active.
/// This setting only works when can take the following three values:
// Which files containing diagnostic errors/warnings to mark in the tabs.
// Diagnostics are only shown when file icons are also active.
// This setting only works when can take the following three values:
///
/// 1. Do not mark any files:
/// "off"
/// 2. Only mark files with errors:
/// "errors"
/// 3. Mark files with errors and warnings:
/// "all"
// 1. Do not mark any files:
// "off"
// 2. Only mark files with errors:
// "errors"
// 3. Mark files with errors and warnings:
// "all"
"show_diagnostics": "off"
},
// Settings related to preview tabs.
@@ -829,6 +841,8 @@
// A list of globs representing files that edit predictions should be disabled for.
// There's a sensible default list of globs already included.
// 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": [
"**/.env*",
"**/*.pem",
@@ -840,11 +854,14 @@
],
// When to show edit predictions previews in buffer.
// This setting takes two possible values:
// 1. Display inline when there are no language server completions available.
// "mode": "eager_preview"
// 2. Display inline when holding modifier key (alt by default).
// "mode": "auto"
"mode": "eager_preview"
// 1. Display predictions inline when there are no language server completions available.
// "mode": "eager"
// 2. Display predictions inline only when holding a modifier key (alt by default).
// "mode": "subtle"
"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
"journal": {
@@ -980,21 +997,21 @@
// Example: `echo -e "\e]2;New Title\007";`
"breadcrumbs": true
},
/// Scrollbar-related settings
// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the terminal.
/// This setting can take five values:
// When to show the scrollbar in the terminal.
// This setting can take five values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default):
/// "auto"
/// 3. Match the system's configured behavior:
/// "system"
/// 4. Always show the scrollbar:
/// "always"
/// 5. Never show the scrollbar:
/// "never"
// 1. null (default): Inherit editor settings
// 2. Show the scrollbar if there's important information or
// follow the system's configured behavior (default):
// "auto"
// 3. Match the system's configured behavior:
// "system"
// 4. Always show the scrollbar:
// "always"
// 5. Never show the scrollbar:
// "never"
"show": null
}
// Set the terminal's font size. If this option is not included,
@@ -1013,7 +1030,7 @@
// "max_scroll_history_lines": 10000,
},
"code_actions_on_format": {},
/// Settings related to running tasks.
// Settings related to running tasks.
"tasks": {
"variables": {}
},
@@ -1034,20 +1051,20 @@
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"Shell Script": [".env.*"]
},
/// 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:
/// {
/// "node": {
/// "path": "/path/to/node"
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
/// }
/// }
/// or to ensure Zed always downloads and installs an isolated version of node:
/// {
/// "node": {
/// "ignore_system_version": true,
/// }
/// NOTE: changing this setting currently requires restarting Zed.
// 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:
// {
// "node": {
// "path": "/path/to/node"
// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
// }
// }
// or to ensure Zed always downloads and installs an isolated version of node:
// {
// "node": {
// "ignore_system_version": true,
// }
// NOTE: changing this setting currently requires restarting Zed.
"node": {},
// The extensions that Zed should automatically install on startup.
//
@@ -1296,6 +1313,7 @@
},
// Vim settings
"vim": {
"default_mode": "normal",
"toggle_relative_line_numbers": false,
"use_system_clipboard": "always",
"use_multiline_find": false,

View File

@@ -35,7 +35,7 @@ use language_model::{
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role,
};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction};
@@ -1589,29 +1589,10 @@ impl Render for PromptEditor {
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.justify_center()
.gap_2()
.child(LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
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,
)
},
gpui::Corner::TopRight,
))
.child(
InlineLanguageModelSelector::new(self.language_model_selector.clone())
.render(window, cx),
)
.map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
return el;

View File

@@ -19,7 +19,7 @@ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role,
};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use prompt_library::PromptBuilder;
use settings::{update_settings_file, Settings};
use std::{
@@ -506,7 +506,7 @@ struct PromptEditor {
impl EventEmitter<PromptEditorEvent> 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 buttons = match status {
CodegenStatus::Idle => {
@@ -641,29 +641,10 @@ impl Render for PromptEditor {
.w_12()
.justify_center()
.gap_2()
.child(LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
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,
)
},
gpui::Corner::TopRight,
))
.child(
InlineLanguageModelSelector::new(self.language_model_selector.clone())
.render(window, cx),
)
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string());
@@ -1073,7 +1054,10 @@ pub enum CodegenEvent {
impl EventEmitter<CodegenEvent> for Codegen {}
#[cfg(not(target_os = "windows"))]
const CLEAR_INPUT: &str = "\x15";
#[cfg(target_os = "windows")]
const CLEAR_INPUT: &str = "\x03";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {

View File

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

View File

@@ -8,15 +8,16 @@ use gpui::{
UnderlineStyle, WeakEntity,
};
use language::LanguageRegistry;
use language_model::Role;
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
use markdown::{Markdown, MarkdownStyle};
use settings::Settings as _;
use theme::ThemeSettings;
use ui::prelude::*;
use ui::{prelude::*, Disclosure};
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::tool_use::{ToolUse, ToolUseStatus};
use crate::ui::ContextPill;
pub struct ActiveThread {
@@ -28,6 +29,7 @@ pub struct ActiveThread {
messages: Vec<MessageId>,
list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>,
}
@@ -55,6 +57,7 @@ impl ActiveThread {
thread: thread.clone(),
messages: Vec::new(),
rendered_messages_by_id: HashMap::default(),
expanded_tool_uses: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade();
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);
self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(
tool_use.assistant_message_id,
tool_use.id.clone(),
task,
cx,
);
thread.insert_tool_output(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 tool_uses = self.thread.read(cx).tool_uses_for_message(message_id);
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()
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
.when_some(context, |parent, context| {
@@ -332,7 +356,22 @@ impl ActiveThread {
)
.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(
v_flex()
.bg(colors.editor_background)
@@ -343,6 +382,102 @@ impl ActiveThread {
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 {

View File

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

View File

@@ -158,8 +158,16 @@ impl Render for AssistantConfiguration {
.child(
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.gap_1()
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
.gap_2()
.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(
Button::new("open-prompt-library", "Open Prompt Library")
.style(ButtonStyle::Filled)

View File

@@ -1,24 +1,19 @@
use assistant_settings::AssistantSettings;
use fs::Fs;
use gpui::{Entity, FocusHandle, SharedString};
use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use gpui::{Entity, FocusHandle};
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
use settings::update_settings_file;
use std::sync::Arc;
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::ToggleModelSelector;
use ui::prelude::*;
pub struct AssistantModelSelector {
selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
pub selector: Entity<LanguageModelSelector>,
focus_handle: FocusHandle,
}
impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
window: &mut Window,
cx: &mut App,
@@ -38,50 +33,14 @@ impl AssistantModelSelector {
cx,
)
}),
menu_handle,
focus_handle,
}
}
}
impl Render for AssistantModelSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.focus_handle.clone();
let model_name = match active_model {
Some(model) => model.name().0,
_ => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
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())
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
AssistantLanguageModelSelector::new(self.focus_handle.clone(), self.selector.clone())
.render(window, cx)
}
}

View File

@@ -14,7 +14,7 @@ use client::zed_urls;
use editor::Editor;
use fs::Fs;
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,
};
use language::LanguageRegistry;
@@ -458,6 +458,12 @@ impl AssistantPanel {
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
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 {
@@ -590,7 +596,6 @@ impl AssistantPanel {
h_flex()
.id("assistant-toolbar")
.px(DynamicSpacing::Base08.rems(cx))
.h(Tab::container_height(cx))
.flex_none()
.justify_between()
@@ -598,72 +603,86 @@ impl AssistantPanel {
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.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(
h_flex()
.w_full()
.gap_1()
.justify_between()
.child(Label::new(title))
.h_full()
.pl_2()
.gap_2()
.bg(cx.theme().colors().tab_bar_background)
.children(if matches!(self.active_view, ActiveView::PromptEditor) {
self.context_editor
.as_ref()
.and_then(|editor| render_remaining_tokens(editor, cx))
} else {
None
}),
)
.child(
h_flex()
.h_full()
.pl_1p5()
.border_l_1()
.border_color(cx.theme().colors().border)
.gap(DynamicSpacing::Base02.rems(cx))
})
.child(
PopoverMenu::new("assistant-toolbar-new-popover-menu")
.trigger_with_tooltip(
IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle),
Tooltip::text("New…"),
)
.anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone())
.menu(move |window, cx| {
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.action("New Thread", NewThread.boxed_clone())
.action("New Prompt Editor", NewPromptEditor.boxed_clone())
}))
}),
)
.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,
h_flex()
.h_full()
.px(DynamicSpacing::Base08.rems(cx))
.border_l_1()
.border_color(cx.theme().colors().border)
.gap(DynamicSpacing::Base02.rems(cx))
.child(
PopoverMenu::new("assistant-toolbar-new-popover-menu")
.trigger_with_tooltip(
IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle),
Tooltip::text("New…"),
)
}
})
.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);
}),
.anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone())
.menu(move |window, cx| {
Some(ContextMenu::build(
window,
cx,
|menu, _window, _cx| {
menu.action("New Thread", NewThread.boxed_clone())
.action(
"New Prompt Editor",
NewPromptEditor.boxed_clone(),
)
},
))
}),
)
.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 {
let recent_history = self
.history_store
.update(cx, |this, cx| this.recent_entries(3, cx));
.update(cx, |this, cx| this.recent_entries(6, cx));
let create_welcome_heading = || {
h_flex()
.w_full()
.justify_center()
.child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
};
@@ -718,36 +736,27 @@ impl AssistantPanel {
let no_error = configuration_error.is_none();
v_flex()
.gap_2()
.child(
v_flex().w_full().child(
svg()
.path("icons/logo_96.svg")
.text_color(cx.theme().colors().text)
.w(px(40.))
.h(px(40.))
.mx_auto()
.mb_4(),
),
)
.p_1p5()
.size_full()
.justify_end()
.gap_1()
.map(|parent| {
match configuration_error {
Some(ConfigurationError::ProviderNotAuthenticated)
| Some(ConfigurationError::NoProvider) => {
parent.child(
v_flex()
.px_1p5()
.gap_0p5()
.child(create_welcome_heading())
.child(
h_flex().mb_2().w_full().justify_center().child(
Label::new(
"To start using the assistant, configure at least one LLM provider.",
)
.color(Color::Muted),
),
Label::new(
"To start using the assistant, configure at least one LLM provider.",
)
.color(Color::Muted),
)
.child(
h_flex().w_full().justify_center().child(
h_flex().mt_1().w_full().child(
Button::new("open-configuration", "Configure a Provider")
.size(ButtonSize::Compact)
.icon(Some(IconName::Sliders))
@@ -761,7 +770,7 @@ impl AssistantPanel {
)
}
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(
LanguageModelProviderTosView::ThreadEmptyState,
cx,
@@ -772,21 +781,40 @@ impl AssistantPanel {
})
.when(recent_history.is_empty() && no_error, |parent| {
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| {
parent
.child(
h_flex().w_full().justify_center().child(
Label::new("Recent Threads:")
.size(LabelSize::Small)
.color(Color::Muted),
),
h_flex()
.pl_1p5()
.pb_1()
.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| {
// TODO: Add keyboard navigation.
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::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
use crate::{RemoveAllContext, ToggleContextPicker};
use client::ErrorExt;
use collections::VecDeque;
use editor::{
@@ -20,7 +20,6 @@ use gpui::{
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
};
use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
use parking_lot::Mutex;
use settings::Settings;
use std::cmp;
@@ -40,7 +39,6 @@ pub struct PromptEditor<T> {
context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
@@ -104,7 +102,12 @@ impl<T: 'static> Render for PromptEditor<T> {
.items_start()
.cursor(CursorStyle::Arrow)
.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::cancel))
.on_action(cx.listener(Self::move_up))
@@ -347,15 +350,6 @@ impl<T: 'static> PromptEditor<T> {
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(
&mut self,
_: &RemoveAllContext,
@@ -864,7 +858,6 @@ impl PromptEditor<BufferCodegen> {
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| {
ContextStrip::new(
@@ -888,15 +881,8 @@ impl PromptEditor<BufferCodegen> {
context_strip,
context_picker_menu_handle,
model_selector: cx.new(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
}),
model_selector_menu_handle,
edited_since_done: false,
prompt_history,
prompt_history_ix: None,
@@ -1020,7 +1006,6 @@ impl PromptEditor<TerminalCodegen> {
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| {
ContextStrip::new(
@@ -1044,15 +1029,8 @@ impl PromptEditor<TerminalCodegen> {
context_strip,
context_picker_menu_handle,
model_selector: cx.new(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx),
window,
cx,
)
AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
}),
model_selector_menu_handle,
edited_since_done: false,
prompt_history,
prompt_history_ix: None,

View File

@@ -7,16 +7,17 @@ use gpui::{
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
TextStyle, WeakEntity,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
use language_model::LanguageModelRegistry;
use rope::Point;
use settings::Settings;
use std::time::Duration;
use text::Bias;
use theme::ThemeSettings;
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 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::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore;
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker};
pub struct MessageEditor {
thread: Entity<Thread>,
@@ -36,7 +37,6 @@ pub struct MessageEditor {
inline_context_picker: Entity<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
}
@@ -53,7 +53,6 @@ impl MessageEditor {
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx);
@@ -106,30 +105,13 @@ impl MessageEditor {
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
editor.focus_handle(cx),
window,
cx,
)
}),
model_selector_menu_handle,
model_selector: cx
.new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)),
use_tools: false,
_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>) {
self.use_tools = !self.use_tools;
cx.notify();
@@ -205,22 +187,7 @@ impl MessageEditor {
.update(&mut cx, |thread, cx| {
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
thread.insert_user_message(user_message, context, cx);
let mut request = thread.to_completion_request(request_kind, 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)
thread.send_to_model(model, request_kind, use_tools, cx);
})
.ok();
})
@@ -309,7 +276,6 @@ impl Render for MessageEditor {
let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background;
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_editor_empty = self.is_editor_empty(cx);
let submit_label_color = if is_editor_empty {
@@ -318,10 +284,25 @@ impl Render for MessageEditor {
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()
.key_context("MessageEditor")
.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::remove_all_context))
.on_action(cx.listener(Self::move_up))
@@ -333,7 +314,7 @@ impl Render for MessageEditor {
.child(self.context_strip.clone())
.child(
v_flex()
.gap_4()
.gap_5()
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {

View File

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

View File

@@ -4,14 +4,12 @@ use anyhow::Result;
use assistant_tool::ToolWorkingSet;
use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet};
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use futures::StreamExt as _;
use gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError,
Role, StopReason,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId,
MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason,
};
use serde::{Deserialize, Serialize};
use util::{post_inc, TryFutureExt as _};
@@ -19,10 +17,13 @@ use uuid::Uuid;
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
use crate::thread_store::SavedThread;
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState};
#[derive(Debug, Clone, Copy)]
pub enum RequestKind {
Chat,
/// Used when summarizing a thread.
Summarize,
}
#[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)]
pub struct MessageId(usize);
pub struct MessageId(pub(crate) usize);
impl MessageId {
fn post_inc(&mut self) -> Self {
@@ -70,9 +71,7 @@ pub struct Thread {
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
tools: Arc<ToolWorkingSet>,
tool_uses_by_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
tool_results_by_message: HashMap<MessageId, Vec<LanguageModelToolResult>>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
tool_use: ToolUseState,
}
impl Thread {
@@ -89,9 +88,7 @@ impl Thread {
completion_count: 0,
pending_completions: Vec::new(),
tools,
tool_uses_by_message: HashMap::default(),
tool_results_by_message: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
tool_use: ToolUseState::default(),
}
}
@@ -123,9 +120,7 @@ impl Thread {
completion_count: 0,
pending_completions: Vec::new(),
tools,
tool_uses_by_message: HashMap::default(),
tool_results_by_message: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
tool_use: ToolUseState::default(),
}
}
@@ -187,7 +182,15 @@ impl Thread {
}
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(
@@ -241,9 +244,34 @@ impl Thread {
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(
&self,
_request_kind: RequestKind,
request_kind: RequestKind,
_cx: &App,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
@@ -265,12 +293,13 @@ impl Thread {
content: Vec::new(),
cache: false,
};
if let Some(tool_results) = self.tool_results_by_message.get(&message.id) {
for tool_result in tool_results {
request_message
.content
.push(MessageContent::ToolResult(tool_result.clone()));
match request_kind {
RequestKind::Chat => {
self.tool_use
.attach_tool_results(message.id, &mut request_message);
}
RequestKind::Summarize => {
// We don't care about tool use during summarization.
}
}
@@ -280,11 +309,13 @@ impl Thread {
.push(MessageContent::Text(message.text.clone()));
}
if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) {
for tool_use in tool_uses {
request_message
.content
.push(MessageContent::ToolUse(tool_use.clone()));
match request_kind {
RequestKind::Chat => {
self.tool_use
.attach_tool_uses(message.id, &mut request_message);
}
RequestKind::Summarize => {
// We don't care about tool use during summarization.
}
}
@@ -360,21 +391,8 @@ impl Thread {
.rfind(|message| message.role == Role::Assistant)
{
thread
.tool_uses_by_message
.entry(last_assistant_message.id)
.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,
},
);
.tool_use
.request_tool_use(last_assistant_message.id, tool_use);
}
}
}
@@ -451,7 +469,7 @@ impl Thread {
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 {
role: Role::User,
content: vec![
@@ -494,7 +512,6 @@ impl Thread {
pub fn insert_tool_output(
&mut self,
assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut Context<Self>,
@@ -505,50 +522,18 @@ impl Thread {
let output = output.await;
thread
.update(&mut cx, |thread, cx| {
// The tool use was requested by an Assistant message,
// so we want to attach the tool results to the next
// user message.
let next_user_message = MessageId(assistant_message_id.0 + 1);
thread
.tool_use
.insert_tool_output(tool_use_id.clone(), output);
let tool_results = thread
.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());
}
}
}
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
})
.ok();
}
});
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Running {
_task: insert_output_task.shared(),
};
}
self.tool_use
.run_pending_tool(tool_use_id, insert_output_task);
}
/// Cancels the last pending completion, if there are any pending.
@@ -590,26 +575,3 @@ struct PendingCompletion {
id: usize,
_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();
}
HistoryEntry::Context(_context) => {}
HistoryEntry::Context(context) => {
self.assistant_panel
.update(cx, |this, cx| {
this.delete_context(context.path.clone(), cx);
})
.ok();
}
}
cx.notify();
@@ -248,18 +254,28 @@ impl RenderOnce for PastThread {
);
ListItem::new(SharedString::from(self.thread.id.to_string()))
.outlined()
.rounded()
.toggle_state(self.selected)
.start_slot(
Icon::new(IconName::MessageCircle)
.size(IconSize::Small)
.color(Color::Muted),
)
.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(
h_flex()
.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(
Label::new(thread_timestamp)
.color(Color::Muted)
@@ -334,21 +350,50 @@ impl RenderOnce for PastContext {
ListItem::new(SharedString::from(
self.context.path.to_string_lossy().to_string(),
))
.outlined()
.rounded()
.toggle_state(self.selected)
.start_slot(
Icon::new(IconName::Code)
.size(IconSize::Small)
.color(Color::Muted),
)
.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(
h_flex().gap_1p5().child(
Label::new(context_timestamp)
.color(Color::Muted)
.size(LabelSize::XSmall),
),
h_flex()
.gap_1p5()
.child(
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({
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,
};
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::{
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
Role,
};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
use multi_buffer::MultiBufferRow;
use picker::Picker;
use project::lsp_store::LocalLspAdapterDelegate;
use project::{Project, Worktree};
use rope::Point;
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 text::SelectionGoal;
use ui::{
@@ -77,7 +80,6 @@ actions!(
InsertIntoEditor,
QuoteSelection,
Split,
ToggleModelSelector,
]
);
@@ -194,7 +196,6 @@ pub struct ContextEditor {
// context editor, we keep a reference here.
dragged_file_worktrees: Vec<Entity<Worktree>>,
language_model_selector: Entity<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
}
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_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
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
});
@@ -238,6 +246,7 @@ impl ContextEditor {
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_search_event),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
];
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 patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands().clone();
@@ -281,7 +289,6 @@ impl ContextEditor {
slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(),
language_model_selector,
language_model_selector_menu_handle,
};
this.update_message_headers(cx);
this.update_image_blocks(cx);
@@ -290,6 +297,16 @@ impl ContextEditor {
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> {
&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>) {
self.context.update(cx, |context, 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> {
let last_error = self.last_error.as_ref()?;
@@ -2864,6 +2832,7 @@ impl Render for ContextEditor {
None
};
let language_model_selector = self.language_model_selector.clone();
v_flex()
.key_context("ContextEditor")
.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::assist))
.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()
.children(self.render_notice(cx))
.child(
@@ -2914,11 +2887,14 @@ impl Render for ContextEditor {
.gap_1()
.child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical())
.child(
div()
.pl_0p5()
.child(self.render_language_model_selector(cx)),
),
.child(div().pl_0p5().child({
let focus_handle = self.editor().focus_handle(cx).clone();
AssistantLanguageModelSelector::new(
focus_handle,
self.language_model_selector.clone(),
)
.render(window, cx)
})),
)
.child(
h_flex()

View File

@@ -9,7 +9,7 @@ use clock::ReplicaId;
use collections::HashMap;
use context_server::manager::ContextServerManager;
use context_server::ContextServerFactoryRegistry;
use fs::Fs;
use fs::{Fs, RemoveOptions};
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
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>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;

View File

@@ -21,11 +21,11 @@ impl SlashCommand for DefaultSlashCommand {
}
fn description(&self) -> String {
"insert default prompt".into()
"Insert default prompt".into()
}
fn menu_text(&self) -> String {
"Insert Default Prompt".into()
self.description()
}
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 {
if self
.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>>,
}
impl<'a> ChannelPathsInsertGuard<'a> {
impl ChannelPathsInsertGuard<'_> {
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
let mut ret = false;
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) {
self.channels_ordered.sort_by(|a, b| {
let a = channel_path_sorting_key(*a, self.channels_by_id);

View File

@@ -33,10 +33,13 @@ util.workspace = true
tempfile.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
exec.workspace = true
exec.workspace = true
fork.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true
core-services = "0.2"
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")]
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 std::io;
use std::path::Path;
use std::path::{Path, PathBuf};
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 {
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 {
pub fn detect(_path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
Ok(App)
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
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
}
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 {
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;
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 {
&mut self.guard
}
}
impl<'a> Drop for ConnectionPoolGuard<'a> {
impl Drop for ConnectionPoolGuard<'_> {
fn drop(&mut self) {
#[cfg(test)]
self.check_invariants();

View File

@@ -1537,6 +1537,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
show_parameter_hints: false,
show_other_hints: true,
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_other_hints: true,
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_other_hints: 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_other_hints: true,
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
workspace
.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
.unwrap();
@@ -6375,7 +6375,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview
workspace
.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
.unwrap();
@@ -6507,7 +6507,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in right pane
workspace
.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
.unwrap();
@@ -6545,7 +6545,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in left pane
workspace
.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
.unwrap();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -192,7 +192,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
fn discard(&mut self, cx: &mut Context<Self>) {
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 {
return;

View File

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

View File

@@ -330,7 +330,11 @@ impl DisplayMap {
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 edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
@@ -341,10 +345,14 @@ impl DisplayMap {
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
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 edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
@@ -355,7 +363,7 @@ impl DisplayMap {
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
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 {

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;
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 {
&mut self.snapshot
}
}
impl<'a> BlockMapReader<'a> {
impl BlockMapReader<'_> {
pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
let block = self.blocks.iter().find(|block| block.id == block_id)?;
let buffer_row = block
@@ -1053,7 +1053,7 @@ impl<'a> BlockMapReader<'a> {
}
}
impl<'a> BlockMapWriter<'a> {
impl BlockMapWriter<'_> {
pub fn insert(
&mut self,
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
@@ -1239,26 +1239,45 @@ impl<'a> BlockMapWriter<'a> {
self.remove(blocks_to_remove);
}
pub fn fold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
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(
pub fn fold_buffers(
&mut self,
buffer_id: BufferId,
buffer_ids: impl IntoIterator<Item = BufferId>,
multi_buffer: &MultiBuffer,
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();
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(
wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
wrap_snapshot.max_point().row(),
@@ -1721,7 +1740,7 @@ impl BlockSnapshot {
}
}
impl<'a> BlockChunks<'a> {
impl BlockChunks<'_> {
/// Go to the next transform
fn advance(&mut self) {
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;
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;
fn deref(&self) -> &Self::Target {
@@ -2730,7 +2749,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
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 {
style: BlockStyle::Fixed,
@@ -2805,7 +2824,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
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 = blocks_snapshot
@@ -2861,7 +2880,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
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 = blocks_snapshot
@@ -2922,7 +2941,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
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 = blocks_snapshot
@@ -3000,7 +3019,7 @@ mod tests {
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
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 = blocks_snapshot
@@ -3250,7 +3269,7 @@ mod tests {
);
folded_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 {
let buffer_to_unfold =
@@ -3258,7 +3277,7 @@ mod tests {
log::info!("Unfolding {buffer_to_unfold:?}");
unfolded_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!(
"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);
impl<'a> FoldMapWriter<'a> {
impl FoldMapWriter<'_> {
pub(crate) fn fold<T: ToOffset>(
&mut self,
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 {
AnchorRangeExt::cmp(&self.0, &other.0, buffer)
}
@@ -1144,7 +1144,7 @@ pub struct FoldRows<'a> {
fold_point: FoldPoint,
}
impl<'a> FoldRows<'a> {
impl FoldRows<'_> {
pub(crate) fn seek(&mut self, row: u32) {
let fold_point = FoldPoint::new(row, 0);
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;
fn next(&mut self) -> Option<Self::Item> {
@@ -1190,7 +1190,7 @@ pub struct FoldChunks<'a> {
max_output_offset: FoldOffset,
}
impl<'a> FoldChunks<'a> {
impl FoldChunks<'_> {
pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
self.transform_cursor.seek(&range.start, Bias::Right, &());

View File

@@ -215,7 +215,7 @@ pub struct InlayChunks<'a> {
snapshot: &'a InlaySnapshot,
}
impl<'a> InlayChunks<'a> {
impl InlayChunks<'_> {
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
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) {
let inlay_point = InlayPoint::new(row, 0);
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;
fn next(&mut self) -> Option<Self::Item> {

View File

@@ -498,7 +498,7 @@ pub struct TabChunks<'a> {
inside_leading_tab: bool,
}
impl<'a> TabChunks<'a> {
impl TabChunks<'_> {
pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
let (input_start, expanded_char_column, to_next_stop) =
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)>,
}
impl<'a> WrapRows<'a> {
impl WrapRows<'_> {
pub(crate) fn seek(&mut self, start_row: u32) {
self.transforms
.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>) {
let output_start = WrapPoint::new(rows.start, 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;
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 {
Ord::cmp(&self.0, &cursor_location.input.lines)
}

View File

@@ -994,7 +994,7 @@ struct RegisteredInlineCompletionProvider {
_subscription: Subscription,
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
struct ActiveDiagnosticGroup {
primary_range: Range<Anchor>,
primary_message: String,
@@ -1031,6 +1031,7 @@ pub enum GotoDefinitionKind {
#[derive(Debug, Clone)]
enum InlayHintRefreshReason {
ModifiersChanged(bool),
Toggle(bool),
SettingsChange(InlayHintSettings),
NewLinesShown,
@@ -1042,6 +1043,7 @@ enum InlayHintRefreshReason {
impl InlayHintRefreshReason {
fn description(&self) -> &'static str {
match self {
Self::ModifiersChanged(_) => "modifiers changed",
Self::Toggle(_) => "toggle",
Self::SettingsChange(_) => "settings change",
Self::NewLinesShown => "new lines shown",
@@ -3668,7 +3670,7 @@ impl Editor {
cx: &mut Context<Self>,
) {
self.refresh_inlay_hints(
InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
cx,
);
}
@@ -3690,21 +3692,44 @@ impl Editor {
| InlayHintRefreshReason::ExcerptsRemoved(_)
);
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) => {
self.inlay_hint_cache.enabled = enabled;
if enabled {
(InvalidationStrategy::RefreshRequested, None)
if self.inlay_hint_cache.toggle(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;
}
} 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;
}
}
@@ -4959,7 +4984,7 @@ impl Editor {
});
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 {
show_in_menu,
@@ -5005,7 +5030,7 @@ impl Editor {
return Some(true);
};
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)
}
@@ -7709,12 +7734,20 @@ impl Editor {
for hunk in &hunks {
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);
if !revert_changes.is_empty() {
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| {
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.unmark_text(window, cx);
@@ -8791,6 +8831,13 @@ impl Editor {
self.change_selections(None, window, cx, |s| {
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.unmark_text(window, cx);
@@ -12517,16 +12564,20 @@ impl Editor {
if is_valid != active_diagnostics.is_valid {
active_diagnostics.is_valid = is_valid;
let mut new_styles = HashMap::default();
for (block_id, diagnostic) in &active_diagnostics.blocks {
new_styles.insert(
*block_id,
diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid),
);
if is_valid {
let mut new_styles = HashMap::default();
for (block_id, diagnostic) in &active_diagnostics.blocks {
new_styles.insert(
*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),
),
height: message_height,
render: diagnostic_block_renderer(diagnostic, None, true, true),
render: diagnostic_block_renderer(diagnostic, None, true),
priority: 0,
}
}),
@@ -13288,8 +13339,9 @@ impl Editor {
return;
}
let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
self.display_map
.update(cx, |display_map, cx| display_map.fold_buffer(buffer_id, cx));
self.display_map.update(cx, |display_map, cx| {
display_map.fold_buffers([buffer_id], cx)
});
cx.emit(EditorEvent::BufferFoldToggled {
ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
folded: true,
@@ -13303,7 +13355,7 @@ impl Editor {
}
let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, 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 {
ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
@@ -13424,13 +13476,13 @@ impl Editor {
pub fn toggle_staged_selected_diff_hunks(
&mut self,
_: &::git::ToggleStaged,
_window: &mut Window,
window: &mut Window,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
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(
@@ -13455,6 +13507,7 @@ impl Editor {
&mut self,
stage: bool,
ranges: &[Range<Anchor>],
window: &mut Window,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -13466,7 +13519,7 @@ impl Editor {
.diff_hunks_in_ranges(&ranges, &snapshot)
.chunk_by(|hunk| hunk.buffer_id);
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<_>>();
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;
}
@@ -13521,7 +13574,7 @@ impl Editor {
buffer.read(cx).remote_id(),
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 mut point = ranges.last().unwrap().end.to_point(&snapshot);
if point.row < snapshot.max_row().0 {
@@ -13535,7 +13588,7 @@ impl Editor {
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);
}
@@ -13545,6 +13598,7 @@ impl Editor {
buffer_id: BufferId,
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
snapshot: &MultiBufferSnapshot,
window: &mut Window,
cx: &mut App,
) {
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
@@ -13587,20 +13641,18 @@ impl Editor {
cx,
)
};
if file_exists {
let buffer_store = project.read(cx).buffer_store().clone();
buffer_store
.update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, 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(
repo.read(cx)
.set_index_text(&path, new_index_text.map(|rope| rope.to_string()))
.log_err(),
)
.detach();
cx.background_spawn(async move { recv.await? })
.detach_and_notify_err(window, cx);
}
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());
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
}
multi_buffer::Event::ExcerptsEdited { ids } => {
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
multi_buffer::Event::ExcerptsEdited {
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 } => {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
@@ -15804,11 +15864,12 @@ impl Editor {
&mut self,
event: FocusOutEvent,
_window: &mut Window,
_cx: &mut Context<Self>,
cx: &mut Context<Self>,
) {
if event.blurred != self.focus_handle {
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>) {
@@ -15864,13 +15925,16 @@ impl Editor {
FILE_HEADER_HEIGHT
}
pub fn revert(
pub fn restore(
&mut self,
revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
window: &mut Window,
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 {
if let Some(buffer) = multi_buffer.buffer(buffer_id) {
buffer.update(cx, |buffer, cx| {
@@ -15882,9 +15946,44 @@ impl Editor {
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());
}
@@ -17294,7 +17393,7 @@ impl Focusable 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 mut text_style = match self.mode {
@@ -17735,7 +17834,6 @@ pub fn diagnostic_block_renderer(
diagnostic: Diagnostic,
max_message_rows: Option<u8>,
allow_closing: bool,
_is_valid: bool,
) -> RenderBlock {
let (text_without_backticks, code_ranges) =
highlight_diagnostic_message(&diagnostic, max_message_rows);

View File

@@ -177,7 +177,7 @@ impl<'de> Deserialize<'de> for ScrollbarDiagnostics {
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
impl serde::de::Visitor<'_> for Visitor {
type Value = ScrollbarDiagnostics;
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]
async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
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]
async fn test_display_diff_hunks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -15544,7 +15716,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
}
#[gpui::test]
async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
async fn test_folding_buffers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
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| {
Editor::new(
EditorMode::Full,
multi_buffer,
multi_buffer.clone(),
Some(project.clone()),
true,
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!(
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| {
@@ -15708,12 +15879,25 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
"After unfolding the second buffer, its text should be displayed"
);
multi_buffer_editor.update(cx, |editor, cx| {
editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
// Typing inside of buffer 1 causes that buffer to be unfolded.
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!(
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"
);
@@ -15722,13 +15906,13 @@ async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
});
assert_eq!(
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"
);
}
#[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, |_| {});
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]
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, |_| {});
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::{
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
},
inlay_hint_settings,
items::BufferSearchHighlights,
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
@@ -19,16 +20,17 @@ use crate::{
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt,
RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt,
ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap, HashSet};
use file_icons::FileIcons;
use git::{blame::BlameEntry, Oid};
use git::{blame::BlameEntry, status::FileStatus, Oid};
use gpui::{
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
@@ -73,7 +75,7 @@ use ui::{
POPOVER_Y_PADDING,
};
use unicode_segmentation::UnicodeSegmentation;
use util::{debug_panic, RangeExt, ResultExt};
use util::{debug_panic, maybe, RangeExt, ResultExt};
use workspace::{item::Item, notifications::NotifyTaskExt};
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
@@ -505,6 +507,25 @@ impl EditorElement {
return;
}
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) {
return;
}
@@ -2646,6 +2667,21 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> 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
.editor
.read(cx)
@@ -2749,12 +2785,36 @@ impl EditorElement {
h_flex()
.gap_2()
.child(
filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into()),
Label::new(
filename
.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| {
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| {
@@ -8720,11 +8780,12 @@ fn diff_hunk_controls(
})
.on_click({
let editor = editor.clone();
move |_event, _, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
false,
&[hunk_range.start..hunk_range.start],
window,
cx,
);
});
@@ -8749,11 +8810,12 @@ fn diff_hunk_controls(
})
.on_click({
let editor = editor.clone();
move |_event, _, cx| {
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
true,
&[hunk_range.start..hunk_range.start],
window,
cx,
);
});

View File

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

View File

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

View File

@@ -36,6 +36,7 @@ pub struct InlayHintCache {
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
version: usize,
pub(super) enabled: bool,
modifiers_override: bool,
enabled_in_settings: bool,
update_tasks: HashMap<ExcerptId, TasksForRanges>,
refresh_task: Task<()>,
@@ -265,6 +266,7 @@ impl InlayHintCache {
Self {
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
enabled: inlay_hint_settings.enabled,
modifiers_override: false,
enabled_in_settings: inlay_hint_settings.enabled,
hints: 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.
if new_hint_settings.enabled != self.enabled_in_settings {
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.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
@@ -323,6 +326,7 @@ impl InlayHintCache {
}
}
(true, false) => {
self.modifiers_override = false;
self.allowed_hint_kinds = new_allowed_hint_kinds;
if self.hints.is_empty() {
ControlFlow::Break(None)
@@ -335,12 +339,39 @@ impl InlayHintCache {
}
}
(false, true) => {
self.modifiers_override = false;
self.allowed_hint_kinds = new_allowed_hint_kinds;
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.
/// 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.
@@ -353,7 +384,8 @@ impl InlayHintCache {
ignore_debounce: bool,
cx: &mut Context<Editor>,
) -> Option<InlaySplice> {
if !self.enabled {
if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
{
return None;
}
let mut invalidated_hints = Vec::new();
@@ -1288,6 +1320,7 @@ pub mod tests {
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: allowed_hint_kinds.contains(&None),
show_background: false,
toggle_on_modifiers_press: None,
})
});
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_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -1493,6 +1527,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
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_other_hints: allowed_hint_kinds.contains(&None),
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -1871,6 +1907,7 @@ pub mod tests {
.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: new_allowed_hint_kinds.contains(&None),
show_background: false,
toggle_on_modifiers_press: None,
})
});
cx.executor().run_until_parked();
@@ -1913,6 +1950,7 @@ pub mod tests {
.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: another_allowed_hint_kinds.contains(&None),
show_background: false,
toggle_on_modifiers_press: None,
})
});
cx.executor().run_until_parked();
@@ -1967,6 +2005,7 @@ pub mod tests {
.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: final_allowed_hint_kinds.contains(&None),
show_background: false,
toggle_on_modifiers_press: None,
})
});
cx.executor().run_until_parked();
@@ -2038,6 +2077,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -2169,6 +2209,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -2467,6 +2508,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -2811,6 +2853,7 @@ pub mod tests {
show_parameter_hints: false,
show_other_hints: false,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -2992,6 +3035,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
cx.executor().run_until_parked();
@@ -3023,6 +3067,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -3114,6 +3159,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
@@ -3187,6 +3233,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});
cx.executor().run_until_parked();
@@ -3246,6 +3293,7 @@ pub mod tests {
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
toggle_on_modifiers_press: None,
})
});

View File

@@ -1739,6 +1739,7 @@ mod tests {
let file = TestFile {
path: Path::new("").into(),
root_name: String::new(),
local_root: 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.
///
/// 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).
/// 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;
fn deref(&self) -> &Self::Target {
self.collection
}
}
impl<'a> DerefMut for MutableSelectionsCollection<'a> {
impl DerefMut for MutableSelectionsCollection<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.collection
}

View File

@@ -1,93 +1,73 @@
use crate::Editor;
use gpui::{App, AppContext as _, Task as AsyncTask, Window};
use gpui::{App, Task, Window};
use project::Location;
use task::{TaskContext, TaskVariables, VariableName};
use text::{ToOffset, ToPoint};
use workspace::Workspace;
fn task_context_with_editor(
editor: &mut Editor,
window: &mut Window,
cx: &mut App,
) -> 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);
impl Editor {
pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
let Some(project) = self.project.clone() else {
return Task::ready(None);
};
let snapshot = editor.snapshot(window, cx);
(selection, buffer, snapshot)
};
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let location = Location {
buffer,
range: start..end,
};
let captured_variables = {
let mut variables = TaskVariables::default();
let buffer = location.buffer.read(cx);
let buffer_id = buffer.remote_id();
let snapshot = buffer.snapshot();
let starting_point = location.range.start.to_point(&snapshot);
let starting_offset = starting_point.to_offset(&snapshot);
for (_, tasks) in editor
.tasks
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
{
if !tasks
.context_range
.contains(&crate::BufferOffset(starting_offset))
let (selection, buffer, editor_snapshot) = {
let selection = self.selections.newest_adjusted(cx);
let Some((buffer, _)) = self
.buffer()
.read(cx)
.point_to_buffer_offset(selection.start, cx)
else {
return Task::ready(None);
};
let snapshot = self.snapshot(window, cx);
(selection, buffer, snapshot)
};
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let location = Location {
buffer,
range: start..end,
};
let captured_variables = {
let mut variables = TaskVariables::default();
let buffer = location.buffer.read(cx);
let buffer_id = buffer.remote_id();
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.insert(
VariableName::Custom(capture_name.to_owned().into()),
value.clone(),
);
}
}
variables
};
variables
};
project.update(cx, |project, cx| {
project.task_store().update(cx, |task_store, cx| {
task_store.task_context_for_location(captured_variables, location, cx)
project.update(cx, |project, cx| {
project.task_store().update(cx, |task_store, 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 api_key = std::env::var("OPENAI_API_KEY").unwrap();
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 client = 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 {
unsafe { EXTENSION.as_deref_mut().unwrap() }
#[expect(static_mut_refs)]
unsafe {
EXTENSION.as_deref_mut().unwrap()
}
}
static mut EXTENSION: Option<Box<dyn Extension>> = None;

View File

@@ -78,10 +78,9 @@ impl HeadlessExtensionStore {
if e.dev {
return true;
}
!self
.loaded_extensions
self.loaded_extensions
.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();

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,7 +145,7 @@ pub async fn match_strings(
let query_char_bag = CharBag::from(&lowercase_query[..]);
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)
.map(|_| Vec::with_capacity(max_results.min(candidates.len())))
.collect::<Vec<_>>();

View File

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

View File

@@ -344,7 +344,7 @@ mod tests {
have_json.push('\n');
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);
if update {

View File

@@ -10,8 +10,11 @@ use rope::Rope;
use schemars::JsonSchema;
use serde::Deserialize;
use std::borrow::Borrow;
use std::env::temp_dir;
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::{
cmp::Ordering,
@@ -200,6 +203,7 @@ impl std::fmt::Debug for dyn GitRepository {
pub struct RealGitRepository {
pub repository: Mutex<git2::Repository>,
pub git_binary_path: PathBuf,
pub askpass_path: PathBuf,
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
}
@@ -207,11 +211,13 @@ impl RealGitRepository {
pub fn new(
repository: git2::Repository,
git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
) -> Self {
Self {
repository: Mutex::new(repository),
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
askpass_path,
hosting_provider_registry,
}
}
@@ -358,24 +364,30 @@ impl GitRepository for RealGitRepository {
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)
.args(["update-index", "--add", "--cacheinfo", "100644", &sha])
.arg(path.as_ref())
.status()?;
.output()?;
if !status.success() {
return Err(anyhow!("Failed to add to index: {status:?}"));
if !output.status.success() {
return Err(anyhow!(
"Failed to stage:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
} else {
let status = new_std_command(&self.git_binary_path)
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["update-index", "--force-remove"])
.arg(path.as_ref())
.status()?;
.output()?;
if !status.success() {
return Err(anyhow!("Failed to remove from index: {status:?}"));
if !output.status.success() {
return Err(anyhow!(
"Failed to unstage:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
}
@@ -602,7 +614,10 @@ impl GitRepository for RealGitRepository {
) -> Result<()> {
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)
.args(["push", "--quiet"])
.args(options.map(|option| match option {
@@ -618,18 +633,20 @@ impl GitRepository for RealGitRepository {
"Failed to push:\n{}",
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<()> {
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)
.args(["pull", "--quiet"])
.args(["pull"])
.arg(remote_name)
.arg(branch_name)
.output()?;
@@ -639,16 +656,18 @@ impl GitRepository for RealGitRepository {
"Failed to pull:\n{}",
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<()> {
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)
.args(["fetch", "--quiet", "--all"])
.output()?;
@@ -658,10 +677,9 @@ impl GitRepository for RealGitRepository {
"Failed to fetch:\n{}",
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>> {
@@ -1016,7 +1034,7 @@ impl Borrow<Path> for RepoPath {
#[derive(Debug)]
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 {
if key.starts_with(self.0) {
Ordering::Greater

View File

@@ -12,6 +12,10 @@ workspace = true
name = "git_ui"
path = "src/git_ui.rs"
[features]
default = []
test-support = ["multi_buffer/test-support"]
[dependencies]
anyhow.workspace = true
buffer_diff.workspace = true
@@ -47,6 +51,10 @@ zed_actions.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true
[features]
default = []
test-support = ["multi_buffer/test-support"]
[dev-dependencies]
editor = { workspace = true, features = ["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 git::repository::Branch;
use gpui::{
rems, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
Task, WeakEntity, Window,
Task, Window,
};
use picker::{Picker, PickerDelegate};
use project::ProjectPath;
use project::{Project, ProjectPath};
use std::sync::Arc;
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use ui::{
prelude::*, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenuHandle, TriggerablePopover,
};
use util::ResultExt;
use workspace::notifications::DetachAndPromptErr;
use workspace::{ModalView, Workspace};
@@ -23,19 +25,29 @@ pub fn init(cx: &mut App) {
}
pub fn open(
_: &mut Workspace,
workspace: &mut Workspace,
_: &zed_actions::git::Branch,
window: &mut Window,
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 {
// 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| {
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)
}
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 picker: Entity<Picker<BranchListDelegate>>,
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 {
pub fn new(
delegate: BranchListDelegate,
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));
fn new(project: Entity<Project>, style: BranchListStyle, rem_width: f32, cx: &mut App) -> Self {
let popover_handle = PopoverMenuHandle::default();
Self {
picker,
project,
picker: None,
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 EventEmitter<DismissEvent> for BranchList {}
impl Focusable for BranchList {
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 {
v_flex()
.w(rems(self.rem_width))
.child(self.picker.clone())
.on_mouse_down_out(cx.listener(|this, _, window, cx| {
this.picker.update(cx, |this, cx| {
this.cancel(&Default::default(), window, cx);
})
}))
.map(|parent| match self.picker.as_ref() {
Some(picker) => parent.child(picker.clone()).on_mouse_down_out({
let picker = picker.clone();
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 {
matches: Vec<BranchEntry>,
all_branches: Vec<Branch>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
style: BranchListStyle,
selected_index: usize,
last_query: String,
/// Max length of branch name before we truncate it and add a trailing `...`.
@@ -116,13 +194,14 @@ pub struct BranchListDelegate {
}
impl BranchListDelegate {
pub async fn new(
workspace: Entity<Workspace>,
async fn new(
project: Entity<Project>,
style: BranchListStyle,
branch_name_trailoff_after: usize,
cx: &AsyncApp,
) -> Result<Self> {
let all_branches_request = cx.update(|cx| {
let project = workspace.read(cx).project().read(cx);
let project = project.read(cx);
let first_worktree = project
.visible_worktrees(cx)
.next()
@@ -135,7 +214,8 @@ impl BranchListDelegate {
Ok(Self {
matches: vec![],
workspace: workspace.downgrade(),
project,
style,
all_branches,
selected_index: 0,
last_query: Default::default(),
@@ -254,18 +334,12 @@ impl PickerDelegate for BranchListDelegate {
return;
};
let current_branch = self
.workspace
.update(cx, |workspace, cx| {
workspace
.project()
.read(cx)
.active_repository(cx)
.and_then(|repo| repo.read(cx).current_branch())
.map(|branch| branch.name.to_string())
})
.ok()
.flatten();
let current_branch = self.project.update(cx, |project, cx| {
project
.active_repository(cx)
.and_then(|repo| repo.read(cx).current_branch())
.map(|branch| branch.name.to_string())
});
if current_branch == Some(branch.name().to_string()) {
cx.emit(DismissEvent);
@@ -276,13 +350,7 @@ impl PickerDelegate for BranchListDelegate {
let branch = branch.clone();
|picker, mut cx| async move {
let branch_change_task = picker.update(&mut cx, |this, cx| {
let workspace = this
.delegate
.workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was dropped"))?;
let project = workspace.read(cx).project().read(cx);
let project = this.delegate.project.read(cx);
let branch_to_checkout = match branch {
BranchEntry::Branch(branch) => branch.string,
BranchEntry::History(string) => string,
@@ -327,6 +395,10 @@ impl PickerDelegate for BranchListDelegate {
Some(
ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
.inset(true)
.spacing(match self.style {
BranchListStyle::Modal => ListItemSpacing::default(),
BranchListStyle::Popover => ListItemSpacing::ExtraDense,
})
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.when(matches!(hit, BranchEntry::History(_)), |el| {

View File

@@ -1,9 +1,11 @@
// #![allow(unused, dead_code)]
use crate::branch_picker::{self, BranchList};
use crate::git_panel::{commit_message_editor, GitPanel};
use git::Commit;
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 gpui::*;
@@ -64,6 +66,7 @@ pub fn init(cx: &mut App) {
}
pub struct CommitModal {
branch_list: Entity<BranchList>,
git_panel: Entity<GitPanel>,
commit_editor: Entity<Editor>,
restore_dock: RestoreDock,
@@ -139,9 +142,11 @@ impl CommitModal {
is_open,
active_index,
};
let project = workspace.project().clone();
workspace.open_panel::<GitPanel>(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(
git_panel: Entity<GitPanel>,
restore_dock: RestoreDock,
project: Entity<Project>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -159,7 +165,7 @@ impl CommitModal {
git_panel.set_modal_open(true, cx);
let buffer = git_panel.commit_message_buffer(cx).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);
@@ -182,14 +188,21 @@ impl CommitModal {
let focus_handle = commit_editor.focus_handle(cx);
cx.on_focus_out(&focus_handle, window, |_, _, _, cx| {
cx.emit(DismissEvent);
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
if !this
.branch_list
.focus_handle(cx)
.contains_focused(window, cx)
{
cx.emit(DismissEvent);
}
})
.detach();
let properties = ModalContainerProperties::new(window, 50);
Self {
branch_list: branch_picker::popover(project.clone(), window, cx),
git_panel,
commit_editor,
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 (branch, tooltip, commit_label, co_authors) =
@@ -238,7 +251,12 @@ impl CommitModal {
let branch = git_panel
.active_repository
.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());
let tooltip = if git_panel.has_staged_changes() {
"Commit staged changes"
@@ -248,13 +266,13 @@ impl CommitModal {
let title = if git_panel.has_staged_changes() {
"Commit"
} else {
"Commit Tracked"
"Commit All"
};
let co_authors = git_panel.render_co_authors(cx);
(branch, tooltip, title, co_authors)
});
let branch_selector = panel_button(branch)
let branch_picker_button = panel_button(branch)
.icon(IconName::GitBranch)
.icon_size(IconSize::Small)
.icon_color(Color::Placeholder)
@@ -269,6 +287,13 @@ impl CommitModal {
}))
.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 =
if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) {
Some(
@@ -303,7 +328,12 @@ impl CommitModal {
.w_full()
.h(px(self.properties.footer_height))
.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(
h_flex()
@@ -340,6 +370,13 @@ impl Render for CommitModal {
.key_context("GitCommit")
.on_action(cx.listener(Self::dismiss))
.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)
.overflow_hidden()
.flex_none()

View File

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

View File

@@ -47,6 +47,7 @@ pub struct ProjectDiff {
_subscription: Subscription,
}
#[derive(Debug)]
struct DiffBuffer {
path_key: PathKey,
buffer: Entity<Buffer>,
@@ -105,7 +106,7 @@ impl ProjectDiff {
};
if let Some(entry) = entry {
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,
entry: GitStatusEntry,
window: &mut Window,
@@ -188,16 +189,16 @@ impl ProjectDiff {
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) {
self.editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
s.select_ranges([position..position]);
})
})
});
} else {
self.pending_scroll = Some(path_key);
}
@@ -280,6 +281,7 @@ impl ProjectDiff {
let snapshot = multibuffer.snapshot(cx);
let mut point = anchor.to_point(&snapshot);
point.row = (point.row + 1).min(snapshot.max_row().0);
point.column = 0;
let Some((_, buffer, _)) = self.multibuffer.read(cx).excerpt_containing(point, cx)
else {
@@ -384,21 +386,28 @@ impl ProjectDiff {
.collect::<Vec<_>>()
};
let is_excerpt_newly_added = self.multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_excerpts_for_path(
let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
let was_empty = multibuffer.is_empty();
let is_newly_added = multibuffer.set_excerpts_for_path(
path_key.clone(),
buffer,
diff_hunk_ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
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)
});
}
}
});
if self.multibuffer.read(cx).is_empty()
&& self
@@ -414,7 +423,7 @@ impl ProjectDiff {
});
}
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)]
pub struct AppRef<'a>(Ref<'a, App>);
impl<'a> Drop for AppRef<'a> {
impl Drop for AppRef<'_> {
fn drop(&mut self) {
if option_env!("TRACK_THREAD_BORROWS").is_some() {
let thread_id = std::thread::current().id();
@@ -95,7 +95,7 @@ impl<'a> Drop for AppRef<'a> {
#[derive(Deref, DerefMut)]
pub struct AppRefMut<'a>(RefMut<'a, App>);
impl<'a> Drop for AppRefMut<'a> {
impl Drop for AppRefMut<'_> {
fn drop(&mut self) {
if option_env!("TRACK_THREAD_BORROWS").is_some() {
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.
pub fn emit<Evt>(&mut self, event: Evt)
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;
fn new<U: 'static>(

View File

@@ -191,7 +191,7 @@ pub(crate) struct Lease<'a, 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;
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 {
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) {
if self.entity.is_some() && !panicking() {
panic!("Leases must be ended with EntityMap::end_lease")

View File

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

View File

@@ -1,4 +1,5 @@
use serde::Deserialize;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
error::Error,
fmt::{Display, Write},
@@ -306,24 +307,29 @@ impl std::fmt::Display for Keystroke {
}
/// 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 {
/// The control key
#[serde(default)]
pub control: bool,
/// The alt key
/// Sometimes also known as the 'meta' key
#[serde(default)]
pub alt: bool,
/// The shift key
#[serde(default)]
pub shift: bool,
/// The command key, on macos
/// the windows key, on windows
/// the super key, on linux
#[serde(default)]
pub platform: bool,
/// The function key
#[serde(default)]
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),
/// which are needed for copying rich text (that is, text intermingled with images)
/// to the clipboard. This adds access to those APIs.
#[allow(non_snake_case)]
pub trait NSAttributedString: Sized {
unsafe fn alloc(_: Self) -> id {

View File

@@ -26,7 +26,7 @@ pub(crate) mod dispatch_sys {
use dispatch_sys::*;
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 {

View File

@@ -1221,10 +1221,10 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
unsafe {
type SetWindowCompositionAttributeType =
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);
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 =
std::mem::transmute(GetProcAddress(user32.unwrap(), func_name));
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)
| ((color.1 as u32) << 8)
| ((color.2 as u32) << 16)
| (color.3 as u32) << 24,
| ((color.3 as u32) << 24),
animation_id: 0,
};
let mut data = WINDOWCOMPOSITIONATTRIBDATA {

View File

@@ -670,6 +670,15 @@ pub struct TextRun {
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`].
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]

View File

@@ -601,15 +601,15 @@ struct CacheKeyRef<'a> {
wrap_width: Option<Pixels>,
}
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
impl PartialEq for (dyn AsCacheKeyRef + '_) {
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
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) {
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 {
*self
}

View File

@@ -543,14 +543,6 @@ mod tests {
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 lines = text_system
.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"))]
@@ -59,7 +72,7 @@ where
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
#[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 {
use backtrace::{BacktraceFmt, BytesOrWideString};

View File

@@ -3689,8 +3689,6 @@ impl Window {
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.
pub fn listener_for<V: Render, E>(
&self,

View File

@@ -79,7 +79,7 @@ pub trait HttpClient: 'static + Send + Sync {
.body(body);
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()) }),
}
}
@@ -96,7 +96,7 @@ pub trait HttpClient: 'static + Send + Sync {
.body(body);
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()) }),
}
}

View File

@@ -456,7 +456,7 @@ impl InlineCompletionButton {
}
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, {
let fs = fs.clone();
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx)
@@ -592,8 +592,8 @@ impl InlineCompletionButton {
if cx.has_flag::<feature_flags::PredictEditsNonEagerModeFeatureFlag>() {
let is_eager_preview_enabled = match settings.edit_predictions_mode() {
language::EditPredictionsMode::Auto => false,
language::EditPredictionsMode::EagerPreview => true,
language::EditPredictionsMode::Subtle => false,
language::EditPredictionsMode::Eager => true,
};
menu = menu.separator().toggleable_entry(
"Eager Preview Mode",
@@ -608,8 +608,8 @@ impl InlineCompletionButton {
cx,
move |settings, _cx| {
let new_mode = match is_eager_preview_enabled {
true => language::EditPredictionsMode::Auto,
false => language::EditPredictionsMode::EagerPreview,
true => language::EditPredictionsMode::Subtle,
false => language::EditPredictionsMode::Eager,
};
if let Some(edit_predictions) = settings.edit_predictions.as_mut() {
@@ -702,7 +702,7 @@ impl InlineCompletionButton {
Some(
file.map(|file| {
all_language_settings(Some(file), cx)
.inline_completions_enabled_for_path(file.path())
.edit_predictions_enabled_for_file(file, cx)
})
.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) {
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, _| {
file.defaults.show_edit_predictions = Some(!show_edit_predictions)
});
@@ -845,7 +845,7 @@ fn toggle_show_inline_completions_for_language(
cx: &mut App,
) {
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, _| {
file.languages
.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;
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 {
self.context
}
@@ -3507,24 +3507,13 @@ impl BufferSnapshot {
true,
);
let mut last_buffer_range_end = 0;
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(' ');
}
last_buffer_range_end = buffer_range.end;
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 before_append_len = text.len();
let mut offset = buffer_range.start;
chunks.seek(buffer_range.clone());
for mut chunk in chunks.by_ref() {
@@ -3548,6 +3537,16 @@ impl BufferSnapshot {
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 {
@@ -4138,7 +4137,7 @@ impl Deref for BufferSnapshot {
}
}
unsafe impl<'a> Send for BufferChunks<'a> {}
unsafe impl Send for BufferChunks<'_> {}
impl<'a> BufferChunks<'a> {
pub(crate) fn new(
@@ -4478,6 +4477,7 @@ impl IndentSize {
pub struct TestFile {
pub path: Arc<Path>,
pub root_name: String,
pub local_root: Option<PathBuf>,
}
#[cfg(any(test, feature = "test-support"))]
@@ -4491,7 +4491,11 @@ impl File for TestFile {
}
fn as_local(&self) -> Option<&dyn LocalFile> {
None
if self.local_root.is_some() {
Some(self)
} else {
None
}
}
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(
values: impl Iterator<Item = u32>,
max_len: usize,

View File

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

View File

@@ -9,7 +9,7 @@ use ec4rs::{
Properties as EditorconfigProperties,
};
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::App;
use gpui::{App, Modifiers};
use itertools::{Either, Itertools};
use schemars::{
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.
/// This list adds to a pre-existing, sensible default set of globs.
/// 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.
pub mode: EditPredictionsMode,
/// Settings specific to GitHub Copilot.
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.
@@ -244,10 +267,12 @@ pub struct EditPredictionSettings {
pub enum EditPredictionsMode {
/// If provider supports it, display inline when holding modifier key (e.g., alt).
/// Otherwise, eager preview is used.
Auto,
#[serde(alias = "auto")]
Subtle,
/// Display inline when there are no language server completions available.
#[default]
EagerPreview,
#[serde(alias = "eager_preview")]
Eager,
}
#[derive(Clone, Debug, Default)]
@@ -478,6 +503,10 @@ pub struct EditPredictionSettingsContent {
/// Settings specific to GitHub Copilot.
#[serde(default)]
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)]
@@ -903,6 +932,13 @@ pub struct InlayHintSettings {
/// Default: 50
#[serde(default = "scroll_debounce_ms")]
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 {
@@ -963,16 +999,12 @@ impl AllLanguageSettings {
}
/// Returns whether edit predictions are enabled for the given path.
pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
!self
.edit_predictions
.disabled_globs
.iter()
.any(|glob| glob.is_match(path))
pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
self.edit_predictions.enabled_for_file(file, cx)
}
/// 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)
.show_edit_predictions
}
@@ -1101,6 +1133,12 @@ impl settings::Settings for AllLanguageSettings {
})
.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();
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() {
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() {
completion_globs.extend(disabled_globs.iter());
@@ -1197,10 +1236,16 @@ impl settings::Settings for AllLanguageSettings {
},
disabled_globs: completion_globs
.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(),
mode: edit_predictions_mode,
copilot: copilot_settings,
enabled_in_assistant: edit_predictions_enabled_in_assistant,
},
defaults,
languages,
@@ -1355,6 +1400,8 @@ pub struct PrettierSettings {
#[cfg(test)]
mod tests {
use gpui::TestAppContext;
use super::*;
#[test]
@@ -1399,6 +1446,132 @@ mod tests {
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]
pub fn test_resolve_language_servers() {
fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {

View File

@@ -836,7 +836,7 @@ impl SyntaxSnapshot {
}
#[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)
.collect()
}
@@ -1142,7 +1142,7 @@ impl<'a> SyntaxMapMatches<'a> {
}
}
impl<'a> SyntaxMapCapturesLayer<'a> {
impl SyntaxMapCapturesLayer<'_> {
fn advance(&mut self) {
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) {
if let Some(mat) = self.matches.next() {
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 {
Ord::cmp(&self.depth, &cursor_location.max_depth)
.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 {
Ord::cmp(&self.depth, &cursor_location.max_depth)
.then_with(|| self.position.cmp(&cursor_location.range.end, text))
}
}
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary>
for SyntaxLayerPositionBeforeChange
{
impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPositionBeforeChange {
fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
if self.change.cmp(cursor_location, buffer).is_le() {
Ordering::Less
@@ -1869,7 +1867,7 @@ struct LogPoint(Point);
struct LogAnchorRange<'a>(&'a Range<Anchor>, &'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 {
f.debug_list()
.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 {
let range = self.0.to_point(self.1);
(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 {
f.debug_list()
.entries(

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
use std::io::{Cursor, Write};
use std::sync::Arc;
use crate::role::Role;
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
@@ -167,7 +168,7 @@ impl LanguageModelImage {
pub struct LanguageModelToolResult {
pub tool_use_id: LanguageModelToolUseId,
pub is_error: bool,
pub content: String,
pub content: Arc<str>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
@@ -199,15 +200,16 @@ pub struct LanguageModelRequestMessage {
impl LanguageModelRequestMessage {
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 {
MessageContent::Text(text) => Some(text),
MessageContent::ToolResult(tool_result) => Some(&tool_result.content),
MessageContent::Text(text) => Some(text.as_str()),
MessageContent::ToolResult(tool_result) => Some(tool_result.content.as_ref()),
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 {

View File

@@ -2,17 +2,26 @@ use std::sync::Arc;
use feature_flags::ZedPro;
use gpui::{
Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle,
Focusable, Subscription, Task, WeakEntity,
action_with_deprecated_aliases, Action, AnyElement, App, Corner, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
};
use language_model::{
AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry,
};
use picker::{Picker, PickerDelegate};
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;
action_with_deprecated_aliases!(
assistant,
ToggleModelSelector,
["assistant2::ToggleModelSelector"]
);
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
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 language model registry.
update_matches_task: Option<Task<()>>,
popover_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
_authenticate_all_providers_task: Task<()>,
_subscriptions: Vec<Subscription>,
}
@@ -53,6 +63,7 @@ impl LanguageModelSelector {
LanguageModelSelector {
picker,
update_matches_task: None,
popover_menu_handle: PopoverMenuHandle::default(),
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
_subscriptions: vec![cx.subscribe_in(
&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(
&mut self,
_registry: &Entity<LanguageModelRegistry>,
@@ -181,62 +201,13 @@ impl Render for LanguageModelSelector {
}
}
#[derive(IntoElement)]
pub struct LanguageModelSelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
language_model_selector: Entity<LanguageModelSelector>,
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),
})
impl TriggerablePopover for LanguageModelSelector {
fn menu_handle(
&mut self,
_window: &mut Window,
_cx: &mut gpui::Context<Self>,
) -> PopoverMenuHandle<Self> {
self.popover_menu_handle.clone()
}
}
@@ -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;
mod settings;
pub mod ui;
use crate::provider::anthropic::AnthropicLanguageModelProvider;
use crate::provider::bedrock::BedrockLanguageModelProvider;

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