Compare commits

..

211 Commits

Author SHA1 Message Date
Conrad Irwin
111583b863 WIP: enforce buffer ordering in multibuffer 2025-01-29 22:44:17 -07:00
Osvaldo
9e31b1019e vim: Add any brackets to support motions like ab and ib to work with any type of brackets (#23679)
# Add AnyBrackets text object for Vim mode

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

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

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

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

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

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

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

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

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

Release Notes:

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

Release Notes:

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

Release Notes:

- N/A

---------

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

Release Notes:

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

Release Notes:

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

**After**

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

**Before**

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

Release Notes:

- N/A

---------

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

(hopefully 🤞)

Release Notes:

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

Release Notes:

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

Release Notes:

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

Closes #ISSUE

Release Notes:

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

Release Notes:

- N/A

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

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

Release Notes:

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

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

Usage:

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

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

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

---

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

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

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

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

---


Release Notes:

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

---------

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

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

Release Notes:

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

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

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

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

Release Notes:

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

Closes #ISSUE

Release Notes:

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

Release Notes:

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

Here's an example:

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

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

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

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

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

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

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

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



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

Release Notes:

- N/A

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

Release Notes:

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

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

Release notes:

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

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

Release Notes:

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

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

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

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

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

Release Notes:

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

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

**Cause**: 

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

**Solution**:

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

**Alternate Solution**:

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

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

**Preview**:

Before:


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

After:


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


Release Notes:

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

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

This works just like VS Code does.


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

Release Notes:

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

---------

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

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

Context:

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

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

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

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

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


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

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

In example, `dist` dir is ignored dir.


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

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


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

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

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


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


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

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

Release Notes:

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

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

---

### Release Notes

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

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

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

41 pull requests were merged this release cycle.

##### Added

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

##### Changed

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

##### Fixed

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

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

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

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

Release Notes:

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

Release Notes:

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

- N/A

----------

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

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

## Before


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

## After


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

Release Notes:

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

This is how the opening of a file works currently:

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

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

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

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

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

Release Notes:

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

Release Notes:

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

Release Notes:

- N/A

---------

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

Release Notes:

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

Release Notes:

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

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

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

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

Release Notes:

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

Release Notes:

- Added support for DeepSeek to the Assistant.

---------

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

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

Release Notes:

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

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

Release Notes:

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

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

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

Release Notes:

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

### Before

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

### After

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

Release Notes:

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

### Before


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

### After

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


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

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

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

Release Notes:

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

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

Release Notes:

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

<details>
<summary>Panic</summary>

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

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

Release Notes:

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

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

Release Notes:

- N/A

---------

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

- Added auto-completion support for snippet files.


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

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

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

Release Notes:

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

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

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

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

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

Release Notes:

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

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

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

Release Notes:

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

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

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

Release Notes:

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

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

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

Tasks:

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

### issues post merge

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



Release Notes:

- N/A

---------

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

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

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

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

Release Notes:

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


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

* Fixes incorrect line selection for certain corner cases

Before:

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

After:

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


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

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

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

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

Release Notes:

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

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

I updated the original PR changelog line.

Release Notes:

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


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


After:


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


Release Notes:

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

Release Notes:

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

Release Notes:

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

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


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

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


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


Release Notes:

- N/A
2025-01-24 21:35:24 -07:00
Conrad Irwin
b7c6ffa6c2 Fix panic when multi-cursor edit attempted in deleted hunk (#23633)
Release Notes:

- N/A
2025-01-24 16:14:19 -07:00
Agus Zubiaga
ba16b4eb65 assistant2: Show accept terms UI in thread empty state (#23630)
<img
src="https://github.com/user-attachments/assets/cea93cfb-8a40-48c4-9d90-f1751c79603b"
width=400>



Release Notes:

- N/A

---------

Co-authored-by: Danilo <danilo@zed.dev>
2025-01-24 19:34:46 -03:00
Michael Sloan
ec5d02d5c2 Use send instead of feed on terminal events channel (#23631)
Potentially fixes a bug where tasks are not marked as finished.

Release Notes:

- N/A
2025-01-24 22:32:22 +00:00
Max Brunsfeld
d2c55cbe3d Rework diff rendering to allow putting the cursor into deleted text, soft-wrapping and scrolling deleted text correctly (#22994)
Closes #12553

* [x] Fix `diff_hunk_before`
* [x] Fix failure to show deleted text when expanding hunk w/ cursor on
second line of the hunk
* [x] Failure to expand diff hunk below the cursor.
* [x] Delete the whole file, and expand the diff. Backspace over the
deleted hunk, panic!
* [x] Go-to-line now counts the diff hunks, but it should not
* [x] backspace at the beginning of a deleted hunk deletes too much text
* [x] Indent guides are rendered incorrectly 
* [ ] Fix randomized multi buffer tests

Maybe:
* [ ] Buffer search should include deleted text (in vim mode it turns
out I use `/x` all the time to jump to the next x I can see).
* [ ] vim: should refuse to switch into insert mode if selection is
fully within a diff.
* [ ] vim `o` command when cursor is on last line of deleted hunk.
* [ ] vim `shift-o` on first line of deleted hunk moves cursor but
doesn't insert line
* [x] `enter` at end of diff hunk inserts a new line but doesn't move
cursor
* [x] (`shift-enter` at start of diff hunk does nothing)
* [ ] Inserting a line just before an expanded hunk collapses it

Release Notes:


- Improved diff rendering, allowing you to navigate with your cursor
inside of deleted text in diff hunks.

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Cole <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Michael <michael@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: João <joao@zed.dev>
2025-01-24 14:18:22 -07:00
renovate[bot]
1fdae4bae0 Update Rust crate tokio to v1.43.0 (#22882)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tokio](https://tokio.rs)
([source](https://redirect.github.com/tokio-rs/tokio)) | dependencies |
minor | `1.42.0` -> `1.43.0` |
| [tokio](https://tokio.rs)
([source](https://redirect.github.com/tokio-rs/tokio)) |
workspace.dependencies | minor | `1.42.0` -> `1.43.0` |

---

### Release Notes

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

###
[`v1.43.0`](https://redirect.github.com/tokio-rs/tokio/releases/tag/tokio-1.43.0):
Tokio v1.43.0

[Compare
Source](https://redirect.github.com/tokio-rs/tokio/compare/tokio-1.42.0...tokio-1.43.0)

### 1.43.0 (Jan 8th, 2025)

##### Added

-   net: add `UdpSocket::peek` methods ([#&#8203;7068])
-   net: add support for Haiku OS ([#&#8203;7042])
-   process: add `Command::into_std()` ([#&#8203;7014])
-   signal: add `SignalKind::info` on illumos ([#&#8203;6995])
-   signal: add support for realtime signals on illumos ([#&#8203;7029])

##### Fixed

- io: don't call `set_len` before initializing vector in `Blocking`
([#&#8203;7054])
- macros: suppress `clippy::needless_return` in `#[tokio::main]`
([#&#8203;6874])
-   runtime: fix thread parking on WebAssembly ([#&#8203;7041])

##### Changes

-   chore: use unsync loads for `unsync_load` ([#&#8203;7073])
-   io: use `Buf::put_bytes` in `Repeat` read impl ([#&#8203;7055])
-   task: drop the join waker of a task eagerly ([#&#8203;6986])

##### Changes to unstable APIs

- metrics: improve flexibility of H2Histogram Configuration
([#&#8203;6963])
-   taskdump: add accessor methods for backtrace ([#&#8203;6975])

##### Documented

- io: clarify `ReadBuf::uninit` allows initialized buffers as well
([#&#8203;7053])
- net: fix ambiguity in `TcpStream::try_write_vectored` docs
([#&#8203;7067])
-   runtime: fix `LocalRuntime` doc links ([#&#8203;7074])
- sync: extend documentation for `watch::Receiver::wait_for`
([#&#8203;7038])
-   sync: fix typos in `OnceCell` docs ([#&#8203;7047])

[#&#8203;6874]: https://redirect.github.com/tokio-rs/tokio/pull/6874

[#&#8203;6963]: https://redirect.github.com/tokio-rs/tokio/pull/6963

[#&#8203;6975]: https://redirect.github.com/tokio-rs/tokio/pull/6975

[#&#8203;6986]: https://redirect.github.com/tokio-rs/tokio/pull/6986

[#&#8203;6995]: https://redirect.github.com/tokio-rs/tokio/pull/6995

[#&#8203;7014]: https://redirect.github.com/tokio-rs/tokio/pull/7014

[#&#8203;7029]: https://redirect.github.com/tokio-rs/tokio/pull/7029

[#&#8203;7038]: https://redirect.github.com/tokio-rs/tokio/pull/7038

[#&#8203;7041]: https://redirect.github.com/tokio-rs/tokio/pull/7041

[#&#8203;7042]: https://redirect.github.com/tokio-rs/tokio/pull/7042

[#&#8203;7047]: https://redirect.github.com/tokio-rs/tokio/pull/7047

[#&#8203;7053]: https://redirect.github.com/tokio-rs/tokio/pull/7053

[#&#8203;7054]: https://redirect.github.com/tokio-rs/tokio/pull/7054

[#&#8203;7055]: https://redirect.github.com/tokio-rs/tokio/pull/7055

[#&#8203;7067]: https://redirect.github.com/tokio-rs/tokio/pull/7067

[#&#8203;7068]: https://redirect.github.com/tokio-rs/tokio/pull/7068

[#&#8203;7073]: https://redirect.github.com/tokio-rs/tokio/pull/7073

[#&#8203;7074]: https://redirect.github.com/tokio-rs/tokio/pull/7074

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-24 22:41:04 +02:00
Cole Miller
9d8d21a4dc git: Disable "stage all" checkbox when no entries (#23608)
Release Notes:

- N/A
2025-01-24 15:29:34 -05:00
Michael Sloan
40c18be541 Make editor autoscroll put cursor to the left of scrollbar not under (#23586)
Closes #19706

Release Notes:

- Improved editor horizontal autoscroll to now place the cursor to the
left of the scrollbar rather than under it.
2025-01-24 12:58:17 -07:00
Peter Tripp
15d041c97c Document elm-language-server not supporting linked_edits correctly (#23616) 2025-01-24 18:30:38 +00:00
Peter Tripp
ed54af337d Support yaml-language-server as formatter without lsp settings (#23612)
- Closes: https://github.com/zed-industries/zed/issues/20183
2025-01-24 12:51:16 -05:00
Nate Butler
d5c7e0b1e3 git_ui: Add keybinding for focusing the git panel (#23613)
Adds a keybinding for opening/toggling focus to the git panel

Release Notes:

- N/A
2025-01-24 17:20:26 +00:00
Piotr Osiewicz
77e9d01b39 task: Always use untruncated label if it is short (#23611)
Also changed rust tasks to be less mouthful.

Release Notes:

- Shortened Rust task labels.
- Task modal will now use full task label when it does not require
truncation.
2025-01-24 16:46:48 +00:00
Danilo Leal
7c2b17540b assistant2: Adjust empty state when there is no provider (#23609)
This PR add a "Configure a Provider" button if the user gets to the
assistant panel with no provider configured. Then, upon configuring it,
they'll see a similar welcome message.

| No provider | Empty state |
|--------|--------|
| <img width="1233" alt="Screenshot 2025-01-24 at 12 25 48 PM"
src="https://github.com/user-attachments/assets/2f3c602f-9e46-4c79-95cd-4bb3717f68a3"
/> | <img width="1233" alt="Screenshot 2025-01-24 at 12 26 01 PM"
src="https://github.com/user-attachments/assets/a4a204dd-9531-45ab-89a2-f1d84f375a7b"
/> |

Release Notes:

- N/A
2025-01-24 12:51:14 -03:00
Danilo Leal
fc3a871264 assistant2: Adjust spacing and icons on the context picker (#23607)
Just fine-tuning spacing, icon size and color, and ensure they're are
consistent throughout.

Release Notes:

- N/A
2025-01-24 12:34:11 -03:00
Piotr Osiewicz
8efed4c449 search: Move invalid UTF-8 errors to debug level (#23602)
Closes #ISSUE

Release Notes:

- N/A
2025-01-24 14:11:45 +01:00
Danilo Leal
7b69c4246a title_bar: Use an IconButton for the user menu (#23601)
That's specifically when we're not rendering the user menu with an
Avatar. We were previously rendering a `ButtonLike` with unnecessary
flex styles there. Just a little fine-tune.

Release Notes:

- N/A
2025-01-24 09:49:50 -03:00
Danilo Leal
ad63bdf65b assistant2: Disable the Submit button when missing requirements (#23598)
This PR disables the Assistant 2 Submit button when either there is no
message written in the editor or there's no model selected. To guide the
user, there will be a tooltip displayed on top of the button to indicate
what to do.

Release Notes:

- N/A
2025-01-24 09:44:20 -03:00
Danilo Leal
802d7421bc assistant: Adjust the ToS acceptance card design (#23599)
Just fine-tuning the copywriting and design here.

| Before | After |
|--------|--------|
| <img width="1233" alt="Screenshot 2025-01-24 at 9 28 30 AM"
src="https://github.com/user-attachments/assets/ca91a985-8a20-4ece-b0e4-3a6779db2fda"
/> | <img width="1233" alt="Screenshot 2025-01-24 at 9 27 49 AM"
src="https://github.com/user-attachments/assets/edc9c2ef-4ae0-4caf-a496-9887748673c9"
/> |

Release Notes:

- N/A
2025-01-24 09:44:09 -03:00
Danilo Leal
f2c2ed0ccd Allow the context menu to take an icon_color (#23600)
Doing this to enable customization in the Assistant 2 context picker.

Release Notes:

- N/A
2025-01-24 09:44:00 -03:00
Michael Sloan
813bbecd5c Fix terminal memory leak by deduping alacritty events on background thread (#23593)
Closes #23008

Release Notes:

- Fixed case where the terminal can leak memory when it produces events
at a faster rate than could be processed.
2025-01-24 10:25:03 +00:00
Shivam Mishra
dd8ee76b2e docs: context_servers json example (#23588) 2025-01-24 08:10:59 +00:00
Marshall Bowers
c55cdd0cb9 assistant2: Add thread persistence (#23582)
This PR adds persistence for threads in Assistant2.

Threads are now persisted to an LMDB database.

Release Notes:

- N/A
2025-01-24 00:09:26 +00:00
Piotr Osiewicz
fb63f61755 search: Add heuristic for discarding matching of binary files (#23581)
Fixes #23398 
Closes #23398

We'll bail on searches of files that we know are binary (thus even if we
were to find a match in them, they'd be thrown away by buffer loader).

Release Notes:

- Improved project search performance in worktrees with binary files
2025-01-23 22:15:58 +00:00
Marshall Bowers
35ddb432b3 assistant_context_editor: Put uses in the right spot (#23579)
This PR cleans up some `use` statements that weren't at the very top of
the module.

Release Notes:

- N/A
2025-01-23 21:58:33 +00:00
Marshall Bowers
ec91a8dc82 assistant2: Expose ActiveThread::thread via a getter (#23577)
This PR exposes the `thread` file on the `ActiveThread` via a getter
rather than exposing the field directly.

Release Notes:

- N/A
2025-01-23 21:54:16 +00:00
Michael Sloan
52494f3fdf Update some editor methods to instead take immutable references (#23578)
Makes the signatures more informative and can be more convenient as
multiple immutable borrows are allowed.

Release Notes:

- N/A
2025-01-23 21:52:10 +00:00
Agus Zubiaga
966533624a Disable zeta predictions in assistant completion menu (#23573)
We don't want the zeta predictions entry to show in the assistant
context editor when completing slash commands. Zeta will still make
suggestions in the rest of the context editor, like the other providers
do.

Release Notes:

- N/A
2025-01-23 18:49:17 -03:00
Joseph T. Lyons
06f0f0747d Autoscroll when running editor: swap selection ends (#23575)
Closes https://github.com/zed-industries/zed/issues/23512

Release Notes:

- Improved `editor: swap selection ends` by always scrolling the cursor
into view
2025-01-23 21:35:24 +00:00
Piotr Osiewicz
d8c9fdd014 project: Revert project tree impl (again) (#23572) 2025-01-23 16:10:38 -05:00
Marshall Bowers
2a2c332584 docs: Document ZED_DEVELOPMENT_AUTH (#23571)
This PR adds documentation for the `ZED_DEVELOPMENT_AUTH` environment
variable, in the hopes that it helps folks find it sooner.

Release Notes:

- N/A
2025-01-23 20:52:14 +00:00
Marshall Bowers
76bf4686ef ui: Don't add an on_click handler for disabled ListItems (#23569)
This PR updates the `ListItem` component to not register an `on_click`
handler for `ListItem`s that are disabled.

When working on #23350 I noticed that even when the context menu entry
was disabled you could still click on the entry to fire the action.

Release Notes:

- Fixed some instances of disabled list items still registering clicks.
2025-01-23 19:58:07 +00:00
Swiftaff
5ef5b75099 Disable Copy Permalink context menu item when not in git repo (#23350)
Closes #13979

Please review this approach to hide the permalink, or alternatively to
disable it instead?

Release Notes:

- The Copy Permalink menu item is now disabled when not in a Git
repository.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-01-23 19:53:07 +00:00
Conrad Irwin
0ef53bf476 vim: Add support for ctrl-g (#23562)
Co-Authored-By: Jon Walstedt <jon@walstedt.se>

Closes #22094

Release Notes:

- vim: Added support for ctrl-g

Co-authored-by: Jon Walstedt <jon@walstedt.se>
2025-01-23 12:10:08 -07:00
Peter Tripp
f38d0ff069 ollama: Set default max_tokens for llama3.3 (#23558) 2025-01-23 17:38:43 +00:00
Bennet Bo Fenner
3dee32c43d inline completion: Add syntax highlighting for edit prediction (#23361)
Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Agus <agus@zed.dev>
2025-01-23 17:32:43 +00:00
Cole Miller
75ae4dada4 Remove unwrap in GitTraversal::synchronize_statuses (#23555)
Release Notes:

- Fixed a potential panic in handling of Git statuses.

Co-authored-by: Marshall <marshall@zed.dev>
2025-01-23 12:13:20 -05:00
Peter Tripp
1ac7da8473 terraform: Bump to v0.1.2 (#23546)
Includes:
- https://github.com/zed-industries/zed/pull/22268
2025-01-23 11:34:40 -05:00
Peter Tripp
469bfa752a scheme: Bump to v0.0.2 (#23545)
Includes:
- https://github.com/zed-industries/zed/pull/18728
- https://github.com/zed-industries/zed/pull/20206
2025-01-23 11:34:29 -05:00
Nate Butler
fb332a0170 git_panel: Toggle stage all when clicking changes label (#23548)
When clicking the checkbox label fire the toggle action.

At first I wasn't sure this is what we wanted, but after looking at a
few existing implementations of checkboxes with labels it seems like
this is reasonably standard.

Eventually this piece of UI will be updated to a CheckboxWithLabel, but
for now it is custom due to some specific style requirements.

Release Notes:

- N/A
2025-01-23 16:18:25 +00:00
Piotr Osiewicz
9e6b10018a project: Bring back language servers in detached worktrees (#23530)
Closes #ISSUE

Release Notes:

- N/A
2025-01-23 17:03:53 +01:00
Peter Tripp
bb937b6cee zig: Bump to v0.3.3 (#23547)
Includes:
- https://github.com/zed-industries/zed/pull/22609
2025-01-23 10:54:50 -05:00
Peter Tripp
ea1a6a68d3 php: Bump to v0.2.4 (#23543)
Includes:
- https://github.com/zed-industries/zed/pull/23532
- https://github.com/zed-industries/zed/pull/22268
2025-01-23 10:48:58 -05:00
Peter Tripp
63c5141365 purescript: Bump to v0.1.0 (#23544)
Includes:
- https://github.com/zed-industries/zed/pull/15181
- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/22609
2025-01-23 10:44:22 -05:00
Peter Tripp
26453a0f8f csharp: Bump to v0.1.1 (#23539)
Includes:
- https://github.com/zed-industries/zed/pull/22936
2025-01-23 10:40:54 -05:00
Peter Tripp
a328c81ff6 html: Bump to v0.1.5 (#23542)
Includes:
- https://github.com/zed-industries/zed/pull/20752
- https://github.com/zed-industries/zed/pull/22268
2025-01-23 10:37:14 -05:00
Peter Tripp
9d6d98e94e haskell: Bump to v0.1.3 (#23541)
Includes:
- https://github.com/zed-industries/zed/pull/22609
2025-01-23 10:30:47 -05:00
Peter Tripp
7d7db4debc elixir: Bump to v0.1.4 (#23540)
Includes:
- https://github.com/zed-industries/zed/pull/22055
- https://github.com/zed-industries/zed/pull/22268
2025-01-23 10:27:21 -05:00
Ollie Beckwith
79683ebcaa php: Add nowdoc language injection (#23532)
We already have heredoc injection support, so this just extends it to also cover
[nowdoc](https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.nowdoc).
2025-01-23 09:51:18 -05:00
Thorsten Ball
0cb41754e2 llm: Sample ~10% of staff members inputs/outputs to LLM (#23537)
Release Notes:

- N/A
2025-01-23 15:32:25 +01:00
Kirill Bulatov
95c045ad93 Fix LSP violation when dismissing server notifications (#23531)
Part of https://github.com/zed-industries/zed/issues/22606
Closes https://github.com/zed-industries/zed/issues/23509

When a user sees an odd notification from the language server like

<img width="508" alt="image"
src="https://github.com/user-attachments/assets/6f5ef1aa-0f09-4705-a02a-aaf81dd8620c"
/>

they usually dismiss that.

Zed uses channels to wait and handle user interactions with such
notifications, and, due to `?`, sends back
```json
{"jsonrpc":"2.0","id":1,"error":{"message":"receiving from an empty and closed channel"}}
```

which is not spec-compliant:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessageRequest

> Response:
>
> * result: the selected
[MessageActionItem](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#messageActionItem)
| null if none got selected.
> * error: code and message set in case an exception happens during
showing a message.

Unfortunately, vtsls (and, potentially, others) crash if receive such
non-compliant requests, and do not get back.

After the fix, the message is correct:
```json
{"jsonrpc":"2.0","id":1,"result":null}
```


Release Notes:

- Fixed vtsls crashing on notification dismiss

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
2025-01-23 13:30:35 +00:00
Nate Butler
f9e354ee9b theme: Add version control colors (#23529)
This PR adds version control-specific theme tokens to the them to allow
styling entries in the git ui and elsewhere.

Release Notes:

- N/A
2025-01-23 07:56:49 -05:00
Piotr Osiewicz
828b5ab975 project: Reorder LSP Adapters within LanguageRegistry (#23528)
Closes #ISSUE

Release Notes:

- N/A
2025-01-23 12:03:37 +00:00
Piotr Osiewicz
11bb906520 terminal: Check for script existence properly before activating it (#23476)
We were not covering a variant where the returned metadata was Ok(None)

Closes #ISSUE

Release Notes:

- Fixed venv activation script path showing up in terminal for
non-existant scripts.
2025-01-23 12:32:21 +01:00
Kirill Bulatov
91b0ca0895 Omit tsdk_path from the servers' options if it does not exist (#23525)
Part of https://github.com/zed-industries/zed/issues/22606

Before, `tsdk_path` for vtsls and typescript-language-server
unconditionally set a `tsdk`/`tsserver` property for the corresponding
language server, even if there were no such directory at all.
Instead, make the corresponding code to omit such property if it was not
found on the FS.

Release Notes:

- Fixed "The path /.../tsserver.js doesn't point to a valid tsserver
install. Falling back to bundled TypeScript version." pop-up appearing
2025-01-23 11:17:32 +00:00
Kirill Bulatov
d1be419fff Fix panics on opening repositories with empty remotes (#23520)
Uses a newer `git2` version with
https://github.com/rust-lang/git2-rs/pull/1120 fix

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

Release Notes:

- Fixed panics on opening repositories with empty remotes
2025-01-23 08:20:32 +00:00
renovate[bot]
70f8224a89 Update cloudflare/wrangler-action digest to 7a5f8bb (#23487)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cloudflare/wrangler-action](https://redirect.github.com/cloudflare/wrangler-action)
| action | digest | `6d58852` -> `7a5f8bb` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 08:14:14 +00:00
renovate[bot]
29b57de4ab Update actions/stale digest to 5bef64f (#23486)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/stale](https://redirect.github.com/actions/stale) | action |
digest | `28ca103` -> `5bef64f` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 09:54:25 +02:00
renovate[bot]
36c62bc2d8 Update Rust crate metal to 0.31 (#23508)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [metal](https://redirect.github.com/gfx-rs/metal-rs) |
workspace.dependencies | minor | `0.30` -> `0.31` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 09:50:53 +02:00
renovate[bot]
9d7199e403 Update Rust crate convert_case to 0.7.0 (#23504)
This PR contains the following updates:

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 09:49:28 +02:00
Marshall Bowers
82cee9e9a4 assistant2: Don't suggest thread context for inline assist without a ThreadStore (#23506)
This PR makes it so we don't suggest threads as context in the inline
assist when we don't have a `ThreadStore` to pull from.

Release Notes:

- N/A
2025-01-22 23:47:56 +00:00
renovate[bot]
ecf70db7f0 Update Rust crate serde_json to v1.0.137 (#23498)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

- Turn on "float_roundtrip" and "unbounded_depth" features for
serde_json in play.rust-lang.org
([#&#8203;1231](https://redirect.github.com/serde-rs/json/issues/1231))

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

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

- Optimize serde_json::value::Serializer::serialize_map by using
Map::with_capacity
([#&#8203;1230](https://redirect.github.com/serde-rs/json/issues/1230),
thanks [@&#8203;goffrie](https://redirect.github.com/goffrie))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 18:12:27 -05:00
renovate[bot]
848f29831e Update Rust crate semver to v1.0.25 (#23497)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [semver](https://redirect.github.com/dtolnay/semver) |
workspace.dependencies | patch | `1.0.24` -> `1.0.25` |

---

### Release Notes

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

###
[`v1.0.25`](https://redirect.github.com/dtolnay/semver/releases/tag/1.0.25)

[Compare
Source](https://redirect.github.com/dtolnay/semver/compare/1.0.24...1.0.25)

- Enable serde impls on play.rust-lang.org
([#&#8203;330](https://redirect.github.com/dtolnay/semver/issues/330),
thanks [@&#8203;jofas](https://redirect.github.com/jofas))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 18:11:47 -05:00
Cole Miller
78a8c1a68a git: No-op when trying to commit with nothing staged or no commit message (#23491)
The buttons are disabled in this case, but users can still trigger a
commit via the actions directly, and an error toast seems a bit loud for
this.

cc @iamnbutler 

Release Notes:

- N/A
2025-01-22 18:04:14 -05:00
renovate[bot]
db65ee0add Update Rust crate etagere to v0.2.15 (#23493)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [etagere](https://redirect.github.com/nical/etagere) | dependencies |
patch | `0.2.13` -> `0.2.15` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 18:03:51 -05:00
Marshall Bowers
8ed2a81962 feature_flags: Enable assistant2 for all staff (#23502)
This PR makes it so the `assistant2` feature flag is automatically
enabled for all staff.

Previously all the staff members had been opted in to the feature flag
manually (to allow for opting out), but I think we're ready to
unconditionally ship it to all staff.

Release Notes:

- N/A
2025-01-22 23:02:53 +00:00
Marshall Bowers
01b57b10e7 assistant2: Only show thread context in picker when we have a ThreadStore (#23501)
This PR fixes an issue with the context picker where it would show
thread context as an option even if there was no `ThreadStore`
available.

Release Notes:

- N/A
2025-01-22 23:00:43 +00:00
Marshall Bowers
a1077c6fff assistant2: Add button to open the prompt library (#23500)
This PR adds a button to open the prompt library from the configuration
view in Assistant2.

<img width="1309" alt="Screenshot 2025-01-22 at 5 38 08 PM"
src="https://github.com/user-attachments/assets/d514abca-53bc-4cde-bead-ab68a1994fb5"
/>

Release Notes:

- N/A
2025-01-22 22:53:54 +00:00
Marshall Bowers
51fcb710d7 Dedupe PromptBuilder construction (#23496)
This PR dedupes the construction of the `PromptBuilder`.

Previously this was constructed by both `assistant` and `assistant2`,
but now we construct it outside and pass it in.

Release Notes:

- N/A
2025-01-22 22:17:36 +00:00
renovate[bot]
aad04e078a Update Rust crate indexmap to v2.7.1 (#23494)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [indexmap](https://redirect.github.com/indexmap-rs/indexmap) |
workspace.dependencies | patch | `2.7.0` -> `2.7.1` |

---

### Release Notes

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

###
[`v2.7.1`](https://redirect.github.com/indexmap-rs/indexmap/blob/HEAD/RELEASES.md#271-2025-01-19)

[Compare
Source](https://redirect.github.com/indexmap-rs/indexmap/compare/2.7.0...2.7.1)

-   Added `#[track_caller]` to functions that may panic.
-   Improved memory reservation for `insert_entry`.

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 16:19:23 -05:00
Piotr Osiewicz
3c0d05e362 lsp: Use replace edits for completions (#23490)
(Late) follow up to #9634.
Fixes #23395

Release Notes:

- Accepting completions while the cursor is in the middle of suggested
completion will now result in smaller edits being applied.
2025-01-22 21:00:04 +00:00
Cole Miller
55d99e0259 git: Handle git status output for deleted-in-index state (#23483)
When a file exists in HEAD, is deleted in the index, and exists again in
the working copy, git produces two lines for it, one reading `D `
(deleted in index, unmodified in working copy), and the other reading
`??` (untracked). Merge these two into the equivalent of `DA`.

Release Notes:

- Improved handling of files that are deleted in the git index but exist
in HEAD and the working copy
2025-01-22 15:28:25 -05:00
Piotr Osiewicz
08b3c03241 project: Allow running multiple instances of a single language server within a single worktree (#23473)
This PR introduces a new entity called Project Tree which is responsible
for finding subprojects within a worktree;
a subproject is a language-specific subset of a worktree which should be
accurately tracked on the language server side. We'll have an ability to
set multiple disjoint workspaceFolders on language server side OR spawn
multiple instances of a single language server (which will be the case
with e.g. Python language servers, as they need to interact with
multiple disjoint virtual environments).
Project Tree assumes that projects of the same LspAdapter kind cannot
overlap. Additionally project nesting is not allowed within the scope of
a single LspAdapter.

Closes https://github.com/zed-industries/zed/issues/5108
Re-lands #22182 which I had to revert due to merging it into todays
Preview.

Release Notes:

- Language servers now track their working directory more accurately.

---------

Co-authored-by: João <joao@zed.dev>
2025-01-22 21:19:02 +01:00
Marshall Bowers
2c2a3ef13d assistant2: Handle LLM providers that do not emit StartMessage events (#23485)
This PR updates Assistant2's response streaming to work with LLM
providers that do not emit `StartMessage` events.

Now if we get a `Text` event without having received a `StartMessage`
event we will still insert an Assistant message so we can stream in the
response from the model.

Release Notes:

- N/A
2025-01-22 20:15:16 +00:00
Peter Tripp
6aab82c180 docs: Mention PopOS 24.04 in Linux FAQ for /etc/prime-discrete (#23482) 2025-01-22 19:37:04 +00:00
Cole Miller
a811979894 git: Fix the panel's update debouncing (#23462)
This PR replaces the update debouncing code in the git panel with a more
correct and conventional structure (holding a `Task` field instead of
spawning a task that runs a loop). I wrote the code that this replaces
without realizing that it doesn't implement debouncing properly.

Release Notes:

- N/A
2025-01-22 14:33:17 -05:00
Marshall Bowers
514d9b4161 assistant2: Add configuration (#23481)
This PR wires up the ability to configure Assistant2.

<img width="1309" alt="Screenshot 2025-01-22 at 1 52 56 PM"
src="https://github.com/user-attachments/assets/3de47797-7959-47af-bd93-51f105e87c28"
/>

Release Notes:

- N/A
2025-01-22 19:07:48 +00:00
Marshall Bowers
f7703973d2 assistant: Extract ConfigurationView to its own module (#23480)
This PR is a small refactoring that extracts the Assistant's
`ConfigurationView` into its own module.

Release Notes:

- N/A
2025-01-22 18:37:00 +00:00
Michael Sloan
09fe1e7f66 Scroll completions menu to new selection on filter, fix corner case (#23478)
In the future if `filter` was used more this would fix other issues. In
the current code paths, this just fixes the particular corner case of
edit prediction arriving async while `y_flipped = true` (in this case it
needs to be scrolled down to show item with index 0).

Release Notes:

- N/A
2025-01-22 17:54:10 +00:00
Marshall Bowers
9f87145af9 title_bar: Simplify git-ui feature flag check (#23475)
This PR is a follow-up to
https://github.com/zed-industries/zed/pull/23470 that simplifies the way
we check the `git-ui` feature flag in the title bar.

Release Notes:

- N/A
2025-01-22 17:28:47 +00:00
Peter Tripp
5930b552b9 Bump Zed to v0.172 (#23474) 2025-01-22 11:57:24 -05:00
Piotr Osiewicz
da406ae07e Revert "project: Allow running multiple instances of a single language server within a single worktree" (#23472)
Reverts zed-industries/zed#22182
I've merged the build too soon as I wanted it to be excluded from todays
Preview.
2025-01-22 11:42:50 -05:00
Thorsten Ball
706e46c3ff vim: Fix % not working for multi-char bracket pairs (#23471)
Closes #23358

Demo/proof:


https://github.com/user-attachments/assets/2036d183-8830-415b-9155-0d3efe3d824c




Release Notes:

- Fixed `%` in Vim mode not working correctly for multi-char brackets,
such as `do/end` in Elixir or `\begin` and `\end` in LaTeX.

Co-authored-by: Conrad <conrad@zed.dev>
2025-01-22 17:39:11 +01:00
Nate Butler
5be2784ddb git_ui: Feature flag repo selector (#23470)
Fixes an issue where the repo selector showed for all users, not just
those in the git_ui feature flag.

This was meant to be included in the `git_ui` feature flag.

Release Notes:

- N/A
2025-01-22 16:37:19 +00:00
Piotr Osiewicz
bed917b0b1 project: Allow running multiple instances of a single language server within a single worktree (#22182)
This PR introduces a new entity called Project Tree which is responsible
for finding subprojects within a worktree;
a subproject is a language-specific subset of a worktree which should be
accurately tracked on the language server side. We'll have an ability to
set multiple disjoint `workspaceFolder`s on language server side OR
spawn multiple instances of a single language server (which will be the
case with e.g. Python language servers, as they need to interact with
multiple disjoint virtual environments).
Project Tree assumes that projects of the same LspAdapter kind cannot
overlap. Additionally **project nesting** is not allowed within the
scope of a single LspAdapter.

Closes #5108

Release Notes:

- Language servers now track their working directory more accurately.

---------

Co-authored-by: João <joao@zed.dev>
2025-01-22 17:31:14 +01:00
Danilo Leal
d85fec5f13 Update "Book Onboarding" destination link (#23464)
This PR updates the Book Onboarding links to point to a new form.

Release Notes:

- N/A
2025-01-22 16:24:39 +00:00
Agus Zubiaga
95cde129af Show "tab Accept" only for zeta (#23463)
#23460 brought up we are showing the new "tab Accept" marker for single
line suggestions for non-zeta providers. We think this might be valid
for any provider, but we only want to enable it for zeta initially so it
doesn't affect an existing user base.

Release Notes:

- N/A
2025-01-22 13:19:18 -03:00
Marshall Bowers
1e88e2924c assistant2: Respect panel dock position (#23465)
This PR fixes an issue where Assistant2 was not respecting the panel
dock position.

Release Notes:

- N/A
2025-01-22 16:05:04 +00:00
Agus Zubiaga
636df12b74 Only show accept callout if suggestion is all insertions/deletions (#23461)
Release Notes:

- N/A
2025-01-22 14:01:25 +00:00
Agus Zubiaga
55721c65d4 Fix single line edit prediction detection (#23456)
#23411 introduced an "Accept" callout for single line edits, but the
logic to detect them was incorrect causing it to trigger for multiline
insertions, this PR fixes that.

Release Notes:

- N/A
2025-01-22 10:00:43 -03:00
Piotr Osiewicz
f0b5b0b4df lsp: Ignore payload of DidChangeConfiguration dynamic registration (#23454)
Fixes #23430

Closes #23430

Release Notes:

- N/A
2025-01-22 11:23:42 +01:00
张小白
c66f611037 windows: Improve foreground task dispatching on Windows (continued) (#23415)
Closes #22653 again

In PR #23283, I thought that every `runnable` dispatched to the main
thread would correspond to an `EVENT_DISPATCHED` message in the message
queue. However, after testing, some `runnable`s occasionally weren’t
executed.

This PR updated the code as follows:  
```rust
if let Ok(runnable) = self.main_receiver.try_recv() {    <-- before
for runnable in self.main_receiver.drain() {            <-- after
    runnable.run();
}
```

This ensures that runnables are handled more proactively on the main
thread, now we handle `runnable`s with a much higher priority.

A big thanks to @MolotovCherry and @ArthurBrussee for their testing
efforts!

Release Notes:

- N/A
2025-01-22 16:37:36 +08:00
Michael Sloan
2f1af2ab69 When completions menu is displayed above cursor, reverse order (#23446)
Considered doing this when previously working on completions menu
layout, as it brings the default selection position next to the cursor
position, and is generally more symmetrical. With #23445 there is now a
more compelling reason, as the "translucent, cropped bottom" display
doesn't make sense when displayed above.

Release Notes:

- N/A
2025-01-21 22:47:56 -07:00
Michael Sloan
1769bc957b Make app notifications appear in new workspaces + dismiss on all workspaces (#23432)
The keymap error notifications got convoluted to support displaying the
notification on startup. This change addresses it systemically for all
future app notifications.

Reverts most of #20531, while keeping the fix to handle keyboard layout
switching. This is a better fix for #20531

Release Notes:

- N/A
2025-01-22 05:39:04 +00:00
Marshall Bowers
1e1997c97b Wire up AssistantPanelDelegate based on assistant2 feature flag (#23444)
This PR adjusts how the `AssistantPanelDelegate` global is set to be
based on the state of the feature flag.

This should prevent `assistant` and `assistant2` from potentially
clobbering each other.

Release Notes:

- N/A
2025-01-22 04:57:51 +00:00
Marshall Bowers
9ee1db3552 assistant2: Insert default prompt into prompt editor (#23443)
This PR fixes an issue where the default prompt was not being inserted
into the prompt editor in Assistant2.

Release Notes:

- N/A
2025-01-22 03:01:34 +00:00
Marshall Bowers
c887bf8e54 Consolidate Assistant panels in assistant2 feature flag (#23442)
This PR consolidates the two Assistant panels into one for users in the
`assistant2` feature flag.

Now that the Assistant1 prompt editor is accessible through the
Assistant2 panel, we no longer have a need to show both panels.

Release Notes:

- N/A
2025-01-22 02:56:42 +00:00
Marshall Bowers
7516b8c8b7 assistant2: Add prompt editor history (#23439)
This PR adds the prompt editor history to Assistant2.

<img width="1309" alt="Screenshot 2025-01-21 at 9 02 07 PM"
src="https://github.com/user-attachments/assets/d79936fe-1c23-425f-b99d-43f85afd0c39"
/>

Release Notes:

- N/A
2025-01-22 02:11:48 +00:00
Marshall Bowers
be407e27f9 Extract ContextHistory to assistant_context_editor (#23437)
This PR extracts the `ContextHistory` to the `assistant_context_editor`
crate.

Release Notes:

- N/A
2025-01-22 01:32:24 +00:00
Marshall Bowers
e59c910845 assistant2: Add prompt editor (#23436)
This PR adds the Assistant1 experience to Assistant2 as a "prompt
editor".

<img width="1309" alt="Screenshot 2025-01-21 at 7 17 26 PM"
src="https://github.com/user-attachments/assets/3ce2f32b-2b1a-48a8-8e56-4c44e3ac4ce5"
/>

Release Notes:

- N/A
2025-01-22 00:36:55 +00:00
Peter Tripp
3d47f32f0c Add support for editor::SwapSelectionEnds everywhere (emacs exchange-point-and-mark) (#23428)
Add `editor:: SwapSelectionEnds ` action which swaps the cursor location from the beginning/end of a given selection.
Renamed from `editor::ExchangeMark` to `editor::SwapSelectionEnds`.
Unbound by default, bound to `ctrl-x ctrl-x` in Emacs keymap.
2025-01-21 18:14:00 -05:00
Cole Miller
31909bf334 git: Implement a basic repository selector (#23419)
This PR adds a rough-and-ready picker for selecting which of the
project's repositories the git panel should display.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Nate <nate@zed.dev>
2025-01-21 18:11:53 -05:00
Marshall Bowers
417760ade7 Extract ContextEditor to assistant_context_editor (#23433)
This PR extracts the `ContextEditor` to the `assistant_context_editor`
crate.

As part of this, we have decoupled the `ContextEditor` from the
`AssistantPanel`.

There is now an `AssistantPanelDelegate` that the `ContextEditor` uses
when it needs to interface with the Assistant panel.

Release Notes:

- N/A
2025-01-21 23:08:34 +00:00
Marshall Bowers
9a7f1d1de4 Add assistant_context_editor crate (#23429)
This PR adds a new `assistant_context_editor` crate.

This will ultimately house the `ContextEditor` so that it can be
consumed by both `assistant` and `assistant2`.

For the purposes of this PR, we just introduce the crate and move some
supporting constructs to it, such as the `ContextStore`.

Release Notes:

- N/A
2025-01-21 21:22:59 +00:00
Peter Tripp
c450cd51ea open_ai: Move from o1-preview to o1 for OpenAI Assistant provider (#23425)
- Closes: https://github.com/zed-industries/zed/issues/22521
- Follow-up to: https://github.com/zed-industries/zed/pull/22376
2025-01-21 15:05:21 -05:00
k4leg
6eaaced60d Fix button demo in the component preview (#23423)
Problem: If you click on "Tinted Icons", "Icon Color" is also activated.

Release Notes:

- N/A
2025-01-21 19:33:08 +00:00
Marshall Bowers
836b4c1db4 ci: Remove paths-ignore for docs, as it does not work with required status checks (#23424)
This PR removes the `paths-ignore` for docs again, as it causes
docs-only PRs to be unmergable in combination with required status
checks (which we need in order to support merge-when-ready).

We can put these back if and only if we come up with a solution for how
to make it work with required status checks.

Release Notes:

- N/A
2025-01-21 19:28:22 +00:00
Hristo Kanchev
718da3f9f5 docs: Fix broken link for the Odin language (#23421) 2025-01-21 14:20:54 -05:00
Marshall Bowers
51b6cbf9ac assistant: Extract ContextEditor and ContextHistory to their own modules (#23422)
This PR extracts the `ContextEditor` and `ContextHistory`
implementations into their own modules so that it's clearer which parts
depend on other constructs in the `assistant` crate.

Release Notes:

- N/A
2025-01-21 19:10:13 +00:00
Agus Zubiaga
c825bb492d assistant2: Propagate move up action to inline picker from message editor (#23416)
Release Notes:

- N/A

Co-authored-by: Richard <richard@zed.dev>
2025-01-21 16:02:52 -03:00
Agus Zubiaga
86ff88ae1d Show "tab Accept" indicator for single line insertions/deletions (#23411)
https://github.com/user-attachments/assets/655f20a9-f22f-4f91-870e-d40b20c8c994

Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2025-01-21 16:02:26 -03:00
Peter Tripp
14cd178ab0 ollama: Add deepseek-r1 context size to defaults (#23420) 2025-01-21 18:31:15 +00:00
Cole Miller
aa5fa4b7d4 git: Use a buffer for the panel's commit message (#23308)
This PR changes the `GitPanel` and `GitState` to use a
`language::Buffer` for the commit message. This is a small initial step
toward remote editing and collaboration support.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2025-01-21 17:58:18 +00:00
Michael Sloan
64f9acf020 Simplify workspace notification code (#23414)
* Remove `NotificationHandle` trait in favor of just passing `AnyView` -
id field wasn't used.

* Remove `show_notification_once`, doesn't seem to be needed for its
only use.

Release Notes:

- N/A
2025-01-21 17:49:11 +00:00
Enrique Kessler Martínez
8c215d43ad Fix pulling metadata out of broken symlinks (#22396)
## Problem

When developing extensions locally, developers will commonly put their
source code in a specific directory. Zed uses this directory to create a
symlink starting from `$HOME/Library/Application
Support/Zed/extensions/installed` (MacOS path). When a developer then
moves this source code and tries to reinstall the extension, Zed will
fail with an unhelpful message (you can check the #Testing section).

## Change Summary

With this PR, we fix this behaviour by handling broken symlinks
specifically when returning the metadata on `fs::metadata`. Today, we

1. Pull the symlink metadata.
2. Return it if the file was not a symlink OR if it is, pull the
metadata for the pointed file.

After this change gets merged, we return the Symlink metadata if the
symlink is broken. This makes the symlink be recreated since we remove
the symlink either way.

## Risks associated with this change

It's possible changing this behaviour will show additional cases where
we are handling broken symlinks incorrectly. I expect this to be a
better scenario AND backwards compatible. We have the same behaviour we
had for 1. existing symlinks 2. normal files.

## Testing

The way I have been reproducing this is by having a private extension of
my own. I install it using the `zed: install dev extension` command
after running `RUST_LOG=debug RUST_BACKTRACE=1 scripts/zed-local -1`.
Then I move the extension to a different directory.

Zed will now keeps a broken link on its `installed` directory:

```
❯ ll
Permissions Size User    Date Modified Name
lrwxr-xr-x@    - enrikes 24 Dec 12:15  brazil-config-zed-extension -> /Volumes/workplace/BrazilConfigZedExtension
drwxr-xr-x@    - enrikes  5 Dec 14:48  java
drwxr-xr-x@    - enrikes 12 Dec 13:04  kotlin
drwxr-xr-x@    - enrikes 25 Oct 08:13  rose-pine-theme
```

Before the patch, Zed shows on its logs:

```
2024-12-24T16:44:02+01:00 INFO  extension::extension_builder] compiled Rust extension /Users/enrikes/Documents/BrazilConfigZedExtension
[2024-12-24T16:44:02+01:00 INFO  extension::extension_builder] compiling grammar brazil_config for extension /Users/enrikes/Documents/BrazilConfigZedExtension
[2024-12-24T16:44:02+01:00 INFO  extension::extension_builder] checking out brazil_config parser
[2024-12-24T16:44:04+01:00 INFO  extension::extension_builder] compiling brazil_config parser
[2024-12-24T16:44:05+01:00 INFO  extension::extension_builder] compiled grammar brazil_config for extension /Users/enrikes/Documents/BrazilConfigZedExtension
[2024-12-24T16:44:05+01:00 INFO  extension::extension_builder] finished compiling extension /Users/enrikes/Documents/BrazilConfigZedExtension
[2024-12-24T16:44:05+01:00 ERROR extensions_ui] No such file or directory (os error 2)

Stack backtrace:
   0: std::backtrace_rs::backtrace::libunwind::trace
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/../../backtrace/src/backtrace/libunwind.rs:116:5
   1: std::backtrace_rs::backtrace::trace_unsynchronized
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2: std::backtrace::Backtrace::create
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/backtrace.rs:331:13
   3: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from
             at /Users/enrikes/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anyhow-1.0.94/src/backtrace.rs:27:14
   4: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual
             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/result.rs:1989:27
   5: <fs::RealFs as fs::Fs>::metadata::{{closure}}
             at ./crates/fs/src/fs.rs:603:13
```

After the patch, the extension is installed and the symlink replaced for
a new one pointing to the user's directory choice.

```
2024-12-24T16:53:33.916022+01:00 [INFO] compiled Rust extension /Users/enrikes/Documents/BrazilConfigZedExtension
2024-12-24T16:53:33.916094+01:00 [INFO] compiling grammar brazil_config for extension /Users/enrikes/Documents/BrazilConfigZedExtension
2024-12-24T16:53:33.916225+01:00 [INFO] checking out brazil_config parser
2024-12-24T16:53:35.481602+01:00 [INFO] compiling brazil_config parser
2024-12-24T16:53:35.964189+01:00 [INFO] compiled grammar brazil_config for extension /Users/enrikes/Documents/BrazilConfigZedExtension
2024-12-24T16:53:35.964319+01:00 [INFO] finished compiling extension /Users/enrikes/Documents/BrazilConfigZedExtension
2024-12-24T16:53:36.213608+01:00 [INFO] rebuilt extension index in 39.108542ms
2024-12-24T16:53:36.213835+01:00 [INFO] extensions updated. loading 0, reloading 1, unloading 0
2024-12-24T16:53:36.375928+01:00 [INFO] rebuilt extension index in 34.478167ms
2024-12-24T16:53:36.376054+01:00 [INFO] extensions updated. loading 0, reloading 1, unloading 0
```

and

```
❯ ll
lrwxr-xr-x@    - enrikes 24 Dec 16:53  brazil-config-zed-extension -> /Users/enrikes/Documents/BrazilConfigZedExtension
drwxr-xr-x@    - enrikes  5 Dec 14:48  java
drwxr-xr-x@    - enrikes 12 Dec 13:04  kotlin
drwxr-xr-x@    - enrikes 25 Oct 08:13  rose-pine-theme
```


Release Notes:

- Fix broken symlinks when installing dev extensions

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-01-21 17:09:16 +00:00
Kirill Bulatov
75c5344754 Fix completion labels becoming overly large due to LSP completion items with newlines (#23407)
Reworks https://github.com/zed-industries/zed/pull/23030 and
https://github.com/zed-industries/zed/pull/15087
Closes https://github.com/zed-industries/zed/issues/23352
Closes https://github.com/zed-industries/zed/issues/23310 

Zed's completion items use `label` from LSP completion items as a base
to show in the list:
d290da7dac/crates/project/src/lsp_store.rs (L4371-L4374)

Besides that, certain language plugins append `detail` or
`label_details.description` as a suffix:
d290da7dac/crates/languages/src/vtsls.rs (L178-L188)

Either of these 3 properties may return `\n` (or multiple) in it,
spoiling Zed's completion menu, which uses `UniformList` to render those
items: a uniform list uses common, minimum possible height for each
element, and `\n` bloats that overly.

Good approach would be to use something else:
https://github.com/zed-industries/zed/issues/21403 but that has its own
drawbacks and relatively hard to use instead (?).

We could follow VSCode's approach and move away all but `label` from
`CodeLabel.text` to the side, where the documentation is, but that does
not solve the issue with `details` having newlines.

So, for now, sanitize all labels and remove any newlines from them. If
newlines are found, also replace whitespace sequences if there's more
than 1 in a row.

Later, this approach can be improved similarly to how Helix and Zed's
inline completions do: rendering a "ghost" text, showing the
completion's edit applied to the editor.

Release Notes:

- Fixed completion labels becoming overly large due to LSP completion
items with newlines
2025-01-21 17:55:41 +02:00
Conrad Irwin
94189e1784 Add emacs mark mode (#23297)
Updates #21927
Replaces https://github.com/zed-industries/zed/pull/22904
Closes #8580

Adds actions (default keybinds with emacs keymap):
- editor::SetMark (`ctrl-space` and `ctrl-@`)
- editor::ExchangeMark (`ctrl-x ctrl-x`)

Co-Authored-By: Peter <peter@zed.dev>

Release Notes:

- Add Emacs mark mode (`ctrl-space` / `ctrl-@` to set mark; `ctrl-x
ctrl-x` to swap mark/cursor)
- Breaking change: `selection` keyboard context has been replaced with
`selection_mode`

---------

Co-authored-by: Peter <peter@zed.dev>
2025-01-21 10:18:25 -05:00
Danilo Leal
c4542ca731 zeta: Make the Jump to Edit callout pop up more (#23404)
| Before | After |
|--------|--------|
| <img width="1328" alt="Screenshot 2025-01-21 at 11 20 49 AM"
src="https://github.com/user-attachments/assets/ad8e3017-122a-4ebd-b1f5-5eb41cc3725a"
/> | <img width="1328" alt="Screenshot 2025-01-21 at 11 19 39 AM"
src="https://github.com/user-attachments/assets/a0dcbd52-6aca-43fa-97ee-6efde15c8bc1"
/> |

Release Notes:

- N/A
2025-01-21 11:40:02 -03:00
Danilo Leal
d011b97830 project_panel: Adjust entry background and border colors (#23403)
Follow up to https://github.com/zed-industries/zed/pull/22658

This PR ensures the background and border color of a project panel entry
is exactly the same with one exception: if the item is focused, active,
and not with mouse down. The point is to not be able to see the border
at all given they're there to act sort of akin to CSS's `outline` (which
doesn't add up to the box model).

Please let me know if there is any edge case I either messed up here or
didn't account for.


https://github.com/user-attachments/assets/29c74f6a-b027-4d19-a7de-b9614f0d7859

Release Notes:

- N/A
2025-01-21 11:13:46 -03:00
Piotr Osiewicz
f33d02c4c1 lsp_store: Do not associate a language server with the language for symbol highlighting (#23401)
This unblocks work on #22182; a single language server might actually be
required by multiple languages (think of e.g. C/C++,
Javascript/Typescript), in which case it doesn't make sense to use a
single grammar. We already use primary language of a buffer for
highlights and this PR makes this the only supported syntax highlighting
flavour for returned symbols.

Closes #ISSUE

Release Notes:

- N/A
2025-01-21 13:35:15 +01:00
Agus Zubiaga
e2c7934783 Show inline completion popover for hard to spot single edits (#23385)
If a suggested edit is a single character insert or a single line
deletion, we'll show the diff popover to make it stand out more.

Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2025-01-21 09:31:52 -03:00
Andrew Borg (Kashin)
d40177c2ae Fix project entry rename in Remote Development (#23382)
Closes #22883

To fix the problem, we move `handle_rename_project_entry` from
`Worktree` to `LspStore` and register it there. This way it becomes
available both in local and headless projects and this avoids the
duplication.

Release Notes:

- Fixed renaming project entries in Remote Development
2025-01-21 12:48:40 +01:00
Michael Sloan
cc1af7d96b Display keymap errors on initial load (#23394)
Also fixes issue introduced in #23113 where changes to keyboard layout
would not cause reload of keymap configuration.

Closes #20531

Release Notes:

- N/A
2025-01-21 07:14:46 +00:00
张小白
04c04e8406 windows: Fix FS-related issues (#23369)
I've noticed an occasional error: `ignoring event C:\some\path\to\file
outside of root path \\?\C:\some\path`. This happens because UNC paths
always fail to match with non-UNC paths during operations like
`strip_prefix` or `starts_with`. To address this, I changed the types of
some key parameters to `SanitizedPath`. With this adjustment, FS events
are now correctly identified, and under the changes in this PR, the
`test_rescan_and_remote_updates` test also passes successfully on
Windows.

Release Notes:

- N/A
2025-01-21 14:19:23 +08:00
Richard Feldman
8f87b5637a Wire up @mentioned files in assistant2 (#23389)
`@mention`ed files in assistant2 now get replaced by the full path of
the file in what gets sent to the model, while rendering visually as
just the filename (in a crease, so they can only be selected/deleted as
a whole unit, not character by character).


https://github.com/user-attachments/assets/a5867a93-d656-4a17-aced-58424c6e8cf6

Release Notes:

- N/A

---------

Co-authored-by: João Marcos <joao@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
2025-01-20 21:33:18 -05:00
Michael Sloan
aacd80ee4a Prefer later bindings in keymap section for display in UI (#23378)
Closes #23015

Release Notes:

- Improved which keybindings are selected for display. Now later entries
within `bindings` will take precedence. The default keymaps have been
updated accordingly.
2025-01-20 23:20:15 +00:00
Agus Zubiaga
919703e6a8 Toggle inline completion menu from keyboard (#23380) 2025-01-20 19:11:13 -03:00
Cherry
3c0acdea5e docs: Add troubleshooting section warning about RUSTFLAGS env var (#23354)
According to #23223, manually setting `RUSTFLAGS` env var overrides
settings in `.cargo/config.toml`. Since users possibly may set their own
`RUSTFLAGS` when building, this creates an avenue where builds may fail
for really strange reasons that are difficult to debug.

This PR adds notes to the troubleshooting section to avoid setting
`RUSTFLAGS`, and offers alternatives which do not conflict.

This problem most recently affected nightly CI builders since we had
been setting `RUSTFLAGS` in our workflows to enable custom things like
gles or compiling with a specific target cpu. PR #23117 caused builds to
fail unless they were compiled with `-C target-feature=+crt-static`,
which due to this issue the `RUSTFLAGS` env var we set overrode the
`config.toml` compile flags, causing our builds to fail.

Release Notes:

- N/A
2025-01-20 15:43:14 -05:00
Agus Zubiaga
36c7b3eb91 Hide "Edit prediction" tooltip when menu is open (#23377)
Release Notes:

- N/A
2025-01-20 17:13:47 -03:00
Jules Bertholet
a22d8ef78f Add support for various action keys to Linux keymap (#22997)
Adds support for Cut, Copy, Paste, Undo, Redo, New, Open, Save, and Find
keys to the default keymap. These keys can be found on old keyboards,
but also custom layouts like
[Extend](https://dreymar.colemak.org/layers-extend.html).

Release Notes:

- Added support for the Cut, Copy, Paste, Undo, Redo, New, Open, Save,
and Find keys to the default keymap.
2025-01-20 12:28:34 -07:00
Michael Sloan
4b4876d48a Document KeymapFile and KeymapSection for display in hovers (#23375)
Release Notes:

- N/A
2025-01-20 19:16:16 +00:00
Cole Miller
fed4b48c57 git: Skip directories in git status output (#23300)
The output of `git status --porcelain=v1` includes untracked
directories, i.e. directories that have no tracked files beneath. Since
we have our own way of computing a "summary" status for each directory
in a repo, this is not helpful for Zed; and it interferes with our
handling of nested repos. So just skip these lines in the output.

Closes #23133 

Release Notes:

- Fix project panel colors when one git repository is nested beneath
another
2025-01-20 13:57:19 -05:00
804 changed files with 56142 additions and 40161 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,16 +7,10 @@ on:
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
paths-ignore:
- "docs/**/*"
- ".github/workflows/community_*"
pull_request:
branches:
- "**"
paths-ignore:
- "docs/**/*"
- ".github/workflows/community_*"
concurrency:
# Allow only one workflow per any non-`main` branch.

View File

@@ -9,7 +9,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: >
@@ -19,11 +19,7 @@ jobs:
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
# We will increase `days-before-stale` to 365 on or after Jan 24th,
# 2024. This date marks one year since migrating issues from
# 'community' to 'zed' repository. The migration added activity to all
# issues, preventing 365 days from working until then.
days-before-stale: 180
days-before-stale: 120
days-before-close: 7
any-of-issue-labels: "bug,panic / crash"
operations-per-run: 1000

View File

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

View File

@@ -6,6 +6,7 @@ on:
jobs:
discord_release:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Get release URL
@@ -28,7 +29,8 @@ jobs:
maxLength: 2000
truncationSymbol: "..."
- name: Discord Webhook Action
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4 # v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ steps.get-content.outputs.string }}
flags: 4 # suppress embeds - https://discord.com/developers/docs/resources/message#message-object-message-flags

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ on:
jobs:
danger:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:

View File

@@ -37,28 +37,28 @@ jobs:
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Deploy Docs
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy target/deploy --project-name=docs
- name: Deploy Install
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
- name: Deploy Docs Workers
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Deploy Install Workers
uses: cloudflare/wrangler-action@6d58852c35a27e6034745c5d0bc373d739014f7f # v3
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ env:
jobs:
tests:
name: Run randomized tests
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
steps:

233
Cargo.lock generated
View File

@@ -372,14 +372,13 @@ name = "assistant"
version = "0.1.0"
dependencies = [
"anyhow",
"assistant_context_editor",
"assistant_settings",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_tool",
"async-watch",
"chrono",
"client",
"clock",
"collections",
"command_palette_hooks",
"context_server",
@@ -390,7 +389,6 @@ dependencies = [
"feature_flags",
"fs",
"futures 0.3.31",
"fuzzy",
"gpui",
"indexed_docs",
"indoc",
@@ -403,30 +401,23 @@ dependencies = [
"lsp",
"menu",
"multi_buffer",
"open_ai",
"parking_lot",
"paths",
"picker",
"pretty_assertions",
"project",
"prompt_library",
"proto",
"rand 0.8.5",
"regex",
"rope",
"rpc",
"schemars",
"search",
"semantic_index",
"serde",
"serde_json",
"serde_json_lenient",
"settings",
"similar",
"smallvec",
"smol",
"streaming_diff",
"strum",
"telemetry",
"telemetry_events",
"terminal",
@@ -437,7 +428,6 @@ dependencies = [
"ui",
"unindent",
"util",
"uuid",
"workspace",
"zed_actions",
]
@@ -447,7 +437,9 @@ name = "assistant2"
version = "0.1.0"
dependencies = [
"anyhow",
"assistant_context_editor",
"assistant_settings",
"assistant_slash_command",
"assistant_tool",
"async-watch",
"chrono",
@@ -464,6 +456,7 @@ dependencies = [
"futures 0.3.31",
"fuzzy",
"gpui",
"heed",
"html_to_markdown",
"http_client",
"indoc",
@@ -478,6 +471,7 @@ dependencies = [
"menu",
"multi_buffer",
"parking_lot",
"paths",
"picker",
"project",
"prompt_library",
@@ -498,19 +492,75 @@ dependencies = [
"time",
"time_format",
"ui",
"unindent",
"util",
"uuid",
"workspace",
"zed_actions",
]
[[package]]
name = "assistant_context_editor"
version = "0.1.0"
dependencies = [
"anyhow",
"assistant_settings",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_tool",
"chrono",
"client",
"clock",
"collections",
"context_server",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"fuzzy",
"gpui",
"indexed_docs",
"language",
"language_model",
"language_model_selector",
"language_models",
"languages",
"log",
"multi_buffer",
"open_ai",
"parking_lot",
"paths",
"picker",
"pretty_assertions",
"project",
"prompt_library",
"rand 0.8.5",
"regex",
"rope",
"rpc",
"serde",
"serde_json",
"settings",
"smallvec",
"smol",
"strum",
"telemetry_events",
"text",
"theme",
"tree-sitter-md",
"ui",
"unindent",
"util",
"uuid",
"workspace",
]
[[package]]
name = "assistant_settings"
version = "0.1.0"
dependencies = [
"anthropic",
"anyhow",
"deepseek",
"feature_flags",
"fs",
"gpui",
@@ -587,6 +637,7 @@ dependencies = [
"ui",
"util",
"workspace",
"worktree",
]
[[package]]
@@ -2640,6 +2691,7 @@ dependencies = [
"anthropic",
"anyhow",
"assistant",
"assistant_context_editor",
"assistant_slash_command",
"assistant_tool",
"async-stripe",
@@ -2958,9 +3010,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.6.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
dependencies = [
"unicode-segmentation",
]
@@ -3635,6 +3687,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "deepseek"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.31",
"http_client",
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "deflate64"
version = "0.1.9"
@@ -3903,7 +3967,7 @@ dependencies = [
"client",
"clock",
"collections",
"convert_case 0.6.0",
"convert_case 0.7.1",
"ctor",
"db",
"emojis",
@@ -4174,9 +4238,9 @@ dependencies = [
[[package]]
name = "etagere"
version = "0.2.13"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e2f1e3be19fb10f549be8c1bf013e8675b4066c445e36eb76d2ebb2f54ee495"
checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342"
dependencies = [
"euclid",
"svg_fmt",
@@ -5155,9 +5219,8 @@ dependencies = [
[[package]]
name = "git2"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
version = "0.20.0"
source = "git+https://github.com/rust-lang/git2-rs?rev=a3b90cb3756c1bb63e2317bf9cfa57838178de5c#a3b90cb3756c1bb63e2317bf9cfa57838178de5c"
dependencies = [
"bitflags 2.8.0",
"libc",
@@ -5196,8 +5259,8 @@ dependencies = [
"futures 0.3.31",
"git",
"gpui",
"language",
"menu",
"picker",
"project",
"schemars",
"serde",
@@ -5209,7 +5272,6 @@ dependencies = [
"util",
"windows 0.58.0",
"workspace",
"worktree",
]
[[package]]
@@ -5416,6 +5478,7 @@ dependencies = [
name = "gpui_macros"
version = "0.1.0"
dependencies = [
"gpui",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -5566,11 +5629,11 @@ dependencies = [
[[package]]
name = "hashlink"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.14.5",
"hashbrown 0.15.2",
]
[[package]]
@@ -6260,9 +6323,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.7.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
@@ -6741,6 +6804,7 @@ dependencies = [
"async-trait",
"collections",
"extension",
"fs",
"futures 0.3.31",
"gpui",
"language",
@@ -6758,6 +6822,7 @@ dependencies = [
"anyhow",
"base64 0.22.1",
"collections",
"deepseek",
"futures 0.3.31",
"google_ai",
"gpui",
@@ -6801,6 +6866,7 @@ dependencies = [
"client",
"collections",
"copilot",
"deepseek",
"editor",
"feature_flags",
"fs",
@@ -6906,6 +6972,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"snippet_provider",
"task",
"text",
"theme",
@@ -6981,9 +7048,8 @@ dependencies = [
[[package]]
name = "libgit2-sys"
version = "0.17.0+1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
version = "0.18.0+1.9.0"
source = "git+https://github.com/rust-lang/git2-rs?rev=a3b90cb3756c1bb63e2317bf9cfa57838178de5c#a3b90cb3756c1bb63e2317bf9cfa57838178de5c"
dependencies = [
"cc",
"libc",
@@ -7594,9 +7660,9 @@ dependencies = [
[[package]]
name = "metal"
version = "0.30.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3572083504c43e14aec05447f8a3d57cce0f66d7a3c1b9058572eca4d70ab9"
checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e"
dependencies = [
"bitflags 2.8.0",
"block",
@@ -7706,15 +7772,21 @@ dependencies = [
"ctor",
"env_logger 0.11.6",
"futures 0.3.31",
"git",
"gpui",
"indoc",
"itertools 0.14.0",
"language",
"log",
"parking_lot",
"pretty_assertions",
"project",
"rand 0.8.5",
"rope",
"serde",
"settings",
"smallvec",
"smol",
"sum_tree",
"text",
"theme",
@@ -9464,9 +9536,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@@ -9969,7 +10041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes 1.9.0",
"heck 0.5.0",
"heck 0.4.1",
"itertools 0.12.1",
"log",
"multimap 0.10.0",
@@ -11530,9 +11602,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
dependencies = [
"serde",
]
@@ -11579,9 +11651,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.135"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [
"indexmap",
"itoa",
@@ -12004,8 +12076,9 @@ dependencies = [
"gpui",
"parking_lot",
"paths",
"schemars",
"serde",
"serde_json",
"serde_json_lenient",
"snippet",
"util",
]
@@ -12125,9 +12198,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -12138,32 +12211,27 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
dependencies = [
"atoi",
"bigdecimal",
"byteorder",
"bytes 1.9.0",
"chrono",
"crc",
"crossbeam-queue",
"either",
"event-listener 5.3.1",
"futures-channel",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.14.5",
"hashlink 0.9.1",
"hex",
"hashbrown 0.15.2",
"hashlink 0.10.0",
"indexmap",
"log",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"rust_decimal",
"rustls 0.23.20",
@@ -12172,8 +12240,7 @@ dependencies = [
"serde_json",
"sha2",
"smallvec",
"sqlformat",
"thiserror 1.0.69",
"thiserror 2.0.6",
"time",
"tokio",
"tokio-stream",
@@ -12185,9 +12252,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
dependencies = [
"proc-macro2",
"quote",
@@ -12198,9 +12265,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
dependencies = [
"dotenvy",
"either",
@@ -12224,9 +12291,9 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -12262,7 +12329,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 1.0.69",
"thiserror 2.0.6",
"time",
"tracing",
"uuid",
@@ -12271,9 +12338,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -12286,7 +12353,6 @@ dependencies = [
"etcetera",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"hex",
"hkdf",
@@ -12306,7 +12372,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 1.0.69",
"thiserror 2.0.6",
"time",
"tracing",
"uuid",
@@ -12315,9 +12381,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
dependencies = [
"atoi",
"chrono",
@@ -13325,6 +13391,7 @@ dependencies = [
"client",
"collections",
"feature_flags",
"git_ui",
"gpui",
"http_client",
"notifications",
@@ -13349,9 +13416,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.42.0"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes 1.9.0",
@@ -13378,9 +13445,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
@@ -14042,7 +14109,7 @@ dependencies = [
name = "ui_macros"
version = "0.1.0"
dependencies = [
"convert_case 0.6.0",
"convert_case 0.7.1",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -14344,6 +14411,7 @@ dependencies = [
"command_palette_hooks",
"editor",
"futures 0.3.31",
"git_ui",
"gpui",
"indoc",
"itertools 0.14.0",
@@ -15217,7 +15285,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -16218,7 +16286,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.171.0"
version = "0.172.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16226,6 +16294,7 @@ dependencies = [
"assets",
"assistant",
"assistant2",
"assistant_context_editor",
"assistant_settings",
"assistant_tools",
"async-watch",
@@ -16355,7 +16424,7 @@ dependencies = [
[[package]]
name = "zed_csharp"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16369,7 +16438,7 @@ dependencies = [
[[package]]
name = "zed_elixir"
version = "0.1.2"
version = "0.1.4"
dependencies = [
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -16428,14 +16497,14 @@ dependencies = [
[[package]]
name = "zed_haskell"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_html"
version = "0.1.4"
version = "0.1.5"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16449,7 +16518,7 @@ dependencies = [
[[package]]
name = "zed_php"
version = "0.2.3"
version = "0.2.4"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16481,7 +16550,7 @@ dependencies = [
[[package]]
name = "zed_purescript"
version = "0.0.1"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16503,7 +16572,7 @@ dependencies = [
[[package]]
name = "zed_terraform"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16531,7 +16600,7 @@ dependencies = [
[[package]]
name = "zed_zig"
version = "0.3.2"
version = "0.3.3"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16660,9 +16729,11 @@ dependencies = [
"client",
"clock",
"collections",
"command_palette_hooks",
"ctor",
"editor",
"env_logger 0.11.6",
"feature_flags",
"futures 0.3.31",
"gpui",
"http_client",

View File

@@ -7,6 +7,7 @@ members = [
"crates/assets",
"crates/assistant",
"crates/assistant2",
"crates/assistant_context_editor",
"crates/assistant_settings",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
@@ -31,6 +32,7 @@ members = [
"crates/copilot",
"crates/db",
"crates/diagnostics",
"crates/deepseek",
"crates/docs_preprocessor",
"crates/editor",
"crates/evals",
@@ -204,6 +206,7 @@ anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
assistant2 = { path = "crates/assistant2" }
assistant_context_editor = { path = "crates/assistant_context_editor" }
assistant_settings = { path = "crates/assistant_settings" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
@@ -227,6 +230,7 @@ context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -382,7 +386,7 @@ chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26"
cocoa-foundation = "0.2.0"
convert_case = "0.6.0"
convert_case = "0.7.0"
core-foundation = "0.9.3"
core-foundation-sys = "0.8.6"
ctor = "0.2.6"
@@ -398,7 +402,8 @@ fork = "0.2.0"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.19", default-features = false }
# TODO: get back to regular versions when https://github.com/rust-lang/git2-rs/pull/1120 is released
git2 = { git = "https://github.com/rust-lang/git2-rs", rev = "a3b90cb3756c1bb63e2317bf9cfa57838178de5c", default-features = false }
globset = "0.4"
handlebars = "4.3"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
@@ -487,6 +492,7 @@ strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0"
sys-locale = "0.3.1"
sysinfo = "0.31.0"
take-until = "0.2.0"
tempfile = "3.9.0"
thiserror = "1.0.29"
tiktoken-rs = "0.6.0"
@@ -542,7 +548,7 @@ wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zstd = "0.11"
metal = "0.30"
metal = "0.31"
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"

View File

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

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,8 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.5 2C3.22386 2 3 2.22386 3 2.5V12.5C3 12.7761 3.22386 13 3.5 13H11.5C11.7761 13 12 12.7761 12 12.5V6H8.5C8.22386 6 8 5.77614 8 5.5V2H3.5ZM9 2.70711L11.2929 5H9V2.70711ZM2 2.5C2 1.67157 2.67157 1 3.5 1H8.5C8.63261 1 8.75979 1.05268 8.85355 1.14645L12.8536 5.14645C12.9473 5.24021 13 5.36739 13 5.5V12.5C13 13.3284 12.3284 14 11.5 14H3.5C2.67157 14 2 13.3284 2 12.5V2.5Z"
fill="currentColor"
/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 1.33334H4.00008C3.64646 1.33334 3.30732 1.47382 3.05727 1.72387C2.80722 1.97392 2.66675 2.31305 2.66675 2.66668V13.3333C2.66675 13.687 2.80722 14.0261 3.05727 14.2762C3.30732 14.5262 3.64646 14.6667 4.00008 14.6667H12.0001C12.3537 14.6667 12.6928 14.5262 12.9429 14.2762C13.1929 14.0261 13.3334 13.687 13.3334 13.3333V4.66668L10.0001 1.33334Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.33325 1.33334V4.00001C9.33325 4.35363 9.47373 4.69277 9.72378 4.94282C9.97383 5.19287 10.313 5.33334 10.6666 5.33334H13.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 567 B

After

Width:  |  Height:  |  Size: 769 B

View File

@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.26046 3.97337C8.3527 4.17617 8.4795 4.47151 8.57375 4.69341C8.65258 4.87898 8.83437 4.99999 9.03599 4.99999H12.5C12.7761 4.99999 13 5.22385 13 5.49999V12.125C13 12.4011 12.7761 12.625 12.5 12.625H3.5C3.22386 12.625 3 12.4011 3 12.125V3.86932C3 3.59318 3.22386 3.36932 3.5 3.36932H7.34219C7.74141 3.36932 8.09483 3.60924 8.26046 3.97337Z" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M13.3333 13.3333C13.6869 13.3333 14.026 13.1929 14.2761 12.9428C14.5261 12.6928 14.6666 12.3536 14.6666 12V5.33333C14.6666 4.97971 14.5261 4.64057 14.2761 4.39052C14.026 4.14048 13.6869 4 13.3333 4H8.06659C7.84359 4.00219 7.62362 3.94841 7.42679 3.84359C7.22996 3.73877 7.06256 3.58625 6.93992 3.4L6.39992 2.6C6.27851 2.41565 6.11324 2.26432 5.91892 2.1596C5.7246 2.05488 5.50732 2.00004 5.28659 2H2.66659C2.31296 2 1.97382 2.14048 1.72378 2.39052C1.47373 2.64057 1.33325 2.97971 1.33325 3.33333V12C1.33325 12.3536 1.47373 12.6928 1.72378 12.9428C1.97382 13.1929 2.31296 13.3333 2.66659 13.3333H13.3333Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 800 B

View File

@@ -1 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2226_61)">
<path d="M7.99992 14.6667C11.6818 14.6667 14.6666 11.6819 14.6666 8C14.6666 4.3181 11.6818 1.33333 7.99992 1.33333C4.31802 1.33333 1.33325 4.3181 1.33325 8C1.33325 11.6819 4.31802 14.6667 7.99992 14.6667Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99992 1.33333C6.28807 3.13076 5.33325 5.51782 5.33325 8C5.33325 10.4822 6.28807 12.8692 7.99992 14.6667C9.71176 12.8692 10.6666 10.4822 10.6666 8C10.6666 5.51782 9.71176 3.13076 7.99992 1.33333Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 8H14.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_2226_61">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 937 B

View File

@@ -1,4 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.46115 8.43419C7.30678 8.43419 8.92229 7.43411 8.92229 5.21171C8.92229 2.98933 7.30678 1.98926 5.46115 1.98926C3.61553 1.98926 2 2.98933 2 5.21171C2 6.028 2.21794 6.67935 2.58519 7.17685C2.7184 7.35732 2.69033 7.77795 2.58387 7.97539C2.32908 8.44793 2.81048 8.9657 3.33372 8.84571C3.72539 8.75597 4.13621 8.63447 4.49574 8.4715C4.62736 8.41181 4.7727 8.38777 4.91631 8.40402C5.09471 8.42416 5.27678 8.43419 5.46115 8.43419Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.3385 6.24835C11.7049 6.74561 11.9224 7.39641 11.9224 8.2117C11.9224 9.02799 11.7044 9.67934 11.3372 10.1768C11.204 10.3573 11.232 10.7779 11.3385 10.9754C11.5933 11.4479 11.1119 11.9657 10.5886 11.8457C10.197 11.756 9.78615 11.6345 9.42662 11.4715C9.295 11.4118 9.14966 11.3878 9.00605 11.404C8.82765 11.4242 8.64558 11.4342 8.46121 11.4342C7.61469 11.4342 6.81658 11.2238 6.20055 10.7816" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.26659 13.3333C6.53897 13.986 8.00264 14.1628 9.39384 13.8319C10.785 13.5009 12.0123 12.6839 12.8544 11.5281C13.6966 10.3724 14.0982 8.95381 13.987 7.52811C13.8758 6.10241 13.259 4.76332 12.2478 3.75213C11.2366 2.74095 9.89751 2.12417 8.47181 2.01295C7.04611 1.90173 5.62757 2.30337 4.4718 3.1455C3.31603 3.98764 2.49905 5.21488 2.16807 6.60608C1.83709 7.99728 2.01388 9.46095 2.66659 10.7333L1.33325 14.6667L5.26659 13.3333Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.33325 8H5.33992" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 8H8.00667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6667 8H10.6734" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 954 B

View File

@@ -2,26 +2,27 @@
// Standard Linux bindings
{
"bindings": {
"shift-tab": "menu::SelectPrev",
"home": "menu::SelectFirst",
"pageup": "menu::SelectFirst",
"shift-pageup": "menu::SelectFirst",
"ctrl-p": "menu::SelectPrev",
"tab": "menu::SelectNext",
"pageup": "menu::SelectFirst",
"end": "menu::SelectLast",
"shift-pagedown": "menu::SelectLast",
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext",
"tab": "menu::SelectNext",
"ctrl-p": "menu::SelectPrev",
"shift-tab": "menu::SelectPrev",
"enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"escape": "menu::Cancel",
"alt-shift-enter": "menu::Restart",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
"ctrl-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
"open": "workspace::Open",
"ctrl-o": "workspace::Open",
"ctrl-=": "zed::IncreaseBufferFontSize",
"ctrl-+": "zed::IncreaseBufferFontSize",
@@ -29,7 +30,9 @@
"ctrl-0": "zed::ResetBufferFontSize",
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f11": "zed::ToggleFullScreen"
"f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "zeta::RateCompletions",
"ctrl-shift-i": "inline_completion::ToggleMenu"
}
},
{
@@ -50,8 +53,8 @@
"context": "Editor",
"bindings": {
"escape": "editor::Cancel",
"backspace": "editor::Backspace",
"shift-backspace": "editor::Backspace",
"backspace": "editor::Backspace",
"delete": "editor::Delete",
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
@@ -61,11 +64,19 @@
"ctrl-k q": "editor::Rewrap",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
"cut": "editor::Cut",
"shift-delete": "editor::Cut",
"ctrl-x": "editor::Cut",
"copy": "editor::Copy",
"ctrl-insert": "editor::Copy",
"ctrl-c": "editor::Copy",
"paste": "editor::Paste",
"shift-insert": "editor::Paste",
"ctrl-y": "editor::Redo",
"ctrl-v": "editor::Paste",
"undo": "editor::Undo",
"ctrl-z": "editor::Undo",
"redo": "editor::Redo",
"ctrl-y": "editor::Redo",
"ctrl-shift-z": "editor::Redo",
"up": "editor::MoveUp",
"ctrl-up": "editor::LineUp",
@@ -97,41 +108,34 @@
"ctrl-l": "editor::SelectLine",
"ctrl-shift-i": "editor::Format",
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
"ctrl-alt-space": "editor::ShowCharacterPalette",
"ctrl-;": "editor::ToggleLineNumbers",
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
"ctrl-'": "editor::ToggleHunkDiff",
"ctrl-'": "editor::ToggleSelectedDiffHunks",
"ctrl-\"": "editor::ExpandAllHunkDiffs",
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu"
}
},
{
// Separate block with same context so these display in context menus
"context": "Editor",
"bindings": {
"ctrl-x": "editor::Cut",
"ctrl-c": "editor::Copy",
"ctrl-v": "editor::Paste"
"shift-f10": "editor::OpenContextMenu",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
}
},
{
"context": "Editor && mode == full",
"bindings": {
"enter": "editor::Newline",
"shift-enter": "editor::Newline",
"enter": "editor::Newline",
"ctrl-enter": "editor::NewlineAbove",
"ctrl-shift-enter": "editor::NewlineBelow",
"ctrl-k ctrl-z": "editor::ToggleSoftWrap",
"ctrl-k z": "editor::ToggleSoftWrap",
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
@@ -165,6 +169,7 @@
{
"context": "Markdown",
"bindings": {
"copy": "markdown::Copy",
"ctrl-c": "markdown::Copy"
}
},
@@ -178,12 +183,14 @@
"ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-k h": "assistant::DeployHistory",
"ctrl-k l": "assistant::DeployPromptLibrary",
"new": "assistant::NewContext",
"ctrl-n": "assistant::NewContext"
}
},
{
"context": "PromptLibrary",
"bindings": {
"new": "prompt_library::NewPrompt",
"ctrl-n": "prompt_library::NewPrompt",
"ctrl-shift-s": "prompt_library::ToggleDefaultPrompt"
}
@@ -197,6 +204,7 @@
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"ctrl-f": "search::FocusSearch",
"find": "search::FocusSearch",
"ctrl-h": "search::ToggleReplace",
"ctrl-l": "search::ToggleSelection"
}
@@ -219,6 +227,7 @@
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus",
"shift-find": "search::FocusSearch",
"ctrl-shift-f": "search::FocusSearch",
"ctrl-shift-h": "search::ToggleReplace",
"alt-ctrl-g": "search::ToggleRegex",
@@ -251,32 +260,48 @@
{
"context": "Pane",
"bindings": {
"alt-1": ["pane::ActivateItem", 0],
"alt-2": ["pane::ActivateItem", 1],
"alt-3": ["pane::ActivateItem", 2],
"alt-4": ["pane::ActivateItem", 3],
"alt-5": ["pane::ActivateItem", 4],
"alt-6": ["pane::ActivateItem", 5],
"alt-7": ["pane::ActivateItem", 6],
"alt-8": ["pane::ActivateItem", 7],
"alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"back": "pane::GoBack",
"forward": "pane::GoForward",
"ctrl-w": "pane::CloseActiveItem",
"ctrl-f4": "pane::CloseActiveItem",
"ctrl-w": "pane::CloseActiveItem",
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
"ctrl-shift-f": "pane::DeploySearch",
"back": "pane::GoBack",
"ctrl-alt--": "pane::GoBack",
"ctrl-alt-_": "pane::GoForward",
"forward": "pane::GoForward",
"ctrl-alt-g": "search::SelectNextMatch",
"f3": "search::SelectNextMatch",
"ctrl-alt-shift-g": "search::SelectPrevMatch",
"shift-f3": "search::SelectPrevMatch",
"ctrl-shift-f": "project_search::ToggleFocus",
"shift-find": "project_search::ToggleFocus",
"ctrl-alt-shift-h": "search::ToggleReplace",
"ctrl-alt-shift-l": "search::ToggleSelection",
"alt-enter": "search::SelectAllMatches",
"alt-c": "search::ToggleCaseSensitive",
"alt-w": "search::ToggleWholeWord",
"alt-r": "search::ToggleRegex",
"alt-ctrl-f": "project_search::ToggleFilters",
"alt-find": "project_search::ToggleFilters",
"ctrl-alt-shift-r": "search::ToggleRegex",
"ctrl-alt-shift-x": "search::ToggleRegex",
"alt-r": "search::ToggleRegex",
"ctrl-k shift-enter": "pane::TogglePinTab"
}
},
@@ -351,39 +376,25 @@
"ctrl-g": "go_to_line::Toggle"
}
},
{
"context": "Pane",
"bindings": {
"alt-1": ["pane::ActivateItem", 0],
"alt-2": ["pane::ActivateItem", 1],
"alt-3": ["pane::ActivateItem", 2],
"alt-4": ["pane::ActivateItem", 3],
"alt-5": ["pane::ActivateItem", 4],
"alt-6": ["pane::ActivateItem", 5],
"alt-7": ["pane::ActivateItem", 6],
"alt-8": ["pane::ActivateItem", 7],
"alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem",
"ctrl-alt--": "pane::GoBack",
"ctrl-alt-_": "pane::GoForward",
"f3": "search::SelectNextMatch",
"shift-f3": "search::SelectPrevMatch",
"ctrl-shift-f": "project_search::ToggleFocus"
}
},
{
"context": "Workspace",
"bindings": {
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
"alt-open": "projects::OpenRecent",
"alt-ctrl-o": "projects::OpenRecent",
"alt-shift-open": "projects::OpenRemote",
"alt-ctrl-shift-o": "projects::OpenRemote",
"alt-ctrl-shift-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"save": "workspace::Save",
"ctrl-s": "workspace::Save",
"ctrl-k s": "workspace::SaveWithoutFormat",
"shift-save": "workspace::SaveAs",
"ctrl-shift-s": "workspace::SaveAs",
"new": "workspace::NewFile",
"ctrl-n": "workspace::NewFile",
"shift-new": "workspace::NewWindow",
"ctrl-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"alt-1": ["workspace::ActivatePane", 0],
@@ -399,6 +410,7 @@
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
"ctrl-alt-y": "workspace::CloseAllDocks",
"shift-find": "pane::DeploySearch",
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-shift-t": "pane::ReopenClosedItem",
@@ -409,12 +421,14 @@
"ctrl-tab": "tab_switcher::Toggle",
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"ctrl-e": "file_finder::Toggle",
"ctrl-shift-p": "command_palette::Toggle",
"f1": "command_palette::Toggle",
"ctrl-shift-p": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-shift-g": "git_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"alt-save": "workspace::SaveAll",
"ctrl-alt-s": "workspace::SaveAll",
"ctrl-k m": "language_selector::Toggle",
"escape": "workspace::Unfollow",
@@ -447,7 +461,6 @@
{
"context": "Editor",
"bindings": {
"ctrl-shift-k": "editor::DeleteLine",
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
@@ -505,10 +518,10 @@
{
"context": "Editor && (showing_code_actions || showing_completions)",
"bindings": {
"up": "editor::ContextMenuPrev",
"ctrl-p": "editor::ContextMenuPrev",
"down": "editor::ContextMenuNext",
"up": "editor::ContextMenuPrev",
"ctrl-n": "editor::ContextMenuNext",
"down": "editor::ContextMenuNext",
"pageup": "editor::ContextMenuFirst",
"pagedown": "editor::ContextMenuLast"
}
@@ -532,7 +545,7 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit",
"ctrl-alt-enter": "editor::OpenExcerptsSplit",
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
@@ -559,6 +572,7 @@
"ctrl-enter": "assistant::Assist",
"ctrl-shift-enter": "assistant::Edit",
"ctrl-s": "workspace::Save",
"save": "workspace::Save",
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"shift-enter": "assistant::Split",
@@ -571,6 +585,7 @@
"context": "AssistantPanel2",
"bindings": {
"ctrl-n": "assistant2::NewThread",
"new": "assistant2::NewThread",
"ctrl-shift-h": "assistant2::OpenHistory",
"ctrl-alt-/": "assistant2::ToggleModelSelector",
"ctrl-shift-a": "assistant2::ToggleContextPicker",
@@ -623,14 +638,16 @@
"escape": "menu::Cancel",
"left": "outline_panel::CollapseSelectedEntry",
"right": "outline_panel::ExpandSelectedEntry",
"alt-copy": "outline_panel::CopyPath",
"ctrl-alt-c": "outline_panel::CopyPath",
"alt-shift-copy": "outline_panel::CopyRelativePath",
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
"alt-ctrl-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit"
"ctrl-alt-enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -638,42 +655,72 @@
"bindings": {
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"new": "project_panel::NewFile",
"ctrl-n": "project_panel::NewFile",
"alt-new": "project_panel::NewDirectory",
"alt-ctrl-n": "project_panel::NewDirectory",
"cut": "project_panel::Cut",
"ctrl-x": "project_panel::Cut",
"copy": "project_panel::Copy",
"ctrl-insert": "project_panel::Copy",
"ctrl-c": "project_panel::Copy",
"paste": "project_panel::Paste",
"shift-insert": "project_panel::Paste",
"ctrl-v": "project_panel::Paste",
"alt-copy": "project_panel::CopyPath",
"ctrl-alt-c": "project_panel::CopyPath",
"alt-shift-copy": "project_panel::CopyRelativePath",
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"enter": "project_panel::Rename",
"f2": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-find": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"escape": "menu::Cancel"
}
},
{
// Separate block with same context so these display in context menus
"context": "ProjectPanel",
"bindings": {
"f2": "project_panel::Rename",
"ctrl-c": "project_panel::Copy",
"ctrl-x": "project_panel::Cut",
"ctrl-v": "project_panel::Paste",
"delete": ["project_panel::Trash", { "skip_prompt": false }]
}
},
{
"context": "ProjectPanel && not_editing",
"bindings": {
"space": "project_panel::Open"
}
},
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrev",
"down": "menu::SelectNext",
"enter": "menu::Confirm",
"space": "git::ToggleStaged",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll"
}
},
{
"context": "GitPanel && CommitEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::FocusChanges",
"ctrl-enter": "git::CommitChanges",
"ctrl-shift-enter": "git::CommitAllChanges"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -734,9 +781,9 @@
{
"context": "TabSwitcher",
"bindings": {
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext",
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}
},
@@ -744,12 +791,17 @@
"context": "Terminal",
"bindings": {
"ctrl-alt-space": "terminal::ShowCharacterPalette",
"copy": "terminal::Copy",
"ctrl-insert": "terminal::Copy",
"ctrl-shift-c": "terminal::Copy",
"paste": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
// Overrides for conflicting keybindings
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
"ctrl-shift-a": "editor::SelectAll",
"find": "buffer_search::Deploy",
"ctrl-shift-f": "buffer_search::Deploy",
"ctrl-shift-l": "terminal::Clear",
"ctrl-shift-w": "pane::CloseActiveItem",
@@ -769,13 +821,5 @@
"shift-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode"
}
},
{
// Separate block with same context so these display in context menus
"context": "Terminal",
"bindings": {
"ctrl-shift-c": "terminal::Copy",
"ctrl-shift-v": "terminal::Paste"
}
}
]

View File

@@ -3,27 +3,27 @@
{
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrev",
"shift-tab": "menu::SelectPrev",
"home": "menu::SelectFirst",
"pageup": "menu::SelectFirst",
"shift-pageup": "menu::SelectFirst",
"ctrl-p": "menu::SelectPrev",
"down": "menu::SelectNext",
"tab": "menu::SelectNext",
"end": "menu::SelectLast",
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext",
"pageup": "menu::SelectFirst",
"cmd-up": "menu::SelectFirst",
"end": "menu::SelectLast",
"shift-pagedown": "menu::SelectLast",
"pagedown": "menu::SelectLast",
"cmd-down": "menu::SelectLast",
"tab": "menu::SelectNext",
"ctrl-n": "menu::SelectNext",
"down": "menu::SelectNext",
"shift-tab": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrev",
"up": "menu::SelectPrev",
"enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"escape": "menu::Cancel",
"alt-shift-enter": "menu::Restart",
"cmd-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
@@ -38,7 +38,9 @@
"alt-cmd-h": "zed::HideOthers",
"cmd-m": "zed::Minimize",
"fn-f": "zed::ToggleFullScreen",
"ctrl-cmd-f": "zed::ToggleFullScreen"
"ctrl-cmd-f": "zed::ToggleFullScreen",
"ctrl-shift-z": "zeta::RateCompletions",
"ctrl-shift-i": "inline_completion::ToggleMenu"
}
},
{
@@ -46,18 +48,18 @@
"use_key_equivalents": true,
"bindings": {
"escape": "editor::Cancel",
"backspace": "editor::Backspace",
"shift-backspace": "editor::Backspace",
"ctrl-h": "editor::Backspace",
"delete": "editor::Delete",
"backspace": "editor::Backspace",
"ctrl-d": "editor::Delete",
"delete": "editor::Delete",
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
"ctrl-t": "editor::Transpose",
"ctrl-k": "editor::KillRingCut",
"ctrl-y": "editor::KillRingYank",
"cmd-k q": "editor::Rewrap",
"cmd-k cmd-q": "editor::Rewrap",
"cmd-k q": "editor::Rewrap",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-delete": "editor::DeleteToEndOfLine",
"alt-backspace": "editor::DeleteToPreviousWordStart",
@@ -68,34 +70,33 @@
"cmd-v": "editor::Paste",
"cmd-z": "editor::Undo",
"cmd-shift-z": "editor::Redo",
"ctrl-shift-z": "zeta::RateCompletions",
"up": "editor::MoveUp",
"ctrl-up": "editor::MoveToStartOfParagraph",
"pageup": "editor::MovePageUp",
"shift-pageup": "editor::SelectPageUp",
"cmd-pageup": "editor::PageUp",
"ctrl-pageup": "editor::LineUp",
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"ctrl-down": "editor::MoveToEndOfParagraph",
"pagedown": "editor::MovePageDown",
"shift-pagedown": "editor::SelectPageDown",
"cmd-pagedown": "editor::PageDown",
"ctrl-pagedown": "editor::LineDown",
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
"right": "editor::MoveRight",
"ctrl-p": "editor::MoveUp",
"ctrl-n": "editor::MoveDown",
"ctrl-b": "editor::MoveLeft",
"left": "editor::MoveLeft",
"ctrl-f": "editor::MoveRight",
"right": "editor::MoveRight",
"ctrl-l": "editor::ScrollCursorCenter",
"alt-left": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd",
"cmd-left": "editor::MoveToBeginningOfLine",
"ctrl-a": "editor::MoveToBeginningOfLine",
"home": "editor::MoveToBeginningOfLine",
"cmd-right": "editor::MoveToEndOfLine",
"ctrl-e": "editor::MoveToEndOfLine",
"end": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
@@ -126,20 +127,21 @@
"ctrl-cmd-space": "editor::ShowCharacterPalette",
"cmd-;": "editor::ToggleLineNumbers",
"cmd-alt-z": "editor::RevertSelectedHunks",
"cmd-'": "editor::ToggleHunkDiff",
"cmd-'": "editor::ToggleSelectedDiffHunks",
"cmd-\"": "editor::ExpandAllHunkDiffs",
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp",
"ctrl-f12": "editor::GoToDeclaration",
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
}
},
{
"context": "Editor && mode == full",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
"shift-enter": "editor::Newline",
"enter": "editor::Newline",
"cmd-enter": "editor::NewlineBelow",
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-k z": "editor::ToggleSoftWrap",
@@ -226,6 +228,7 @@
"use_key_equivalents": true,
"bindings": {
"cmd-n": "assistant2::NewThread",
"cmd-alt-p": "assistant2::NewPromptEditor",
"cmd-shift-h": "assistant2::OpenHistory",
"cmd-alt-/": "assistant2::ToggleModelSelector",
"cmd-shift-a": "assistant2::ToggleContextPicker",
@@ -340,10 +343,10 @@
"context": "Pane",
"use_key_equivalents": true,
"bindings": {
"cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem",
"alt-cmd-left": "pane::ActivatePrevItem",
"cmd-{": "pane::ActivatePrevItem",
"alt-cmd-right": "pane::ActivateNextItem",
"cmd-}": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"cmd-w": "pane::CloseActiveItem",
@@ -373,10 +376,10 @@
"bindings": {
"cmd-[": "editor::Outdent",
"cmd-]": "editor::Indent",
"cmd-alt-up": "editor::AddSelectionAbove", // Insert cursor above
"cmd-ctrl-p": "editor::AddSelectionAbove",
"cmd-alt-down": "editor::AddSelectionBelow", // Insert cursor below
"cmd-ctrl-n": "editor::AddSelectionBelow",
"cmd-ctrl-p": "editor::AddSelectionAbove", // Insert cursor above
"cmd-alt-up": "editor::AddSelectionAbove",
"cmd-ctrl-n": "editor::AddSelectionBelow", // Insert cursor below
"cmd-alt-down": "editor::AddSelectionBelow",
"cmd-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -403,8 +406,8 @@
"shift-f12": "editor::GoToImplementation",
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"cmd-|": "editor::MoveToEnclosingBracket",
"ctrl-m": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"cmd-k cmd-l": "editor::ToggleFold",
@@ -501,6 +504,7 @@
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-b": "outline_panel::ToggleFocus",
"ctrl-shift-g": "git_panel::ToggleFocus",
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
"cmd-k m": "language_selector::Toggle",
@@ -618,7 +622,7 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit",
"cmd-alt-enter": "editor::OpenExcerptsSplit",
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
@@ -665,7 +669,7 @@
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit"
"cmd-alt-enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -806,9 +810,9 @@
"context": "TabSwitcher",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext",
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}
},
@@ -839,16 +843,16 @@
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"cmd-up": "terminal::ScrollPageUp",
"cmd-down": "terminal::ScrollPageDown",
"shift-pageup": "terminal::ScrollPageUp",
"cmd-up": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
"cmd-down": "terminal::ScrollPageDown",
"shift-up": "terminal::ScrollLineUp",
"shift-down": "terminal::ScrollLineDown",
"cmd-home": "terminal::ScrollToTop",
"cmd-end": "terminal::ScrollToBottom",
"shift-home": "terminal::ScrollToTop",
"cmd-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom",
"cmd-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode",
"ctrl-k up": "pane::SplitUp",
"ctrl-k down": "pane::SplitDown",

View File

@@ -15,7 +15,9 @@
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
//"ctrl-space": "editor::SetMark",
"ctrl-space": "editor::SetMark", // set-mark
"ctrl-@": "editor::SetMark", // set-mark
"ctrl-x ctrl-x": "editor::SwapSelectionEnds", // exchange-point-and-mark
"ctrl-f": "editor::MoveRight", // forward-char
"ctrl-b": "editor::MoveLeft", // backward-char
"ctrl-n": "editor::MoveDown", // next-line
@@ -24,6 +26,8 @@
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
@@ -55,6 +59,32 @@
"alt-^": "editor::JoinLines" // join-line
}
},
{
"context": "Editor && selection_mode", // region selection
"bindings": {
"right": "editor::SelectRight",
"left": "editor::SelectLeft",
"down": "editor::SelectDown",
"up": "editor::SelectUp",
"alt-left": "editor::SelectToPreviousWordStart",
"alt-right": "editor::SelectToNextWordEnd",
"pagedown": "editor::SelectPageDown",
"pageup": "editor::SelectPageUp",
"ctrl-f": "editor::SelectRight",
"ctrl-b": "editor::SelectLeft",
"ctrl-n": "editor::SelectDown",
"ctrl-p": "editor::SelectUp",
"home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"ctrl-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-<": "editor::SelectToBeginning",
"alt->": "editor::SelectToEnd",
"ctrl-g": "editor::Cancel"
}
},
{
"context": "Workspace",
"bindings": {

View File

@@ -15,7 +15,9 @@
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
//"ctrl-space": "editor::SetMark",
"ctrl-space": "editor::SetMark", // set-mark
"ctrl-@": "editor::SetMark", // set-mark
"ctrl-x ctrl-x": "editor::SwapSelectionEnds", // exchange-point-and-mark
"ctrl-f": "editor::MoveRight", // forward-char
"ctrl-b": "editor::MoveLeft", // backward-char
"ctrl-n": "editor::MoveDown", // next-line
@@ -24,6 +26,8 @@
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
@@ -55,6 +59,32 @@
"alt-^": "editor::JoinLines" // join-line
}
},
{
"context": "Editor && selection_mode", // region selection
"bindings": {
"right": "editor::SelectRight",
"left": "editor::SelectLeft",
"down": "editor::SelectDown",
"up": "editor::SelectUp",
"alt-left": "editor::SelectToPreviousWordStart",
"alt-right": "editor::SelectToNextWordEnd",
"pagedown": "editor::SelectPageDown",
"pageup": "editor::SelectPageUp",
"ctrl-f": "editor::SelectRight",
"ctrl-b": "editor::SelectLeft",
"ctrl-n": "editor::SelectDown",
"ctrl-p": "editor::SelectUp",
"home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"ctrl-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false }],
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-<": "editor::SelectToBeginning",
"alt->": "editor::SelectToEnd",
"ctrl-g": "editor::Cancel"
}
},
{
"context": "Workspace",
"bindings": {

View File

@@ -2,21 +2,27 @@
// Standard macOS bindings
{
"bindings": {
"up": "menu::SelectPrev",
"pageup": "menu::SelectFirst",
"home": "menu::SelectFirst",
"shift-pageup": "menu::SelectFirst",
"ctrl-p": "menu::SelectPrev",
"down": "menu::SelectNext",
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext",
"pageup": "menu::SelectFirst",
"cmd-up": "menu::SelectFirst",
"end": "menu::SelectLast",
"shift-pagedown": "menu::SelectLast",
"pagedown": "menu::SelectLast",
"cmd-down": "menu::SelectLast",
"tab": "menu::SelectNext",
"ctrl-n": "menu::SelectNext",
"down": "menu::SelectNext",
"shift-tab": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrev",
"up": "menu::SelectPrev",
"enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"escape": "menu::Cancel",
"cmd-q": "storybook::Quit",
"backspace": "editor::Backspace",
"delete": "editor::Delete",

View File

@@ -4,25 +4,25 @@
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"h": "vim::Left",
"left": "vim::Left",
"h": "vim::Left",
"backspace": "vim::Backspace",
"j": "vim::Down",
"down": "vim::Down",
"ctrl-j": "vim::Down",
"enter": "vim::NextLineStart",
"j": "vim::Down",
"ctrl-m": "vim::NextLineStart",
"+": "vim::NextLineStart",
"enter": "vim::NextLineStart",
"-": "vim::PreviousLineStart",
"tab": "vim::Tab",
"shift-tab": "vim::Tab",
"k": "vim::Up",
"tab": "vim::Tab",
"up": "vim::Up",
"l": "vim::Right",
"k": "vim::Up",
"right": "vim::Right",
"l": "vim::Right",
"space": "vim::Space",
"$": "vim::EndOfLine",
"end": "vim::EndOfLine",
"$": "vim::EndOfLine",
"^": "vim::FirstNonWhitespace",
"_": "vim::StartOfLineDownward",
"g _": "vim::EndOfLineDownward",
@@ -86,6 +86,7 @@
"ctrl-[": ["vim::SwitchMode", "Normal"],
"v": "vim::ToggleVisual",
"shift-v": "vim::ToggleVisualLine",
"ctrl-g": "vim::ShowLocation",
"ctrl-v": "vim::ToggleVisualBlock",
"ctrl-q": "vim::ToggleVisualBlock",
"shift-k": "editor::Hover",
@@ -188,8 +189,8 @@
{
"context": "vim_mode == normal",
"bindings": {
"escape": "editor::Cancel",
"ctrl-[": "editor::Cancel",
"escape": "editor::Cancel",
":": "command_palette::Toggle",
".": "vim::Repeat",
"c": ["vim::PushOperator", "Change"],
@@ -226,8 +227,8 @@
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
"\"": ["vim::PushOperator", "Register"],
"g q": ["vim::PushOperator", "Rewrap"],
"g w": ["vim::PushOperator", "Rewrap"],
"g q": ["vim::PushOperator", "Rewrap"],
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore",
@@ -254,8 +255,8 @@
":": "vim::VisualCommand",
"u": "vim::ConvertToLowerCase",
"shift-u": "vim::ConvertToUpperCase",
"o": "vim::OtherEnd",
"shift-o": "vim::OtherEnd",
"o": "vim::OtherEnd",
"d": "vim::VisualDelete",
"x": "vim::VisualDelete",
"shift-d": "vim::VisualDeleteLine",
@@ -264,10 +265,10 @@
"shift-y": "vim::VisualYankLine",
"p": "vim::Paste",
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
"shift-r": "vim::SubstituteLine",
"c": "vim::Substitute",
"s": "vim::Substitute",
"shift-r": "vim::SubstituteLine",
"shift-s": "vim::SubstituteLine",
"~": "vim::ChangeCase",
"*": ["vim::MoveToNext", { "partialWord": true }],
"#": ["vim::MoveToPrev", { "partialWord": true }],
@@ -283,8 +284,8 @@
"g shift-j": "vim::JoinLinesNoWhitespace",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
@@ -302,9 +303,9 @@
{
"context": "vim_mode == insert",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"escape": "vim::NormalBefore",
"ctrl-x": null,
"ctrl-x ctrl-o": "editor::ShowCompletions",
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
@@ -352,9 +353,9 @@
{
"context": "vim_mode == replace",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"escape": "vim::NormalBefore",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
@@ -371,9 +372,9 @@
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter",
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators",
"escape": "vim::ClearOperators",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
@@ -382,9 +383,9 @@
{
"context": "vim_mode == operator",
"bindings": {
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators",
"escape": "vim::ClearOperators",
"g c": "vim::Comment"
}
},
@@ -407,6 +408,7 @@
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
// "b": "vim::AnyBrackets",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
@@ -435,7 +437,7 @@
"bindings": {
"d": "vim::CurrentLine",
"s": ["vim::PushOperator", "DeleteSurrounds"],
"o": "editor::ToggleHunkDiff", // "d o"
"o": "editor::ToggleSelectedDiffHunks", // "d o"
"p": "editor::RevertSelectedHunks" // "d p"
}
},
@@ -571,14 +573,14 @@
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
@@ -603,19 +605,19 @@
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
"ctrl-w v": "pane::SplitVertical",
"ctrl-w ctrl-v": "pane::SplitVertical",
"ctrl-w s": "pane::SplitHorizontal",
"ctrl-w v": "pane::SplitVertical",
"ctrl-w shift-s": "pane::SplitHorizontal",
"ctrl-w ctrl-s": "pane::SplitHorizontal",
"ctrl-w c": "pane::CloseAllItems",
"ctrl-w s": "pane::SplitHorizontal",
"ctrl-w ctrl-c": "pane::CloseAllItems",
"ctrl-w q": "pane::CloseAllItems",
"ctrl-w c": "pane::CloseAllItems",
"ctrl-w ctrl-q": "pane::CloseAllItems",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w q": "pane::CloseAllItems",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": "workspace::NewFileSplitHorizontal",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal"
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
"ctrl-w n": "workspace::NewFileSplitHorizontal"
}
},
{
@@ -667,5 +669,20 @@
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"k": "menu::SelectPrev",
"j": "menu::SelectNext",
"g g": "menu::SelectFirst",
"shift-g": "menu::SelectLast",
"g f": "menu::Confirm",
"i": "git_panel::FocusEditor",
"x": "git::ToggleStaged",
"shift-x": "git::StageAll",
"shift-u": "git::UnstageAll"
}
}
]

View File

@@ -592,7 +592,9 @@
// Whether or not to show the tab bar in the editor
"show": true,
// Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true
"show_nav_history_buttons": true,
/// Whether or not to show the tab bar buttons.
"show_tab_bar_buttons": true
},
// Settings related to the editor's tabs
"tabs": {
@@ -1166,6 +1168,9 @@
},
"lmstudio": {
"api_url": "http://localhost:1234/api/v0"
},
"deepseek": {
"api_url": "https://api.deepseek.com"
}
},
// Zed's Prettier integration settings.

View File

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

View File

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

View File

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

View File

@@ -22,14 +22,13 @@ test-support = [
[dependencies]
anyhow.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
context_server.workspace = true
@@ -38,7 +37,6 @@ editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
indexed_docs.workspace = true
indoc.workspace = true
@@ -50,27 +48,20 @@ log.workspace = true
lsp.workspace = true
menu.workspace = true
multi_buffer.workspace = true
open_ai = { workspace = true, features = ["schemars"] }
parking_lot.workspace = true
paths.workspace = true
picker.workspace = true
project.workspace = true
prompt_library.workspace = true
proto.workspace = true
regex.workspace = true
rope.workspace = true
rpc.workspace = true
schemars.workspace = true
search.workspace = true
semantic_index.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
similar.workspace = true
smallvec.workspace = true
smol.workspace = true
streaming_diff.workspace = true
strum.workspace = true
telemetry.workspace = true
telemetry_events.workspace = true
terminal.workspace = true
@@ -79,7 +70,6 @@ text.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
uuid.workspace = true
workspace.workspace = true
zed_actions.workspace = true

View File

@@ -1,84 +1,46 @@
#![cfg_attr(target_os = "windows", allow(unused, dead_code))]
mod assistant_configuration;
pub mod assistant_panel;
mod context;
pub mod context_store;
mod inline_assistant;
mod patch;
mod slash_command;
pub(crate) mod slash_command_picker;
pub mod slash_command_settings;
mod terminal_inline_assistant;
use std::path::PathBuf;
use std::sync::Arc;
use assistant_settings::AssistantSettings;
use assistant_slash_command::SlashCommandRegistry;
use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag};
use client::{proto, Client};
use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::impl_internal_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use gpui::{actions, App, Global, UpdateGlobal};
use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
};
use prompt_library::{PromptBuilder, PromptLoadingParams};
use prompt_library::PromptBuilder;
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use settings::{Settings, SettingsStore};
use util::ResultExt;
pub use crate::assistant_panel::{AssistantPanel, AssistantPanelEvent};
pub use crate::context::*;
pub use crate::context_store::*;
pub(crate) use crate::inline_assistant::*;
pub use crate::patch::*;
use crate::slash_command_settings::SlashCommandSettings;
actions!(
assistant,
[
Assist,
Edit,
Split,
CopyCode,
CycleMessageRole,
QuoteSelection,
InsertIntoEditor,
ToggleFocus,
InsertActivePrompt,
DeployHistory,
DeployPromptLibrary,
ConfirmCommand,
NewContext,
ToggleModelSelector,
CycleNextInlineAssist,
CyclePreviousInlineAssist
]
);
#[derive(PartialEq, Clone)]
pub enum InsertDraggedFiles {
ProjectPaths(Vec<PathBuf>),
ExternalFiles(Vec<PathBuf>),
}
impl_internal_actions!(assistant, [InsertDraggedFiles]);
const DEFAULT_CONTEXT_LINES: usize = 50;
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct MessageId(clock::Lamport);
impl MessageId {
pub fn as_u64(self) -> u64 {
self.0.as_u64()
}
}
#[derive(Deserialize, Debug)]
pub struct LanguageModelUsage {
pub prompt_tokens: u32,
@@ -93,55 +55,6 @@ pub struct LanguageModelChoiceDelta {
pub finish_reason: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum MessageStatus {
Pending,
Done,
Error(SharedString),
Canceled,
}
impl MessageStatus {
pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
match status.variant {
Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
Some(proto::context_message_status::Variant::Error(error)) => {
MessageStatus::Error(error.message.into())
}
Some(proto::context_message_status::Variant::Canceled(_)) => MessageStatus::Canceled,
None => MessageStatus::Pending,
}
}
pub fn to_proto(&self) -> proto::ContextMessageStatus {
match self {
MessageStatus::Pending => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Pending(
proto::context_message_status::Pending {},
)),
},
MessageStatus::Done => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Done(
proto::context_message_status::Done {},
)),
},
MessageStatus::Error(message) => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Error(
proto::context_message_status::Error {
message: message.to_string(),
},
)),
},
MessageStatus::Canceled => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Canceled(
proto::context_message_status::Canceled {},
)),
},
}
}
}
/// The state pertaining to the Assistant.
#[derive(Default)]
struct Assistant {
@@ -154,7 +67,7 @@ impl Global for Assistant {}
impl Assistant {
const NAMESPACE: &'static str = "assistant";
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
fn set_enabled(&mut self, enabled: bool, cx: &mut App) {
if self.enabled == enabled {
return;
}
@@ -178,9 +91,9 @@ impl Assistant {
pub fn init(
fs: Arc<dyn Fs>,
client: Arc<Client>,
stdout_is_a_pty: bool,
cx: &mut AppContext,
) -> Arc<PromptBuilder> {
prompt_builder: Arc<PromptBuilder>,
cx: &mut App,
) {
cx.set_global(Assistant::default());
AssistantSettings::register(cx);
SlashCommandSettings::register(cx);
@@ -212,7 +125,7 @@ pub fn init(
})
.detach();
context_store::init(&client.clone().into());
assistant_context_editor::init(client.clone(), cx);
prompt_library::init(cx);
init_language_model_settings(cx);
assistant_slash_command::init(cx);
@@ -220,16 +133,6 @@ pub fn init(
assistant_panel::init(cx);
context_server::init(cx);
let prompt_builder = PromptBuilder::new(Some(PromptLoadingParams {
fs: fs.clone(),
repo_path: stdout_is_a_pty
.then(|| std::env::current_dir().log_err())
.flatten(),
cx,
}))
.log_err()
.map(Arc::new)
.unwrap_or_else(|| Arc::new(PromptBuilder::new(None).unwrap()));
register_slash_commands(Some(prompt_builder.clone()), cx);
inline_assistant::init(
fs.clone(),
@@ -260,11 +163,9 @@ pub fn init(
});
})
.detach();
prompt_builder
}
fn init_language_model_settings(cx: &mut AppContext) {
fn init_language_model_settings(cx: &mut App) {
update_active_language_model_from_settings(cx);
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
@@ -283,7 +184,7 @@ fn init_language_model_settings(cx: &mut AppContext) {
.detach();
}
fn update_active_language_model_from_settings(cx: &mut AppContext) {
fn update_active_language_model_from_settings(cx: &mut App) {
let settings = AssistantSettings::get_global(cx);
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
let model_id = LanguageModelId::from(settings.default_model.model.clone());
@@ -303,7 +204,7 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
});
}
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut AppContext) {
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut App) {
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
@@ -377,7 +278,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
.detach();
}
fn update_slash_commands_from_settings(cx: &mut AppContext) {
fn update_slash_commands_from_settings(cx: &mut App) {
let slash_command_registry = SlashCommandRegistry::global(cx);
let settings = SlashCommandSettings::get_global(cx);
@@ -396,24 +297,6 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
}
}
pub fn humanize_token_count(count: usize) -> String {
match count {
0..=999 => count.to_string(),
1000..=9999 => {
let thousands = count / 1000;
let hundreds = (count % 1000 + 50) / 100;
if hundreds == 0 {
format!("{}k", thousands)
} else if hundreds == 10 {
format!("{}k", thousands + 1)
} else {
format!("{}.{}k", thousands, hundreds)
}
}
_ => format!("{}k", (count + 500) / 1000),
}
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {

View File

@@ -0,0 +1,199 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::{canvas, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, ElevationIndex};
use workspace::Item;
pub struct ConfigurationView {
focus_handle: FocusHandle,
configuration_views: HashMap<LanguageModelProviderId, AnyView>,
_registry_subscription: Subscription,
}
impl ConfigurationView {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
|this, _, event: &language_model::Event, window, cx| match event {
language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider {
this.add_configuration_view(&provider, window, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
this.remove_configuration_view(provider_id);
}
_ => {}
},
);
let mut this = Self {
focus_handle,
configuration_views: HashMap::default(),
_registry_subscription: registry_subscription,
};
this.build_configuration_views(window, cx);
this
}
fn build_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_configuration_view(&provider, window, cx);
}
}
fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
self.configuration_views.remove(provider_id);
}
fn add_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let configuration_view = provider.configuration_view(window, cx);
self.configuration_views
.insert(provider.id(), configuration_view);
}
fn render_provider_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>,
) -> Div {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
let configuration_view = self.configuration_views.get(&provider.id()).cloned();
let open_new_context = cx.listener({
let provider = provider.clone();
move |_, _, _window, cx| {
cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
provider.clone(),
))
}
});
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
.when(provider.is_authenticated(cx), move |this| {
this.child(
h_flex().justify_end().child(
Button::new(
SharedString::from(format!("new-context-{provider_id}")),
"Open New Chat",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.on_click(open_new_context),
),
)
}),
)
.child(
div()
.p(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().surface_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.when(configuration_view.is_none(), |this| {
this.child(div().child(Label::new(format!(
"No configuration view for {}",
provider_name
))))
})
.when_some(configuration_view, |this, configuration_view| {
this.child(configuration_view)
}),
)
}
}
impl Render for ConfigurationView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
let provider_views = providers
.into_iter()
.map(|provider| self.render_provider_view(&provider, cx))
.collect::<Vec<_>>();
let mut element = v_flex()
.id("assistant-configuration-view")
.track_focus(&self.focus_handle(cx))
.bg(cx.theme().colors().editor_background)
.size_full()
.overflow_y_scroll()
.child(
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.border_b_1()
.border_color(cx.theme().colors().border)
.gap_1()
.child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
.child(
Label::new(
"At least one LLM provider must be configured to use the Assistant.",
)
.color(Color::Muted),
),
)
.child(
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.mt_1()
.gap_6()
.flex_1()
.children(provider_views),
)
.into_any();
// We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
// because we couldn't the element to take up the size of the parent.
canvas(
move |bounds, window, cx| {
element.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
element
},
|_, mut element, window, cx| {
element.paint(window, cx);
},
)
.flex_1()
.w_full()
}
}
pub enum ConfigurationViewEvent {
NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
}
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
impl Focusable for ConfigurationView {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Item for ConfigurationView {
type Event = ConfigurationViewEvent;
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some("Configuration".into())
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -20,7 +20,9 @@ test-support = [
[dependencies]
anyhow.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
chrono.workspace = true
@@ -37,6 +39,7 @@ fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
heed.workspace = true
html_to_markdown.workspace = true
http_client.workspace = true
itertools.workspace = true
@@ -50,6 +53,7 @@ markdown.workspace = true
menu.workspace = true
multi_buffer.workspace = true
parking_lot.workspace = true
paths.workspace = true
picker.workspace = true
project.workspace = true
prompt_library.workspace = true
@@ -69,7 +73,6 @@ theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
unindent.workspace = true
util.workspace = true
uuid.workspace = true
workspace.workspace = true

View File

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

View File

@@ -1,4 +1,5 @@
mod active_thread;
mod assistant_configuration;
mod assistant_model_selector;
mod assistant_panel;
mod buffer_codegen;
@@ -23,23 +24,24 @@ use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use fs::Fs;
use gpui::{actions, AppContext};
use prompt_library::{PromptBuilder, PromptLoadingParams};
use gpui::{actions, App};
use prompt_library::PromptBuilder;
use settings::Settings as _;
use util::ResultExt;
pub use crate::assistant_panel::AssistantPanel;
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};
pub use crate::inline_assistant::InlineAssistant;
actions!(
assistant2,
[
ToggleFocus,
NewThread,
NewPromptEditor,
ToggleContextPicker,
ToggleModelSelector,
RemoveAllContext,
OpenHistory,
OpenPromptEditorHistory,
OpenConfiguration,
RemoveSelectedThread,
Chat,
ChatMode,
@@ -57,20 +59,15 @@ actions!(
const NAMESPACE: &str = "assistant2";
/// Initializes the `assistant2` crate.
pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, stdout_is_a_pty: bool, cx: &mut AppContext) {
pub fn init(
fs: Arc<dyn Fs>,
client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>,
cx: &mut App,
) {
AssistantSettings::register(cx);
assistant_panel::init(cx);
let prompt_builder = PromptBuilder::new(Some(PromptLoadingParams {
fs: fs.clone(),
repo_path: stdout_is_a_pty
.then(|| std::env::current_dir().log_err())
.flatten(),
cx,
}))
.log_err()
.map(Arc::new)
.unwrap_or_else(|| Arc::new(PromptBuilder::new(None).unwrap()));
inline_assistant::init(
fs.clone(),
prompt_builder.clone(),
@@ -87,7 +84,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, stdout_is_a_pty: bool, cx: &mu
feature_gate_assistant2_actions(cx);
}
fn feature_gate_assistant2_actions(cx: &mut AppContext) {
fn feature_gate_assistant2_actions(cx: &mut App) {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE);
});

View File

@@ -0,0 +1,173 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, ElevationIndex};
use zed_actions::assistant::DeployPromptLibrary;
pub struct AssistantConfiguration {
focus_handle: FocusHandle,
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
_registry_subscription: Subscription,
}
impl AssistantConfiguration {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
|this, _, event: &language_model::Event, window, cx| match event {
language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider {
this.add_provider_configuration_view(&provider, window, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
this.remove_provider_configuration_view(provider_id);
}
_ => {}
},
);
let mut this = Self {
focus_handle,
configuration_views_by_provider: HashMap::default(),
_registry_subscription: registry_subscription,
};
this.build_provider_configuration_views(window, cx);
this
}
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_provider_configuration_view(&provider, window, cx);
}
}
fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
self.configuration_views_by_provider.remove(provider_id);
}
fn add_provider_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let configuration_view = provider.configuration_view(window, cx);
self.configuration_views_by_provider
.insert(provider.id(), configuration_view);
}
}
impl Focusable for AssistantConfiguration {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
pub enum AssistantConfigurationEvent {
NewThread(Arc<dyn LanguageModelProvider>),
}
impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
impl AssistantConfiguration {
fn render_provider_configuration(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut Context<Self>,
) -> impl IntoElement {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
let configuration_view = self
.configuration_views_by_provider
.get(&provider.id())
.cloned();
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
.when(provider.is_authenticated(cx), |parent| {
parent.child(
h_flex().justify_end().child(
Button::new(
SharedString::from(format!("new-thread-{provider_id}")),
"Open New Thread",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.on_click(cx.listener({
let provider = provider.clone();
move |_this, _event, _window, cx| {
cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(),
))
}
})),
),
)
}),
)
.child(
div()
.p(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().surface_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.map(|parent| match configuration_view {
Some(configuration_view) => parent.child(configuration_view),
None => parent.child(div().child(Label::new(format!(
"No configuration view for {provider_name}",
)))),
}),
)
}
}
impl Render for AssistantConfiguration {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.id("assistant-configuration")
.track_focus(&self.focus_handle(cx))
.bg(cx.theme().colors().editor_background)
.size_full()
.overflow_y_scroll()
.child(
h_flex().p(DynamicSpacing::Base16.rems(cx)).child(
Button::new("open-prompt-library", "Open Prompt Library")
.style(ButtonStyle::Filled)
.full_width()
.icon(IconName::Book)
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.on_click(|_event, _window, cx| cx.dispatch_action(&DeployPromptLibrary)),
),
)
.child(
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.mt_1()
.gap_6()
.flex_1()
.children(
providers
.into_iter()
.map(|provider| self.render_provider_configuration(&provider, cx)),
),
)
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -6,11 +6,10 @@ mod thread_context_picker;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use editor::Editor;
use file_context_picker::render_file_context_entry;
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, View, WeakModel, WeakView,
};
use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity};
use project::ProjectPath;
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
@@ -33,57 +32,55 @@ pub enum ConfirmBehavior {
#[derive(Debug, Clone)]
enum ContextPickerMode {
Default(View<ContextMenu>),
File(View<FileContextPicker>),
Directory(View<DirectoryContextPicker>),
Fetch(View<FetchContextPicker>),
Thread(View<ThreadContextPicker>),
Default(Entity<ContextMenu>),
File(Entity<FileContextPicker>),
Directory(Entity<DirectoryContextPicker>),
Fetch(Entity<FetchContextPicker>),
Thread(Entity<ThreadContextPicker>),
}
pub(super) struct ContextPicker {
mode: ContextPickerMode,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
thread_store: Option<WeakModel<ThreadStore>>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<ThreadStore>>,
confirm_behavior: ConfirmBehavior,
}
impl ContextPicker {
pub fn new(
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
context_store: WeakEntity<ContextStore>,
editor: WeakEntity<Editor>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
ContextPicker {
mode: ContextPickerMode::Default(ContextMenu::build(cx, |menu, _cx| menu)),
mode: ContextPickerMode::Default(ContextMenu::build(
window,
cx,
|menu, _window, _cx| menu,
)),
workspace,
context_store,
thread_store,
editor,
confirm_behavior,
}
}
pub fn init(&mut self, cx: &mut ViewContext<Self>) {
self.mode = ContextPickerMode::Default(self.build_menu(cx));
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
cx.notify();
}
fn build_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
let context_picker = cx.view().clone();
let menu = ContextMenu::build(cx, move |menu, cx| {
let kind_entry = |kind: &'static ContextKind| {
let context_picker = context_picker.clone();
ContextMenuEntry::new(kind.label())
.icon(kind.icon())
.handler(move |cx| {
context_picker.update(cx, |this, cx| this.select_kind(*kind, cx))
})
};
fn build_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<ContextMenu> {
let context_picker = cx.entity().clone();
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
let recent = self.recent_entries(cx);
let has_recent = !recent.is_empty();
let recent_entries = recent
@@ -91,9 +88,18 @@ impl ContextPicker {
.enumerate()
.map(|(ix, entry)| self.recent_menu_item(context_picker.clone(), ix, entry));
let mut context_kinds = vec![
ContextKind::File,
ContextKind::Directory,
ContextKind::FetchedUrl,
];
if self.allow_threads() {
context_kinds.push(ContextKind::Thread);
}
let menu = menu
.when(has_recent, |menu| {
menu.custom_row(|_| {
menu.custom_row(|_, _| {
div()
.mb_1()
.child(
@@ -106,7 +112,17 @@ impl ContextPicker {
})
.extend(recent_entries)
.when(has_recent, |menu| menu.separator())
.extend(ContextKind::all().into_iter().map(kind_entry));
.extend(context_kinds.into_iter().map(|kind| {
let context_picker = context_picker.clone();
ContextMenuEntry::new(kind.label())
.icon(kind.icon())
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.handler(move |window, cx| {
context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
})
}));
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => menu.keep_open_on_confirm(),
@@ -122,51 +138,61 @@ impl ContextPicker {
menu
}
fn select_kind(&mut self, kind: ContextKind, cx: &mut ViewContext<Self>) {
let context_picker = cx.view().downgrade();
/// Whether threads are allowed as context.
pub fn allow_threads(&self) -> bool {
self.thread_store.is_some()
}
fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
let context_picker = cx.entity().downgrade();
match kind {
ContextKind::File => {
self.mode = ContextPickerMode::File(cx.new_view(|cx| {
self.mode = ContextPickerMode::File(cx.new(|cx| {
FileContextPicker::new(
context_picker.clone(),
self.workspace.clone(),
self.editor.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Directory => {
self.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
self.mode = ContextPickerMode::Directory(cx.new(|cx| {
DirectoryContextPicker::new(
context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::FetchedUrl => {
self.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
FetchContextPicker::new(
context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Thread => {
if let Some(thread_store) = self.thread_store.as_ref() {
self.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
self.mode = ContextPickerMode::Thread(cx.new(|cx| {
ThreadContextPicker::new(
thread_store.clone(),
context_picker.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
@@ -175,12 +201,12 @@ impl ContextPicker {
}
cx.notify();
cx.focus_self();
cx.focus_self(window);
}
fn recent_menu_item(
&self,
context_picker: View<ContextPicker>,
context_picker: Entity<ContextPicker>,
ix: usize,
entry: RecentEntry,
) -> ContextMenuItem {
@@ -193,7 +219,7 @@ impl ContextPicker {
let path = project_path.path.clone();
ContextMenuItem::custom_entry(
move |cx| {
move |_window, cx| {
render_file_context_entry(
ElementId::NamedInteger("ctx-recent".into(), ix),
&path,
@@ -203,9 +229,9 @@ impl ContextPicker {
)
.into_any()
},
move |cx| {
move |window, cx| {
context_picker.update(cx, |this, cx| {
this.add_recent_file(project_path.clone(), cx);
this.add_recent_file(project_path.clone(), window, cx);
})
},
)
@@ -215,13 +241,14 @@ impl ContextPicker {
let view_thread = thread.clone();
ContextMenuItem::custom_entry(
move |cx| {
move |_window, cx| {
render_thread_context_entry(&view_thread, context_store.clone(), cx)
.into_any()
},
move |cx| {
move |_window, cx| {
context_picker.update(cx, |this, cx| {
this.add_recent_thread(thread.clone(), cx);
this.add_recent_thread(thread.clone(), cx)
.detach_and_log_err(cx);
})
},
)
@@ -229,7 +256,12 @@ impl ContextPicker {
}
}
fn add_recent_file(&self, project_path: ProjectPath, cx: &mut ViewContext<Self>) {
fn add_recent_file(
&self,
project_path: ProjectPath,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(context_store) = self.context_store.upgrade() else {
return;
};
@@ -238,34 +270,43 @@ impl ContextPicker {
context_store.add_file_from_path(project_path.clone(), cx)
});
cx.spawn(|_, mut cx| async move { task.await.notify_async_err(&mut cx) })
.detach();
cx.spawn_in(window, |_, mut cx| async move {
task.await.notify_async_err(&mut cx)
})
.detach();
cx.notify();
}
fn add_recent_thread(&self, thread: ThreadContextEntry, cx: &mut ViewContext<Self>) {
fn add_recent_thread(
&self,
thread: ThreadContextEntry,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(context_store) = self.context_store.upgrade() else {
return;
return Task::ready(Err(anyhow!("context store not available")));
};
let Some(thread) = self
let Some(thread_store) = self
.thread_store
.clone()
.and_then(|this| this.upgrade())
.and_then(|this| this.update(cx, |this, cx| this.open_thread(&thread.id, cx)))
.as_ref()
.and_then(|thread_store| thread_store.upgrade())
else {
return;
return Task::ready(Err(anyhow!("thread store not available")));
};
context_store.update(cx, |context_store, cx| {
context_store.add_thread(thread, cx);
});
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&thread.id, cx));
cx.spawn(|this, mut cx| async move {
let thread = open_thread_task.await?;
context_store.update(&mut cx, |context_store, cx| {
context_store.add_thread(thread, cx);
})?;
cx.notify();
this.update(&mut cx, |_this, cx| cx.notify())
})
}
fn recent_entries(&self, cx: &mut WindowContext) -> Vec<RecentEntry> {
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
return vec![];
};
@@ -316,19 +357,17 @@ impl ContextPicker {
return recent;
};
thread_store.update(cx, |thread_store, cx| {
thread_store.update(cx, |thread_store, _cx| {
recent.extend(
thread_store
.threads(cx)
.threads()
.into_iter()
.filter(|thread| !current_threads.contains(thread.read(cx).id()))
.filter(|thread| !current_threads.contains(&thread.id))
.take(2)
.map(|thread| {
let thread = thread.read(cx);
RecentEntry::Thread(ThreadContextEntry {
id: thread.id().clone(),
summary: thread.summary_or_default(),
id: thread.id,
summary: thread.summary,
})
}),
)
@@ -337,7 +376,7 @@ impl ContextPicker {
recent
}
fn active_singleton_buffer_path(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
fn active_singleton_buffer_path(workspace: &Workspace, cx: &App) -> Option<PathBuf> {
let active_item = workspace.active_item(cx)?;
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
@@ -350,8 +389,8 @@ impl ContextPicker {
impl EventEmitter<DismissEvent> for ContextPicker {}
impl FocusableView for ContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
impl Focusable for ContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match &self.mode {
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
@@ -363,7 +402,7 @@ impl FocusableView for ContextPicker {
}
impl Render for ContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.w(px(400.))
.min_w(px(400.))

View File

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

View File

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

View File

@@ -1,15 +1,25 @@
use std::collections::BTreeSet;
use std::ops::Range;
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use editor::actions::FoldAt;
use editor::display_map::{Crease, FoldId};
use editor::scroll::Autoscroll;
use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
use file_icons::FileIcons;
use fuzzy::PathMatch;
use gpui::{
AppContext, DismissEvent, FocusHandle, FocusableView, Stateful, Task, View, WeakModel, WeakView,
AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
WeakEntity,
};
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{prelude::*, ListItem, Tooltip};
use rope::Point;
use text::SelectionGoal;
use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip};
use util::ResultExt as _;
use workspace::{notifications::NotifyResultExt, Workspace};
@@ -17,45 +27,49 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::{ContextStore, FileInclusion};
pub struct FileContextPicker {
picker: View<Picker<FileContextPickerDelegate>>,
picker: Entity<Picker<FileContextPickerDelegate>>,
}
impl FileContextPicker {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let delegate = FileContextPickerDelegate::new(
context_picker,
workspace,
editor,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl FocusableView for FileContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
impl Focusable for FileContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FileContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
@@ -63,14 +77,16 @@ pub struct FileContextPickerDelegate {
impl FileContextPickerDelegate {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
context_picker: WeakEntity<ContextPicker>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
context_picker,
workspace,
editor,
context_store,
confirm_behavior,
matches: Vec::new(),
@@ -82,8 +98,9 @@ impl FileContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
workspace: &Entity<Workspace>,
cx: &mut Context<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -107,7 +124,7 @@ impl FileContextPickerDelegate {
let file_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.files(true, 0).map(move |entry| PathMatch {
worktree.files(false, 0).map(move |entry| PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
@@ -165,22 +182,32 @@ impl PickerDelegate for FileContextPickerDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Search files…".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
// TODO: This should be probably be run in the background.
let paths = search_task.await;
@@ -191,16 +218,108 @@ impl PickerDelegate for FileContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let Some(file_name) = mat
.path
.file_name()
.map(|os_str| os_str.to_string_lossy().into_owned())
else {
return;
};
let full_path = mat.path.display().to_string();
let project_path = ProjectPath {
worktree_id: WorktreeId::from_usize(mat.worktree_id),
path: mat.path.clone(),
};
let Some(editor) = self.editor.upgrade() else {
return;
};
editor.update(cx, |editor, cx| {
editor.transact(window, cx, |editor, window, cx| {
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
{
let mut selections = editor.selections.all::<MultiBufferPoint>(cx);
for selection in selections.iter_mut() {
if selection.is_empty() {
let old_head = selection.head();
let new_head = MultiBufferPoint::new(
old_head.row,
old_head.column.saturating_sub(1),
);
selection.set_head(new_head, SelectionGoal::None);
}
}
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select(selections)
});
}
let start_anchors = {
let snapshot = editor.buffer().read(cx).snapshot(cx);
editor
.selections
.all::<Point>(cx)
.into_iter()
.map(|selection| snapshot.anchor_before(selection.start))
.collect::<Vec<_>>()
};
editor.insert(&full_path, window, cx);
let end_anchors = {
let snapshot = editor.buffer().read(cx).snapshot(cx);
editor
.selections
.all::<Point>(cx)
.into_iter()
.map(|selection| snapshot.anchor_after(selection.end))
.collect::<Vec<_>>()
};
editor.insert("\n", window, cx); // Needed to end the fold
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(IconName::File, file_name.into()),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
let buffer = editor.buffer().read(cx).snapshot(cx);
let mut rows_to_fold = BTreeSet::new();
let crease_iter = start_anchors
.into_iter()
.zip(end_anchors)
.map(|(start, end)| {
rows_to_fold.insert(MultiBufferRow(start.to_point(&buffer).row));
Crease::inline(
start..end,
placeholder.clone(),
fold_toggle("tool-use"),
render_trailer,
)
});
editor.insert_creases(crease_iter, cx);
for buffer_row in rows_to_fold {
editor.fold_at(&FoldAt { buffer_row }, window, cx);
}
});
});
let Some(task) = self
.context_store
.update(cx, |context_store, cx| {
@@ -212,19 +331,19 @@ impl PickerDelegate for FileContextPickerDelegate {
};
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
match task.await.notify_async_err(&mut cx) {
None => anyhow::Ok(()),
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}),
}
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.context_picker
.update(cx, |_, cx| {
cx.emit(DismissEvent);
@@ -236,7 +355,8 @@ impl PickerDelegate for FileContextPickerDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
@@ -259,8 +379,8 @@ pub fn render_file_context_entry(
id: ElementId,
path: &Path,
path_prefix: &Arc<str>,
context_store: WeakModel<ContextStore>,
cx: &WindowContext,
context_store: WeakEntity<ContextStore>,
cx: &App,
) -> Stateful<Div> {
let (file_name, directory) = if path == Path::new("") {
(SharedString::from(path_prefix.clone()), None)
@@ -292,12 +412,12 @@ pub fn render_file_context_entry(
h_flex()
.id(id)
.gap_1()
.gap_1p5()
.w_full()
.child(file_icon.size(IconSize::Small))
.child(file_icon.size(IconSize::Small).color(Color::Muted))
.child(
h_flex()
.gap_2()
.gap_1()
.child(Label::new(file_name))
.children(directory.map(|directory| {
Label::new(directory)
@@ -305,11 +425,12 @@ pub fn render_file_context_entry(
.color(Color::Muted)
})),
)
.child(div().w_full())
.when_some(added, |el, added| match added {
FileInclusion::Direct(_) => el.child(
h_flex()
.gap_1()
.w_full()
.justify_end()
.gap_0p5()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
@@ -322,7 +443,9 @@ pub fn render_file_context_entry(
el.child(
h_flex()
.gap_1()
.w_full()
.justify_end()
.gap_0p5()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
@@ -330,7 +453,38 @@ pub fn render_file_context_entry(
)
.child(Label::new("Included").size(LabelSize::Small)),
)
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
.tooltip(Tooltip::text(format!("in {dir_name}")))
}
})
}
fn render_fold_icon_button(
icon: IconName,
label: SharedString,
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
Arc::new(move |fold_id, _fold_range, _window, _cx| {
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(icon))
.child(Label::new(label.clone()).single_line())
.into_any_element()
})
}
fn fold_toggle(
name: &'static str,
) -> impl Fn(
MultiBufferRow,
bool,
Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
&mut Window,
&mut App,
) -> AnyElement {
move |row, is_folded, fold, _window, _cx| {
Disclosure::new((name, row.0 as u64), !is_folded)
.toggle_state(is_folded)
.on_click(move |_e, window, cx| fold(!is_folded, window, cx))
.into_any_element()
}
}

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem};
@@ -11,16 +11,17 @@ use crate::thread::ThreadId;
use crate::thread_store::ThreadStore;
pub struct ThreadContextPicker {
picker: View<Picker<ThreadContextPickerDelegate>>,
picker: Entity<Picker<ThreadContextPickerDelegate>>,
}
impl ThreadContextPicker {
pub fn new(
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let delegate = ThreadContextPickerDelegate::new(
thread_store,
@@ -28,20 +29,20 @@ impl ThreadContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
ThreadContextPicker { picker }
}
}
impl FocusableView for ThreadContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
impl Focusable for ThreadContextPicker {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ThreadContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -53,9 +54,9 @@ pub struct ThreadContextEntry {
}
pub struct ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>,
selected_index: usize,
@@ -63,9 +64,9 @@ pub struct ThreadContextPickerDelegate {
impl ThreadContextPickerDelegate {
pub fn new(
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
thread_store: WeakEntity<ThreadStore>,
context_picker: WeakEntity<ContextPicker>,
context_store: WeakEntity<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
ThreadContextPickerDelegate {
@@ -90,22 +91,31 @@ impl PickerDelegate for ThreadContextPickerDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Search threads…".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Ok(threads) = self.thread_store.update(cx, |this, cx| {
this.threads(cx)
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let Ok(threads) = self.thread_store.update(cx, |this, _cx| {
this.threads()
.into_iter()
.map(|thread| {
let id = thread.read(cx).id().clone();
let summary = thread.read(cx).summary_or_default();
ThreadContextEntry { id, summary }
.map(|thread| ThreadContextEntry {
id: thread.id,
summary: thread.summary,
})
.collect::<Vec<_>>()
}) else {
@@ -139,7 +149,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
});
cx.spawn(|this, mut cx| async move {
cx.spawn_in(window, |this, mut cx| async move {
let matches = search_task.await;
this.update(&mut cx, |this, cx| {
this.delegate.matches = matches;
@@ -150,7 +160,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
@@ -159,22 +169,26 @@ impl PickerDelegate for ThreadContextPickerDelegate {
return;
};
let Some(thread) = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx))
else {
return;
};
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
self.context_store
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
.ok();
cx.spawn_in(window, |this, mut cx| async move {
let thread = open_thread_task.await?;
this.update_in(&mut cx, |this, window, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
.ok();
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(cx),
}
match this.delegate.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}
})
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
self.context_picker
.update(cx, |_, cx| {
cx.emit(DismissEvent);
@@ -186,7 +200,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let thread = &self.matches[ix];
@@ -198,17 +213,21 @@ impl PickerDelegate for ThreadContextPickerDelegate {
pub fn render_thread_context_entry(
thread: &ThreadContextEntry,
context_store: WeakModel<ContextStore>,
cx: &mut WindowContext,
context_store: WeakEntity<ContextStore>,
cx: &mut App,
) -> Div {
let added = context_store.upgrade().map_or(false, |ctx_store| {
ctx_store.read(cx).includes_thread(&thread.id).is_some()
});
h_flex()
.gap_1()
.gap_1p5()
.w_full()
.child(Icon::new(IconName::MessageCircle).size(IconSize::Small))
.child(
Icon::new(IconName::MessageCircle)
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child(Label::new(thread.summary.clone()))
.child(div().w_full())
.when(added, |el| {

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -4,8 +4,8 @@ use editor::actions::MoveUp;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs;
use gpui::{
pulsating_between, Animation, AnimationExt, AppContext, DismissEvent, FocusableView, Model,
Subscription, TextStyle, View, WeakModel, WeakView,
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
TextStyle, WeakEntity,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
@@ -13,7 +13,9 @@ use rope::Point;
use settings::Settings;
use std::time::Duration;
use theme::ThemeSettings;
use ui::{prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor};
use ui::{
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
};
use workspace::Workspace;
use crate::assistant_model_selector::AssistantModelSelector;
@@ -25,14 +27,14 @@ use crate::thread_store::ThreadStore;
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor {
thread: Model<Thread>,
editor: View<Editor>,
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
thread: Entity<Thread>,
editor: Entity<Editor>,
context_store: Entity<ContextStore>,
context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: View<ContextPicker>,
inline_context_picker: Entity<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
@@ -41,52 +43,58 @@ pub struct MessageEditor {
impl MessageEditor {
pub fn new(
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
thread_store: WeakModel<ThreadStore>,
thread: Model<Thread>,
cx: &mut ViewContext<Self>,
workspace: WeakEntity<Workspace>,
thread_store: WeakEntity<ThreadStore>,
thread: Entity<Thread>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(10, cx);
let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx);
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
editor.set_show_indent_guides(false, cx);
editor
});
let inline_context_picker = cx.new_view(|cx| {
let inline_context_picker = cx.new(|cx| {
ContextPicker::new(
workspace.clone(),
Some(thread_store.clone()),
context_store.downgrade(),
editor.downgrade(),
ConfirmBehavior::Close,
window,
cx,
)
});
let context_strip = cx.new_view(|cx| {
let context_strip = cx.new(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
editor.downgrade(),
Some(thread_store.clone()),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
window,
cx,
)
});
let subscriptions = vec![
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(
&inline_context_picker,
window,
Self::handle_inline_context_picker_event,
),
cx.subscribe(&context_strip, Self::handle_context_strip_event),
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
];
Self {
@@ -97,11 +105,12 @@ impl MessageEditor {
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
model_selector: cx.new(|cx| {
AssistantModelSelector::new(
fs,
model_selector_menu_handle.clone(),
editor.focus_handle(cx),
window,
cx,
)
}),
@@ -111,29 +120,59 @@ impl MessageEditor {
}
}
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx)
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx)
}
fn toggle_chat_mode(&mut self, _: &ChatMode, cx: &mut ViewContext<Self>) {
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
self.use_tools = !self.use_tools;
cx.notify();
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
fn toggle_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
}
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
pub fn remove_all_context(
&mut self,
_: &RemoveAllContext,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_store.update(cx, |store, _cx| store.clear());
cx.notify();
}
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx);
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
self.send_to_model(RequestKind::Chat, window, cx);
}
fn send_to_model(&mut self, request_kind: RequestKind, cx: &mut ViewContext<Self>) {
fn is_editor_empty(&self, cx: &App) -> bool {
self.editor.read(cx).text(cx).is_empty()
}
fn is_model_selected(&self, cx: &App) -> bool {
LanguageModelRegistry::read_global(cx)
.active_model()
.is_some()
}
fn send_to_model(
&mut self,
request_kind: RequestKind,
window: &mut Window,
cx: &mut Context<Self>,
) {
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
.as_ref()
@@ -150,7 +189,7 @@ impl MessageEditor {
let user_message = self.editor.update(cx, |editor, cx| {
let text = editor.text(cx);
editor.clear(cx);
editor.clear(window, cx);
text
});
@@ -189,9 +228,10 @@ impl MessageEditor {
fn handle_editor_event(
&mut self,
editor: View<Editor>,
editor: &Entity<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
match event {
EditorEvent::SelectionsChanged { .. } => {
@@ -202,7 +242,7 @@ impl MessageEditor {
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
if char_behind_cursor == Some('@') {
self.inline_context_picker_menu_handle.show(cx);
self.inline_context_picker_menu_handle.show(window, cx);
}
}
});
@@ -213,55 +253,66 @@ impl MessageEditor {
fn handle_inline_context_picker_event(
&mut self,
_inline_context_picker: View<ContextPicker>,
_inline_context_picker: &Entity<ContextPicker>,
_event: &DismissEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
window.focus(&editor_focus_handle);
}
fn handle_context_strip_event(
&mut self,
_context_strip: View<ContextStrip>,
_context_strip: &Entity<ContextStrip>,
event: &ContextStripEvent,
cx: &mut ViewContext<Self>,
window: &mut Window,
cx: &mut Context<Self>,
) {
match event {
ContextStripEvent::PickerDismissed
| ContextStripEvent::BlurredEmpty
| ContextStripEvent::BlurredDown => {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
window.focus(&editor_focus_handle);
}
ContextStripEvent::BlurredUp => {}
}
}
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if self.context_picker_menu_handle.is_deployed() {
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
if self.context_picker_menu_handle.is_deployed()
|| self.inline_context_picker_menu_handle.is_deployed()
{
cx.propagate();
} else {
cx.focus_view(&self.context_strip);
self.context_strip.focus_handle(cx).focus(window);
}
}
}
impl FocusableView for MessageEditor {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
impl Focusable for MessageEditor {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl Render for MessageEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
let focus_handle = self.editor.focus_handle(cx);
let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background;
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 {
Color::Muted
} else {
Color::Default
};
v_flex()
.key_context("MessageEditor")
@@ -303,9 +354,9 @@ impl Render for MessageEditor {
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |cx| {
.menu(move |window, cx| {
inline_context_picker.update(cx, |this, cx| {
this.init(cx);
this.init(window, cx);
});
Some(inline_context_picker.clone())
@@ -328,7 +379,7 @@ impl Render for MessageEditor {
.child(
Switch::new("use-tools", self.use_tools.into())
.label("Tools")
.on_click(cx.listener(|this, selection, _cx| {
.on_click(cx.listener(|this, selection, _window, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected
@@ -338,7 +389,7 @@ impl Render for MessageEditor {
.key_binding(KeyBinding::for_action_in(
&ChatMode,
&focus_handle,
cx,
window,
)),
)
.child(h_flex().gap_1().child(self.model_selector.clone()).child(
@@ -367,35 +418,52 @@ impl Render for MessageEditor {
KeyBinding::for_action_in(
&editor::actions::Cancel,
&focus_handle,
cx,
window,
)
.map(|binding| binding.into_any_element()),
),
)
.on_click(move |_event, cx| {
focus_handle
.dispatch_action(&editor::actions::Cancel, cx);
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(
&editor::actions::Cancel,
window,
cx,
);
})
} else {
ButtonLike::new("submit-message")
.width(button_width.into())
.style(ButtonStyle::Filled)
.disabled(is_editor_empty || !is_model_selected)
.child(
h_flex()
.w_full()
.justify_between()
.child(Label::new("Submit").size(LabelSize::Small))
.child(
Label::new("Submit")
.size(LabelSize::Small)
.color(submit_label_color),
)
.children(
KeyBinding::for_action_in(
&Chat,
&focus_handle,
cx,
window,
)
.map(|binding| binding.into_any_element()),
),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, window, cx);
})
.when(is_editor_empty, |button| {
button
.tooltip(Tooltip::text("Type a message to submit"))
})
.when(!is_model_selected, |button| {
button.tooltip(Tooltip::text(
"Select a model to continue",
))
})
},
)),

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet};
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
use gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
@@ -18,6 +18,7 @@ use util::{post_inc, TryFutureExt as _};
use uuid::Uuid;
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
use crate::thread_store::SavedThread;
#[derive(Debug, Clone, Copy)]
pub enum RequestKind {
@@ -75,7 +76,7 @@ pub struct Thread {
}
impl Thread {
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut Context<Self>) -> Self {
Self {
id: ThreadId::new(),
updated_at: Utc::now(),
@@ -94,6 +95,40 @@ impl Thread {
}
}
pub fn from_saved(
id: ThreadId,
saved: SavedThread,
tools: Arc<ToolWorkingSet>,
_cx: &mut Context<Self>,
) -> Self {
let next_message_id = MessageId(saved.messages.len());
Self {
id,
updated_at: saved.updated_at,
summary: Some(saved.summary),
pending_summary: Task::ready(None),
messages: saved
.messages
.into_iter()
.map(|message| Message {
id: message.id,
role: message.role,
text: message.text,
})
.collect(),
next_message_id,
context: BTreeMap::default(),
context_by_message: HashMap::default(),
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(),
}
}
pub fn id(&self) -> &ThreadId {
&self.id
}
@@ -119,7 +154,7 @@ impl Thread {
self.summary.clone().unwrap_or(DEFAULT)
}
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut ModelContext<Self>) {
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) {
self.summary = Some(summary.into());
cx.emit(ThreadEvent::SummaryChanged);
}
@@ -159,7 +194,7 @@ impl Thread {
&mut self,
text: impl Into<String>,
context: Vec<ContextSnapshot>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let message_id = self.insert_message(Role::User, text, cx);
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
@@ -172,7 +207,7 @@ impl Thread {
&mut self,
role: Role,
text: impl Into<String>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> MessageId {
let id = self.next_message_id.post_inc();
self.messages.push(Message {
@@ -209,7 +244,7 @@ impl Thread {
pub fn to_completion_request(
&self,
_request_kind: RequestKind,
_cx: &AppContext,
_cx: &App,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
messages: vec![],
@@ -279,7 +314,7 @@ impl Thread {
&mut self,
request: LanguageModelRequest,
model: Arc<dyn LanguageModel>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let pending_completion_id = post_inc(&mut self.completion_count);
@@ -308,6 +343,13 @@ impl Thread {
last_message.id,
chunk,
));
} else {
// If we won't have an Assistant message yet, assume this chunk marks the beginning
// of a new Assistant response.
//
// Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
// will result in duplicating the text of the chunk in the rendered Markdown.
thread.insert_message(Role::Assistant, chunk, cx);
}
}
}
@@ -397,7 +439,7 @@ impl Thread {
});
}
pub fn summarize(&mut self, cx: &mut ModelContext<Self>) {
pub fn summarize(&mut self, cx: &mut Context<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return;
};
@@ -455,7 +497,7 @@ impl Thread {
assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let insert_output_task = cx.spawn(|thread, mut cx| {
let tool_use_id = tool_use_id.clone();

View File

@@ -1,27 +1,27 @@
use gpui::{
uniform_list, AppContext, FocusHandle, FocusableView, Model, ScrollStrategy,
UniformListScrollHandle, WeakView,
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
WeakEntity,
};
use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
use crate::thread::Thread;
use crate::thread_store::ThreadStore;
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
use crate::{AssistantPanel, RemoveSelectedThread};
pub struct ThreadHistory {
focus_handle: FocusHandle,
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
assistant_panel: WeakEntity<AssistantPanel>,
thread_store: Entity<ThreadStore>,
scroll_handle: UniformListScrollHandle,
selected_index: usize,
}
impl ThreadHistory {
pub(crate) fn new(
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
cx: &mut ViewContext<Self>,
assistant_panel: WeakEntity<AssistantPanel>,
thread_store: Entity<ThreadStore>,
cx: &mut Context<Self>,
) -> Self {
Self {
focus_handle: cx.focus_handle(),
@@ -32,74 +32,83 @@ impl ThreadHistory {
}
}
pub fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).non_empty_len(cx);
pub fn select_prev(
&mut self,
_: &menu::SelectPrev,
window: &mut Window,
cx: &mut Context<Self>,
) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
if self.selected_index == 0 {
self.set_selected_index(count - 1, cx);
self.set_selected_index(count - 1, window, cx);
} else {
self.set_selected_index(self.selected_index - 1, cx);
self.set_selected_index(self.selected_index - 1, window, cx);
}
}
}
pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).non_empty_len(cx);
pub fn select_next(
&mut self,
_: &menu::SelectNext,
window: &mut Window,
cx: &mut Context<Self>,
) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
if self.selected_index == count - 1 {
self.set_selected_index(0, cx);
self.set_selected_index(0, window, cx);
} else {
self.set_selected_index(self.selected_index + 1, cx);
self.set_selected_index(self.selected_index + 1, window, cx);
}
}
}
fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).non_empty_len(cx);
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
self.set_selected_index(0, cx);
self.set_selected_index(0, window, cx);
}
}
fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
let count = self.thread_store.read(cx).non_empty_len(cx);
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
let count = self.thread_store.read(cx).thread_count();
if count > 0 {
self.set_selected_index(count - 1, cx);
self.set_selected_index(count - 1, window, cx);
}
}
fn set_selected_index(&mut self, index: usize, cx: &mut ViewContext<Self>) {
fn set_selected_index(&mut self, index: usize, _window: &mut Window, cx: &mut Context<Self>) {
self.selected_index = index;
self.scroll_handle
.scroll_to_item(index, ScrollStrategy::Top);
cx.notify();
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(thread) = threads.get(self.selected_index) {
self.assistant_panel
.update(cx, move |this, cx| {
let thread_id = thread.read(cx).id().clone();
this.open_thread(&thread_id, cx)
})
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
.ok();
cx.notify();
}
}
fn remove_selected_thread(&mut self, _: &RemoveSelectedThread, cx: &mut ViewContext<Self>) {
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
fn remove_selected_thread(
&mut self,
_: &RemoveSelectedThread,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(thread) = threads.get(self.selected_index) {
self.assistant_panel
.update(cx, |this, cx| {
let thread_id = thread.read(cx).id().clone();
this.delete_thread(&thread_id, cx);
this.delete_thread(&thread.id, cx);
})
.ok();
@@ -108,15 +117,15 @@ impl ThreadHistory {
}
}
impl FocusableView for ThreadHistory {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
impl Focusable for ThreadHistory {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ThreadHistory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
let selected_index = self.selected_index;
v_flex()
@@ -145,10 +154,10 @@ impl Render for ThreadHistory {
} else {
history.child(
uniform_list(
cx.view().clone(),
cx.entity().clone(),
"thread-history",
threads.len(),
move |history, range, _cx| {
move |history, range, _window, _cx| {
threads[range]
.iter()
.enumerate()
@@ -172,15 +181,15 @@ impl Render for ThreadHistory {
#[derive(IntoElement)]
pub struct PastThread {
thread: Model<Thread>,
assistant_panel: WeakView<AssistantPanel>,
thread: SavedThreadMetadata,
assistant_panel: WeakEntity<AssistantPanel>,
selected: bool,
}
impl PastThread {
pub fn new(
thread: Model<Thread>,
assistant_panel: WeakView<AssistantPanel>,
thread: SavedThreadMetadata,
assistant_panel: WeakEntity<AssistantPanel>,
selected: bool,
) -> Self {
Self {
@@ -192,15 +201,11 @@ impl PastThread {
}
impl RenderOnce for PastThread {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let (id, summary) = {
let thread = self.thread.read(cx);
(thread.id().clone(), thread.summary_or_default())
};
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let summary = self.thread.summary;
let thread_timestamp = time_format::format_localized_timestamp(
OffsetDateTime::from_unix_timestamp(self.thread.read(cx).updated_at().timestamp())
.unwrap(),
OffsetDateTime::from_unix_timestamp(self.thread.updated_at.timestamp()).unwrap(),
OffsetDateTime::now_utc(),
self.assistant_panel
.update(cx, |this, _cx| this.local_timezone())
@@ -208,7 +213,7 @@ impl RenderOnce for PastThread {
time_format::TimestampFormat::EnhancedAbsolute,
);
ListItem::new(("past-thread", self.thread.entity_id()))
ListItem::new(SharedString::from(self.thread.id.to_string()))
.outlined()
.toggle_state(self.selected)
.start_slot(
@@ -220,21 +225,21 @@ impl RenderOnce for PastThread {
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
.end_slot(
h_flex()
.gap_2()
.gap_1p5()
.child(
Label::new(thread_timestamp)
.color(Color::Disabled)
.size(LabelSize::Small),
.color(Color::Muted)
.size(LabelSize::XSmall),
)
.child(
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Delete Thread", cx))
.icon_size(IconSize::XSmall)
.tooltip(Tooltip::text("Delete Thread"))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = id.clone();
move |_event, cx| {
let id = self.thread.id.clone();
move |_event, _window, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_thread(&id, cx);
@@ -246,11 +251,11 @@ impl RenderOnce for PastThread {
)
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = id.clone();
move |_event, cx| {
let id = self.thread.id.clone();
move |_event, window, cx| {
assistant_panel
.update(cx, |this, cx| {
this.open_thread(&id, cx);
this.open_thread(&id, window, cx).detach_and_log_err(cx);
})
.ok();
}

View File

@@ -1,98 +1,171 @@
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use anyhow::{anyhow, Result};
use assistant_tool::{ToolId, ToolWorkingSet};
use chrono::{DateTime, Utc};
use collections::HashMap;
use context_server::manager::ContextServerManager;
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use gpui::{prelude::*, AppContext, Model, ModelContext, Task};
use futures::future::{self, BoxFuture, Shared};
use futures::FutureExt as _;
use gpui::{prelude::*, App, BackgroundExecutor, Context, Entity, SharedString, Task};
use heed::types::SerdeBincode;
use heed::Database;
use language_model::Role;
use project::Project;
use unindent::Unindent;
use serde::{Deserialize, Serialize};
use util::ResultExt as _;
use crate::thread::{Thread, ThreadId};
use crate::thread::{MessageId, Thread, ThreadId};
pub struct ThreadStore {
#[allow(unused)]
project: Model<Project>,
project: Entity<Project>,
tools: Arc<ToolWorkingSet>,
context_server_manager: Model<ContextServerManager>,
context_server_manager: Entity<ContextServerManager>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
threads: Vec<Model<Thread>>,
threads: Vec<SavedThreadMetadata>,
database_future: Shared<BoxFuture<'static, Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>,
}
impl ThreadStore {
pub fn new(
project: Model<Project>,
project: Entity<Project>,
tools: Arc<ToolWorkingSet>,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new_model(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
cx: &mut App,
) -> Result<Entity<Self>> {
let this = cx.new(|cx| {
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
let mut this = Self {
project,
tools,
context_server_manager,
context_server_tool_ids: HashMap::default(),
threads: Vec::new(),
};
this.mock_recent_threads(cx);
this.register_context_server_handlers(cx);
let executor = cx.background_executor().clone();
let database_future = executor
.spawn({
let executor = executor.clone();
let database_path = paths::support_dir().join("threads/threads-db.0.mdb");
async move { ThreadsDatabase::new(database_path, executor) }
})
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
.shared();
this
})?;
let this = Self {
project,
tools,
context_server_manager,
context_server_tool_ids: HashMap::default(),
threads: Vec::new(),
database_future,
};
this.register_context_server_handlers(cx);
this.reload(cx).detach_and_log_err(cx);
Ok(this)
})
this
});
Ok(this)
}
/// Returns the number of non-empty threads.
pub fn non_empty_len(&self, cx: &AppContext) -> usize {
self.threads
.iter()
.filter(|thread| !thread.read(cx).is_empty())
.count()
/// Returns the number of threads.
pub fn thread_count(&self) -> usize {
self.threads.len()
}
pub fn threads(&self, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
let mut threads = self
.threads
.iter()
.filter(|thread| !thread.read(cx).is_empty())
.cloned()
.collect::<Vec<_>>();
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.read(cx).updated_at()));
pub fn threads(&self) -> Vec<SavedThreadMetadata> {
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
threads
}
pub fn recent_threads(&self, limit: usize, cx: &ModelContext<Self>) -> Vec<Model<Thread>> {
self.threads(cx).into_iter().take(limit).collect()
pub fn recent_threads(&self, limit: usize) -> Vec<SavedThreadMetadata> {
self.threads().into_iter().take(limit).collect()
}
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> {
let thread = cx.new_model(|cx| Thread::new(self.tools.clone(), cx));
self.threads.push(thread.clone());
thread
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
cx.new(|cx| Thread::new(self.tools.clone(), cx))
}
pub fn open_thread(&self, id: &ThreadId, cx: &mut ModelContext<Self>) -> Option<Model<Thread>> {
self.threads
.iter()
.find(|thread| thread.read(cx).id() == id)
.cloned()
pub fn open_thread(
&self,
id: &ThreadId,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Thread>>> {
let id = id.clone();
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
let database = database_future.await.map_err(|err| anyhow!(err))?;
let thread = database
.try_find_thread(id.clone())
.await?
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
this.update(&mut cx, |this, cx| {
cx.new(|cx| Thread::from_saved(id.clone(), thread, this.tools.clone(), cx))
})
})
}
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut ModelContext<Self>) {
self.threads.retain(|thread| thread.read(cx).id() != id);
pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
let (metadata, thread) = thread.update(cx, |thread, _cx| {
let id = thread.id().clone();
let thread = SavedThread {
summary: thread.summary_or_default(),
updated_at: thread.updated_at(),
messages: thread
.messages()
.map(|message| SavedMessage {
id: message.id,
role: message.role,
text: message.text.clone(),
})
.collect(),
};
(id, thread)
});
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.save_thread(metadata, thread).await?;
this.update(&mut cx, |this, cx| this.reload(cx))?.await
})
}
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
let id = id.clone();
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.delete_thread(id.clone()).await?;
this.update(&mut cx, |this, _cx| {
this.threads.retain(|thread| thread.id != id)
})
})
}
fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move {
let threads = database_future
.await
.map_err(|err| anyhow!(err))?
.list_threads()
.await?;
this.update(&mut cx, |this, cx| {
this.threads = threads;
cx.notify();
})
})
}
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
cx.subscribe(
&self.context_server_manager.clone(),
Self::handle_context_server_event,
@@ -102,9 +175,9 @@ impl ThreadStore {
fn handle_context_server_event(
&mut self,
context_server_manager: Model<ContextServerManager>,
context_server_manager: Entity<ContextServerManager>,
event: &context_server::manager::Event,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let tool_working_set = self.tools.clone();
match event {
@@ -159,133 +232,108 @@ impl ThreadStore {
}
}
impl ThreadStore {
/// Creates some mocked recent threads for testing purposes.
fn mock_recent_threads(&mut self, cx: &mut ModelContext<Self>) {
use language_model::Role;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedThreadMetadata {
pub id: ThreadId,
pub summary: SharedString,
pub updated_at: DateTime<Utc>,
}
self.threads.push(cx.new_model(|cx| {
let mut thread = Thread::new(self.tools.clone(), cx);
thread.set_summary("Introduction to quantum computing", cx);
thread.insert_user_message("Hello! Can you help me understand quantum computing?", Vec::new(), cx);
thread.insert_message(Role::Assistant, "Of course! I'd be happy to help you understand quantum computing. Quantum computing is a fascinating field that uses the principles of quantum mechanics to process information. Unlike classical computers that use bits (0s and 1s), quantum computers use quantum bits or 'qubits'. These qubits can exist in multiple states simultaneously, a property called superposition. This allows quantum computers to perform certain calculations much faster than classical computers. What specific aspect of quantum computing would you like to know more about?", cx);
thread.insert_user_message("That's interesting! Can you explain how quantum entanglement is used in quantum computing?", Vec::new(), cx);
thread.insert_message(Role::Assistant, "Certainly! Quantum entanglement is a key principle used in quantum computing. When two qubits become entangled, the state of one qubit is directly related to the state of the other, regardless of the distance between them. This property is used in quantum computing to create complex quantum states and to perform operations on multiple qubits simultaneously. Entanglement allows quantum computers to process information in ways that classical computers cannot, potentially solving certain problems much more efficiently. For example, it's crucial in quantum error correction and in algorithms like quantum teleportation, which is important for quantum communication.", cx);
thread
}));
#[derive(Serialize, Deserialize)]
pub struct SavedThread {
pub summary: SharedString,
pub updated_at: DateTime<Utc>,
pub messages: Vec<SavedMessage>,
}
self.threads.push(cx.new_model(|cx| {
let mut thread = Thread::new(self.tools.clone(), cx);
thread.set_summary("Rust web development and async programming", cx);
thread.insert_user_message("Can you show me an example of Rust code for a simple web server?", Vec::new(), cx);
thread.insert_message(Role::Assistant, "Certainly! Here's an example of a simple web server in Rust using the `actix-web` framework:
#[derive(Serialize, Deserialize)]
pub struct SavedMessage {
pub id: MessageId,
pub role: Role,
pub text: String,
}
```rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
struct ThreadsDatabase {
executor: BackgroundExecutor,
env: heed::Env,
threads: Database<SerdeBincode<ThreadId>, SerdeBincode<SavedThread>>,
}
async fn hello() -> impl Responder {
HttpResponse::Ok().body(\"Hello, World!\")
}
impl ThreadsDatabase {
pub fn new(path: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
std::fs::create_dir_all(&path)?;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route(\"/\", web::get().to(hello))
})
.bind(\"127.0.0.1:8080\")?
.run()
.await
}
```
const ONE_GB_IN_BYTES: usize = 1024 * 1024 * 1024;
let env = unsafe {
heed::EnvOpenOptions::new()
.map_size(ONE_GB_IN_BYTES)
.max_dbs(1)
.open(path)?
};
This code creates a basic web server that responds with 'Hello, World!' when you access the root URL. Here's a breakdown of what's happening:
let mut txn = env.write_txn()?;
let threads = env.create_database(&mut txn, Some("threads"))?;
txn.commit()?;
1. We import necessary items from the `actix-web` crate.
2. We define an async `hello` function that returns a simple HTTP response.
3. In the `main` function, we set up the server to listen on `127.0.0.1:8080`.
4. We configure the app to respond to GET requests on the root path with our `hello` function.
Ok(Self {
executor,
env,
threads,
})
}
To run this, you'd need to add `actix-web` to your `Cargo.toml` dependencies:
pub fn list_threads(&self) -> Task<Result<Vec<SavedThreadMetadata>>> {
let env = self.env.clone();
let threads = self.threads;
```toml
[dependencies]
actix-web = \"4.0\"
```
Then you can run the server with `cargo run` and access it at `http://localhost:8080`.".unindent(), cx);
thread.insert_user_message("That's great! Can you explain more about async functions in Rust?", Vec::new(), cx);
thread.insert_message(Role::Assistant, "Certainly! Async functions are a key feature in Rust for writing efficient, non-blocking code, especially for I/O-bound operations. Here's an overview:
1. **Syntax**: Async functions are declared using the `async` keyword:
```rust
async fn my_async_function() -> Result<(), Error> {
// Asynchronous code here
}
```
2. **Futures**: Async functions return a `Future`. A `Future` represents a value that may not be available yet but will be at some point.
3. **Await**: Inside an async function, you can use the `.await` syntax to wait for other async operations to complete:
```rust
async fn fetch_data() -> Result<String, Error> {
let response = make_http_request().await?;
let data = process_response(response).await?;
Ok(data)
}
```
4. **Non-blocking**: Async functions allow the runtime to work on other tasks while waiting for I/O or other operations to complete, making efficient use of system resources.
5. **Runtime**: To execute async code, you need a runtime like `tokio` or `async-std`. Actix-web, which we used in the previous example, includes its own runtime.
6. **Error Handling**: Async functions work well with Rust's `?` operator for error handling.
Async programming in Rust provides a powerful way to write concurrent code that's both safe and efficient. It's particularly useful for servers, network programming, and any application that deals with many concurrent operations.".unindent(), cx);
thread
}));
self.threads.push(cx.new_model(|cx| {
let mut thread = Thread::new(self.tools.clone(), cx);
thread.set_summary("Rust code with long lines", cx);
thread.insert_user_message("Could you write me some Rust code with long lines?", Vec::new(), cx);
thread.insert_message(Role::Assistant, r#"Here's some Rust code with some intentionally long lines:
```rust
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let very_long_vector = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
let complicated_hashmap: HashMap<String, Vec<(i32, f64, String)>> = [("key1".to_string(), vec![(1, 1.1, "value1".to_string()), (2, 2.2, "value2".to_string())]), ("key2".to_string(), vec![(3, 3.3, "value3".to_string()), (4, 4.4, "value4".to_string())])].iter().cloned().collect();
let nested_structure = Arc::new(Mutex::new(HashMap::new()));
let long_closure = |x: i32, y: i32, z: i32| -> i32 { let result = x * y + z; println!("The result of the long closure calculation is: {}", result); result };
let thread_handles: Vec<_> = (0..10).map(|i| {
let nested_structure_clone = Arc::clone(&nested_structure);
thread::spawn(move || {
let mut lock = nested_structure_clone.lock().unwrap();
lock.entry(format!("thread_{}", i)).or_insert_with(|| HashSet::new()).insert(i * i);
})
}).collect();
for handle in thread_handles {
handle.join().unwrap();
}
println!("The final state of the nested structure is: {:?}", nested_structure.lock().unwrap());
let complex_expression = very_long_vector.iter().filter(|&&x| x % 2 == 0).map(|&x| x * x).fold(0, |acc, x| acc + x) + long_closure(5, 10, 15);
println!("The result of the complex expression is: {}", complex_expression);
self.executor.spawn(async move {
let txn = env.read_txn()?;
let mut iter = threads.iter(&txn)?;
let mut threads = Vec::new();
while let Some((key, value)) = iter.next().transpose()? {
threads.push(SavedThreadMetadata {
id: key,
summary: value.summary,
updated_at: value.updated_at,
});
}
```"#.unindent(), cx);
thread
}));
Ok(threads)
})
}
pub fn try_find_thread(&self, id: ThreadId) -> Task<Result<Option<SavedThread>>> {
let env = self.env.clone();
let threads = self.threads;
self.executor.spawn(async move {
let txn = env.read_txn()?;
let thread = threads.get(&txn, &id)?;
Ok(thread)
})
}
pub fn save_thread(&self, id: ThreadId, thread: SavedThread) -> Task<Result<()>> {
let env = self.env.clone();
let threads = self.threads;
self.executor.spawn(async move {
let mut txn = env.write_txn()?;
threads.put(&mut txn, &id, &thread)?;
txn.commit()?;
Ok(())
})
}
pub fn delete_thread(&self, id: ThreadId) -> Task<Result<()>> {
let env = self.env.clone();
let threads = self.threads;
self.executor.spawn(async move {
let mut txn = env.write_txn()?;
threads.delete(&mut txn, &id)?;
txn.commit()?;
Ok(())
})
}
}

View File

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

View File

@@ -0,0 +1,68 @@
[package]
name = "assistant_context_editor"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant_context_editor.rs"
[dependencies]
anyhow.workspace = true
assistant_settings.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
context_server.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
indexed_docs.workspace = true
language.workspace = true
language_model.workspace = true
language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true
multi_buffer.workspace = true
open_ai.workspace = true
parking_lot.workspace = true
paths.workspace = true
picker.workspace = true
project.workspace = true
prompt_library.workspace = true
regex.workspace = true
rope.workspace = true
rpc.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smallvec.workspace = true
smol.workspace = true
strum.workspace = true
telemetry_events.workspace = true
text.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
uuid.workspace = true
workspace.workspace = true
[dev-dependencies]
language_model = { workspace = true, features = ["test-support"] }
languages = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
rand.workspace = true
tree-sitter-md.workspace = true
unindent.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

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

View File

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

View File

@@ -1,14 +1,11 @@
#[cfg(test)]
mod context_tests;
use crate::{
slash_command::SlashCommandLine, AssistantEdit, AssistantPatch, AssistantPatchStatus,
MessageId, MessageStatus,
};
use crate::patch::{AssistantEdit, AssistantPatch, AssistantPatchStatus};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
SlashCommandWorkingSet,
SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
SlashCommandResult, SlashCommandWorkingSet,
};
use assistant_slash_commands::FileCommandMetadata;
use assistant_tool::ToolWorkingSet;
@@ -19,11 +16,9 @@ use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
use fs::{Fs, RemoveOptions};
use futures::{future::Shared, FutureExt, StreamExt};
use gpui::{
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString,
Subscription, Task,
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
Task,
};
use prompt_library::PromptBuilder;
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
@@ -38,6 +33,7 @@ use language_models::{
use open_ai::Model as OpenAiModel;
use paths::contexts_dir;
use project::Project;
use prompt_library::PromptBuilder;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
@@ -52,9 +48,9 @@ use std::{
};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use text::{BufferSnapshot, ToPoint};
use ui::IconName;
use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
use workspace::ui::IconName;
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
@@ -73,6 +69,64 @@ impl ContextId {
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct MessageId(pub clock::Lamport);
impl MessageId {
pub fn as_u64(self) -> u64 {
self.0.as_u64()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum MessageStatus {
Pending,
Done,
Error(SharedString),
Canceled,
}
impl MessageStatus {
pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
match status.variant {
Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
Some(proto::context_message_status::Variant::Error(error)) => {
MessageStatus::Error(error.message.into())
}
Some(proto::context_message_status::Variant::Canceled(_)) => MessageStatus::Canceled,
None => MessageStatus::Pending,
}
}
pub fn to_proto(&self) -> proto::ContextMessageStatus {
match self {
MessageStatus::Pending => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Pending(
proto::context_message_status::Pending {},
)),
},
MessageStatus::Done => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Done(
proto::context_message_status::Done {},
)),
},
MessageStatus::Error(message) => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Error(
proto::context_message_status::Error {
message: message.to_string(),
},
)),
},
MessageStatus::Canceled => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Canceled(
proto::context_message_status::Canceled {},
)),
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestType {
/// Request a normal chat response from the model.
@@ -423,7 +477,7 @@ pub struct MessageCacheMetadata {
pub struct MessageMetadata {
pub role: Role,
pub status: MessageStatus,
pub(crate) timestamp: clock::Lamport,
pub timestamp: clock::Lamport,
#[serde(skip)]
pub cache: Option<MessageCacheMetadata>,
}
@@ -534,18 +588,18 @@ pub enum XmlTagKind {
Operation,
}
pub struct Context {
pub struct AssistantContext {
id: ContextId,
timestamp: clock::Lamport,
version: clock::Global,
pending_ops: Vec<ContextOperation>,
operations: Vec<ContextOperation>,
buffer: Model<Buffer>,
buffer: Entity<Buffer>,
parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription,
pub(crate) slash_commands: Arc<SlashCommandWorkingSet>,
pub(crate) tools: Arc<ToolWorkingSet>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
message_anchors: Vec<MessageAnchor>,
@@ -565,7 +619,7 @@ pub struct Context {
language_registry: Arc<LanguageRegistry>,
patches: Vec<AssistantPatch>,
xml_tags: Vec<XmlTag>,
project: Option<Model<Project>>,
project: Option<Entity<Project>>,
prompt_builder: Arc<PromptBuilder>,
}
@@ -591,17 +645,17 @@ impl ContextAnnotation for XmlTag {
}
}
impl EventEmitter<ContextEvent> for Context {}
impl EventEmitter<ContextEvent> for AssistantContext {}
impl Context {
impl AssistantContext {
pub fn local(
language_registry: Arc<LanguageRegistry>,
project: Option<Model<Project>>,
project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Self {
Self::new(
ContextId::new(),
@@ -626,11 +680,11 @@ impl Context {
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>,
project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Self {
let buffer = cx.new_model(|_cx| {
let buffer = cx.new(|_cx| {
let buffer = Buffer::remote(
language::BufferId::new(1).unwrap(),
replica_id,
@@ -701,7 +755,7 @@ impl Context {
this
}
pub(crate) fn serialize(&self, cx: &AppContext) -> SavedContext {
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
let buffer = self.buffer.read(cx);
SavedContext {
id: Some(self.id.clone()),
@@ -749,9 +803,9 @@ impl Context {
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>,
project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Self {
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let mut this = Self::new(
@@ -783,18 +837,22 @@ impl Context {
self.timestamp.replica_id
}
pub fn version(&self, cx: &AppContext) -> ContextVersion {
pub fn version(&self, cx: &App) -> ContextVersion {
ContextVersion {
context: self.version.clone(),
buffer: self.buffer.read(cx).version(),
}
}
pub fn set_capability(
&mut self,
capability: language::Capability,
cx: &mut ModelContext<Self>,
) {
pub fn slash_commands(&self) -> &Arc<SlashCommandWorkingSet> {
&self.slash_commands
}
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
&self.tools
}
pub fn set_capability(&mut self, capability: language::Capability, cx: &mut Context<Self>) {
self.buffer
.update(cx, |buffer, cx| buffer.set_capability(capability, cx));
}
@@ -808,7 +866,7 @@ impl Context {
pub fn serialize_ops(
&self,
since: &ContextVersion,
cx: &AppContext,
cx: &App,
) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self
.buffer
@@ -843,7 +901,7 @@ impl Context {
pub fn apply_ops(
&mut self,
ops: impl IntoIterator<Item = ContextOperation>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let mut buffer_ops = Vec::new();
for op in ops {
@@ -857,7 +915,7 @@ impl Context {
self.flush_ops(cx);
}
fn flush_ops(&mut self, cx: &mut ModelContext<Context>) {
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
let mut changed_messages = HashSet::default();
let mut summary_changed = false;
@@ -976,7 +1034,7 @@ impl Context {
}
}
fn can_apply_op(&self, op: &ContextOperation, cx: &AppContext) -> bool {
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
if !self.version.observed_all(op.version()) {
return false;
}
@@ -1007,7 +1065,7 @@ impl Context {
fn has_received_operations_for_anchor_range(
&self,
range: Range<text::Anchor>,
cx: &AppContext,
cx: &App,
) -> bool {
let version = &self.buffer.read(cx).version;
let observed_start = range.start == language::Anchor::MIN
@@ -1019,12 +1077,12 @@ impl Context {
observed_start && observed_end
}
fn push_op(&mut self, op: ContextOperation, cx: &mut ModelContext<Self>) {
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
self.operations.push(op.clone());
cx.emit(ContextEvent::Operation(op));
}
pub fn buffer(&self) -> &Model<Buffer> {
pub fn buffer(&self) -> &Entity<Buffer> {
&self.buffer
}
@@ -1032,7 +1090,7 @@ impl Context {
self.language_registry.clone()
}
pub fn project(&self) -> Option<Model<Project>> {
pub fn project(&self) -> Option<Entity<Project>> {
self.project.clone()
}
@@ -1048,11 +1106,7 @@ impl Context {
self.summary.as_ref()
}
pub(crate) fn patch_containing(
&self,
position: Point,
cx: &AppContext,
) -> Option<&AssistantPatch> {
pub fn patch_containing(&self, position: Point, cx: &App) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.patches.binary_search_by(|patch| {
let patch_range = patch.range.to_point(&buffer);
@@ -1075,10 +1129,10 @@ impl Context {
self.patches.iter().map(|patch| patch.range.clone())
}
pub(crate) fn patch_for_range(
pub fn patch_for_range(
&self,
range: &Range<language::Anchor>,
cx: &AppContext,
cx: &App,
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.patch_index_for_range(range, buffer).ok()?;
@@ -1109,7 +1163,7 @@ impl Context {
&self.slash_command_output_sections
}
pub fn contains_files(&self, cx: &AppContext) -> bool {
pub fn contains_files(&self, cx: &App) -> bool {
let buffer = self.buffer.read(cx);
self.slash_command_output_sections.iter().any(|section| {
section.is_valid(buffer)
@@ -1131,7 +1185,7 @@ impl Context {
self.pending_tool_uses_by_id.get(id)
}
fn set_language(&mut self, cx: &mut ModelContext<Self>) {
fn set_language(&mut self, cx: &mut Context<Self>) {
let markdown = self.language_registry.language_for_name("Markdown");
cx.spawn(|this, mut cx| async move {
let markdown = markdown.await?;
@@ -1145,9 +1199,9 @@ impl Context {
fn handle_buffer_event(
&mut self,
_: Model<Buffer>,
_: Entity<Buffer>,
event: &language::BufferEvent,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
match event {
language::BufferEvent::Operation {
@@ -1165,11 +1219,11 @@ impl Context {
}
}
pub(crate) fn token_count(&self) -> Option<usize> {
pub fn token_count(&self) -> Option<usize> {
self.token_count
}
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut Context<Self>) {
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
// because otherwise you see in the UI that your empty message has a bunch of tokens already used.
let request = self.to_completion_request(RequestType::Chat, cx);
@@ -1197,7 +1251,7 @@ impl Context {
&mut self,
cache_configuration: &Option<LanguageModelCacheConfiguration>,
speculative: bool,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> bool {
let cache_configuration =
cache_configuration
@@ -1299,7 +1353,7 @@ impl Context {
new_anchor_needs_caching
}
fn start_cache_warming(&mut self, model: &Arc<dyn LanguageModel>, cx: &mut ModelContext<Self>) {
fn start_cache_warming(&mut self, model: &Arc<dyn LanguageModel>, cx: &mut Context<Self>) {
let cache_configuration = model.cache_configuration();
if !self.mark_cache_anchors(&cache_configuration, true, cx) {
@@ -1349,7 +1403,7 @@ impl Context {
});
}
pub fn update_cache_status_for_completion(&mut self, cx: &mut ModelContext<Self>) {
pub fn update_cache_status_for_completion(&mut self, cx: &mut Context<Self>) {
let cached_message_ids: Vec<MessageId> = self
.messages_metadata
.iter()
@@ -1374,7 +1428,7 @@ impl Context {
cx.notify();
}
pub fn reparse(&mut self, cx: &mut ModelContext<Self>) {
pub fn reparse(&mut self, cx: &mut Context<Self>) {
let buffer = self.buffer.read(cx).text_snapshot();
let mut row_ranges = self
.edits_since_last_parse
@@ -1447,7 +1501,7 @@ impl Context {
buffer: &BufferSnapshot,
updated: &mut Vec<ParsedSlashCommand>,
removed: &mut Vec<Range<text::Anchor>>,
cx: &AppContext,
cx: &App,
) {
let old_range = self.pending_command_indices_for_range(range.clone(), cx);
@@ -1501,7 +1555,7 @@ impl Context {
fn invalidate_pending_slash_commands(
&mut self,
buffer: &BufferSnapshot,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let mut invalidated_command_ids = Vec::new();
for (&command_id, command) in self.invoked_slash_commands.iter_mut() {
@@ -1535,7 +1589,7 @@ impl Context {
buffer: &BufferSnapshot,
updated: &mut Vec<Range<text::Anchor>>,
removed: &mut Vec<Range<text::Anchor>>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
// Rebuild the XML tags in the edited range.
let intersecting_tags_range =
@@ -1578,7 +1632,7 @@ impl Context {
&self,
buffer: &BufferSnapshot,
range: Range<text::Anchor>,
cx: &AppContext,
cx: &App,
) -> Vec<XmlTag> {
let mut messages = self.messages(cx).peekable();
@@ -1635,7 +1689,7 @@ impl Context {
tags_start_ix: usize,
buffer_end: text::Anchor,
buffer: &BufferSnapshot,
cx: &AppContext,
cx: &App,
) -> Vec<AssistantPatch> {
let mut new_patches = Vec::new();
let mut pending_patch = None;
@@ -1793,7 +1847,7 @@ impl Context {
pub fn pending_command_for_position(
&mut self,
position: language::Anchor,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Option<&mut ParsedSlashCommand> {
let buffer = self.buffer.read(cx);
match self
@@ -1817,7 +1871,7 @@ impl Context {
pub fn pending_commands_for_range(
&self,
range: Range<language::Anchor>,
cx: &AppContext,
cx: &App,
) -> &[ParsedSlashCommand] {
let range = self.pending_command_indices_for_range(range, cx);
&self.parsed_slash_commands[range]
@@ -1826,7 +1880,7 @@ impl Context {
fn pending_command_indices_for_range(
&self,
range: Range<language::Anchor>,
cx: &AppContext,
cx: &App,
) -> Range<usize> {
self.indices_intersecting_buffer_range(&self.parsed_slash_commands, range, cx)
}
@@ -1835,7 +1889,7 @@ impl Context {
&self,
all_annotations: &[T],
range: Range<language::Anchor>,
cx: &AppContext,
cx: &App,
) -> Range<usize> {
let buffer = self.buffer.read(cx);
let start_ix = match all_annotations
@@ -1858,7 +1912,7 @@ impl Context {
name: &str,
output: Task<SlashCommandResult>,
ensure_trailing_newline: bool,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let version = self.version.clone();
let command_id = InvokedSlashCommandId(self.next_timestamp());
@@ -2126,7 +2180,7 @@ impl Context {
fn insert_slash_command_output_section(
&mut self,
section: SlashCommandOutputSection<language::Anchor>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let buffer = self.buffer.read(cx);
let insertion_ix = match self
@@ -2156,7 +2210,7 @@ impl Context {
&mut self,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let insert_output_task = cx.spawn(|this, mut cx| {
let tool_use_id = tool_use_id.clone();
@@ -2214,11 +2268,11 @@ impl Context {
}
}
pub fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
pub fn completion_provider_changed(&mut self, cx: &mut Context<Self>) {
self.count_remaining_tokens(cx);
}
fn get_last_valid_message_id(&self, cx: &ModelContext<Self>) -> Option<MessageId> {
fn get_last_valid_message_id(&self, cx: &Context<Self>) -> Option<MessageId> {
self.message_anchors.iter().rev().find_map(|message| {
message
.start
@@ -2230,7 +2284,7 @@ impl Context {
pub fn assist(
&mut self,
request_type: RequestType,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Option<MessageAnchor> {
let model_registry = LanguageModelRegistry::read_global(cx);
let provider = model_registry.active_provider()?;
@@ -2246,7 +2300,10 @@ impl Context {
let mut request = self.to_completion_request(request_type, cx);
if cx.has_flag::<ToolUseFeatureFlag>() {
// Don't attach tools for now; we'll be removing tool use from
// Assistant1 shortly.
#[allow(clippy::overly_complex_bool_expr)]
if false && cx.has_flag::<ToolUseFeatureFlag>() {
request.tools = self
.tools
.tools(cx)
@@ -2458,7 +2515,7 @@ impl Context {
pub fn to_completion_request(
&self,
request_type: RequestType,
cx: &AppContext,
cx: &App,
) -> LanguageModelRequest {
let buffer = self.buffer.read(cx);
@@ -2570,7 +2627,7 @@ impl Context {
completion_request
}
pub fn cancel_last_assist(&mut self, cx: &mut ModelContext<Self>) -> bool {
pub fn cancel_last_assist(&mut self, cx: &mut Context<Self>) -> bool {
if let Some(pending_completion) = self.pending_completions.pop() {
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
if metadata.status == MessageStatus::Pending {
@@ -2583,7 +2640,7 @@ impl Context {
}
}
pub fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
pub fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut Context<Self>) {
for id in &ids {
if let Some(metadata) = self.messages_metadata.get(id) {
let role = metadata.role.cycle();
@@ -2594,7 +2651,7 @@ impl Context {
self.message_roles_updated(ids, cx);
}
fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut Context<Self>) {
let mut ranges = Vec::new();
for message in self.messages(cx) {
if ids.contains(&message.id) {
@@ -2617,7 +2674,7 @@ impl Context {
pub fn update_metadata(
&mut self,
id: MessageId,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
f: impl FnOnce(&mut MessageMetadata),
) {
let version = self.version.clone();
@@ -2641,7 +2698,7 @@ impl Context {
message_id: MessageId,
role: Role,
status: MessageStatus,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Option<MessageAnchor> {
if let Some(prev_message_ix) = self
.message_anchors
@@ -2675,7 +2732,7 @@ impl Context {
offset: usize,
role: Role,
status: MessageStatus,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> MessageAnchor {
let start = self.buffer.update(cx, |buffer, cx| {
buffer.edit([(offset..offset, "\n")], None, cx);
@@ -2705,7 +2762,7 @@ impl Context {
anchor
}
pub fn insert_content(&mut self, content: Content, cx: &mut ModelContext<Self>) {
pub fn insert_content(&mut self, content: Content, cx: &mut Context<Self>) {
let buffer = self.buffer.read(cx);
let insertion_ix = match self
.contents
@@ -2721,7 +2778,7 @@ impl Context {
cx.emit(ContextEvent::MessagesEdited);
}
pub fn contents<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Content> {
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
let buffer = self.buffer.read(cx);
self.contents
.iter()
@@ -2735,7 +2792,7 @@ impl Context {
pub fn split_message(
&mut self,
range: Range<usize>,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
let start_message = self.message_for_offset(range.start, cx);
let end_message = self.message_for_offset(range.end, cx);
@@ -2861,7 +2918,7 @@ impl Context {
&mut self,
new_anchor: MessageAnchor,
new_metadata: MessageMetadata,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
cx.emit(ContextEvent::MessagesEdited);
@@ -2879,7 +2936,7 @@ impl Context {
self.message_anchors.insert(insertion_ix, new_anchor);
}
pub(super) fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) {
pub fn summarize(&mut self, replace_old: bool, cx: &mut Context<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return;
};
@@ -2957,14 +3014,14 @@ impl Context {
}
}
fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
fn message_for_offset(&self, offset: usize, cx: &App) -> Option<Message> {
self.messages_for_offsets([offset], cx).pop()
}
pub fn messages_for_offsets(
&self,
offsets: impl IntoIterator<Item = usize>,
cx: &AppContext,
cx: &App,
) -> Vec<Message> {
let mut result = Vec::new();
@@ -2997,14 +3054,14 @@ impl Context {
fn messages_from_anchors<'a>(
&'a self,
message_anchors: impl Iterator<Item = &'a MessageAnchor> + 'a,
cx: &'a AppContext,
cx: &'a App,
) -> impl 'a + Iterator<Item = Message> {
let buffer = self.buffer.read(cx);
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
}
pub fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
pub fn messages<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Message> {
self.messages_from_anchors(self.message_anchors.iter(), cx)
}
@@ -3052,7 +3109,7 @@ impl Context {
&mut self,
debounce: Option<Duration>,
fs: Arc<dyn Fs>,
cx: &mut ModelContext<Context>,
cx: &mut Context<AssistantContext>,
) {
if self.replica_id() != ReplicaId::default() {
// Prevent saving a remote context for now.
@@ -3118,7 +3175,7 @@ impl Context {
});
}
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
pub fn custom_summary(&mut self, custom_summary: String, cx: &mut Context<Self>) {
let timestamp = self.next_timestamp();
let summary = self.summary.get_or_insert(ContextSummary::default());
summary.timestamp = timestamp;
@@ -3278,8 +3335,8 @@ impl SavedContext {
fn into_ops(
self,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Context>,
buffer: &Entity<Buffer>,
cx: &mut Context<AssistantContext>,
) -> Vec<ContextOperation> {
let mut operations = Vec::new();
let mut version = clock::Global::new();

View File

@@ -1,7 +1,6 @@
use super::{AssistantEdit, MessageCacheMetadata};
use crate::{
assistant_panel, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId,
ContextOperation, InvokedSlashCommandId, MessageId, MessageStatus,
AssistantContext, AssistantEdit, AssistantEditKind, CacheStatus, ContextEvent, ContextId,
ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
};
use anyhow::Result;
use assistant_slash_command::{
@@ -16,7 +15,7 @@ use futures::{
channel::mpsc,
stream::{self, StreamExt},
};
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakView};
use gpui::{prelude::*, App, Entity, SharedString, Task, TestAppContext, WeakEntity};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
@@ -35,7 +34,7 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
use ui::{IconName, WindowContext};
use ui::{IconName, Window};
use unindent::Unindent;
use util::{
test::{generate_marked_text, marked_text_ranges},
@@ -44,15 +43,14 @@ use util::{
use workspace::Workspace;
#[gpui::test]
fn test_inserting_and_removing_messages(cx: &mut AppContext) {
fn test_inserting_and_removing_messages(cx: &mut App) {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry,
None,
None,
@@ -185,16 +183,15 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
}
#[gpui::test]
fn test_message_splitting(cx: &mut AppContext) {
fn test_message_splitting(cx: &mut App) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
LanguageModelRegistry::test(cx);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry.clone(),
None,
None,
@@ -290,15 +287,14 @@ fn test_message_splitting(cx: &mut AppContext) {
}
#[gpui::test]
fn test_messages_for_offsets(cx: &mut AppContext) {
fn test_messages_for_offsets(cx: &mut App) {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry,
None,
None,
@@ -371,9 +367,9 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
);
fn message_ids_for_offsets(
context: &Model<Context>,
context: &Entity<AssistantContext>,
offsets: &[usize],
cx: &AppContext,
cx: &App,
) -> Vec<MessageId> {
context
.read(cx)
@@ -390,7 +386,6 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test);
cx.update(Project::init_settings);
cx.update(assistant_panel::init);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
@@ -412,8 +407,8 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry.clone(),
None,
None,
@@ -613,7 +608,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
#[track_caller]
fn assert_text_and_context_ranges(
buffer: &Model<Buffer>,
buffer: &Entity<Buffer>,
ranges: &RefCell<ContextRanges>,
expected_marked_text: &str,
cx: &mut TestAppContext,
@@ -698,13 +693,12 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
let project = Project::test(fs, [Path::new("/root")], cx).await;
cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
// Create a new context
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry.clone(),
Some(project),
None,
@@ -968,8 +962,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
// Ensure steps are re-parsed when deserializing.
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
let deserialized_context = cx.new_model(|cx| {
Context::deserialize(
let deserialized_context = cx.new(|cx| {
AssistantContext::deserialize(
serialized_context,
Default::default(),
registry.clone(),
@@ -1012,7 +1006,11 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx,
);
fn edit(context: &Model<Context>, new_text_marked_with_edits: &str, cx: &mut TestAppContext) {
fn edit(
context: &Entity<AssistantContext>,
new_text_marked_with_edits: &str,
cx: &mut TestAppContext,
) {
context.update(cx, |context, cx| {
context.buffer.update(cx, |buffer, cx| {
buffer.edit_via_marked_text(&new_text_marked_with_edits.unindent(), None, cx);
@@ -1023,7 +1021,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
#[track_caller]
fn expect_patches(
context: &Model<Context>,
context: &Entity<AssistantContext>,
expected_marked_text: &str,
expected_suggestions: &[&[AssistantEdit]],
cx: &mut TestAppContext,
@@ -1081,11 +1079,10 @@ async fn test_serialization(cx: &mut TestAppContext) {
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry.clone(),
None,
None,
@@ -1128,8 +1125,8 @@ async fn test_serialization(cx: &mut TestAppContext) {
);
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
let deserialized_context = cx.new_model(|cx| {
Context::deserialize(
let deserialized_context = cx.new(|cx| {
AssistantContext::deserialize(
serialized_context,
Default::default(),
registry.clone(),
@@ -1173,7 +1170,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
let slash_commands = cx.update(SlashCommandRegistry::default_global);
slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false);
@@ -1187,8 +1183,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
let context_id = ContextId::new();
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
for i in 0..num_peers {
let context = cx.new_model(|cx| {
Context::new(
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
i as ReplicaId,
language::Capability::ReadWrite,
@@ -1442,15 +1438,14 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
}
#[gpui::test]
fn test_mark_cache_anchors(cx: &mut AppContext) {
fn test_mark_cache_anchors(cx: &mut App) {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| {
Context::local(
let context = cx.new(|cx| {
AssistantContext::local(
registry,
None,
None,
@@ -1603,7 +1598,7 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
);
}
fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> {
fn messages(context: &Entity<AssistantContext>, cx: &App) -> Vec<(MessageId, Role, Range<usize>)> {
context
.read(cx)
.messages(cx)
@@ -1612,8 +1607,8 @@ fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role,
}
fn messages_cache(
context: &Model<Context>,
cx: &AppContext,
context: &Entity<AssistantContext>,
cx: &App,
) -> Vec<(MessageId, Option<MessageCacheMetadata>)> {
context
.read(cx)
@@ -1642,8 +1637,9 @@ impl SlashCommand for FakeSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
_workspace: Option<WeakEntity<Workspace>>,
_window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(vec![]))
}
@@ -1657,9 +1653,10 @@ impl SlashCommand for FakeSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
_window: &mut Window,
_cx: &mut App,
) -> Task<SlashCommandResult> {
Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0),

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
use std::sync::Arc;
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
use picker::{Picker, PickerDelegate};
use project::Project;
use ui::utils::{format_distance_from_now, DateTimeType};
use ui::{prelude::*, Avatar, ListItem, ListItemSpacing};
use workspace::{Item, Workspace};
use crate::{
AssistantPanelDelegate, ContextStore, RemoteContextMetadata, SavedContextMetadata,
DEFAULT_TAB_TITLE,
};
#[derive(Clone)]
pub enum ContextMetadata {
Remote(RemoteContextMetadata),
Saved(SavedContextMetadata),
}
enum SavedContextPickerEvent {
Confirmed(ContextMetadata),
}
pub struct ContextHistory {
picker: Entity<Picker<SavedContextPickerDelegate>>,
_subscriptions: Vec<Subscription>,
workspace: WeakEntity<Workspace>,
}
impl ContextHistory {
pub fn new(
project: Entity<Project>,
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(
SavedContextPickerDelegate::new(project, context_store.clone()),
window,
cx,
)
.modal(false)
.max_height(None)
});
let subscriptions = vec![
cx.observe_in(&context_store, window, |this, _, window, cx| {
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
}),
cx.subscribe_in(&picker, window, Self::handle_picker_event),
];
Self {
picker,
_subscriptions: subscriptions,
workspace,
}
}
fn handle_picker_event(
&mut self,
_: &Entity<Picker<SavedContextPickerDelegate>>,
event: &SavedContextPickerEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let SavedContextPickerEvent::Confirmed(context) = event;
let Some(assistant_panel_delegate) = <dyn AssistantPanelDelegate>::try_global(cx) else {
return;
};
self.workspace
.update(cx, |workspace, cx| match context {
ContextMetadata::Remote(metadata) => {
assistant_panel_delegate
.open_remote_context(workspace, metadata.id.clone(), window, cx)
.detach_and_log_err(cx);
}
ContextMetadata::Saved(metadata) => {
assistant_panel_delegate
.open_saved_context(workspace, metadata.path.clone(), window, cx)
.detach_and_log_err(cx);
}
})
.ok();
}
}
impl Render for ContextHistory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().size_full().child(self.picker.clone())
}
}
impl Focusable for ContextHistory {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl EventEmitter<()> for ContextHistory {}
impl Item for ContextHistory {
type Event = ();
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some("History".into())
}
}
struct SavedContextPickerDelegate {
store: Entity<ContextStore>,
project: Entity<Project>,
matches: Vec<ContextMetadata>,
selected_index: usize,
}
impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
impl SavedContextPickerDelegate {
fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {
Self {
project,
store,
matches: Vec::new(),
selected_index: 0,
}
}
}
impl PickerDelegate for SavedContextPickerDelegate {
type ListItem = ListItem;
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Search...".into()
}
fn update_matches(
&mut self,
query: String,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let search = self.store.read(cx).search(query, cx);
cx.spawn(|this, mut cx| async move {
let matches = search.await;
this.update(&mut cx, |this, cx| {
let host_contexts = this.delegate.store.read(cx).host_contexts();
this.delegate.matches = host_contexts
.iter()
.cloned()
.map(ContextMetadata::Remote)
.chain(matches.into_iter().map(ContextMetadata::Saved))
.collect();
this.delegate.selected_index = 0;
cx.notify();
})
.ok();
})
}
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
if let Some(metadata) = self.matches.get(self.selected_index) {
cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
}
}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let context = self.matches.get(ix)?;
let item = match context {
ContextMetadata::Remote(context) => {
let host_user = self.project.read(cx).host().and_then(|collaborator| {
self.project
.read(cx)
.user_store()
.read(cx)
.get_cached_user(collaborator.user_id)
});
div()
.flex()
.w_full()
.justify_between()
.gap_2()
.child(
h_flex().flex_1().overflow_x_hidden().child(
Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
.size(LabelSize::Small),
),
)
.child(
h_flex()
.gap_2()
.children(if let Some(host_user) = host_user {
vec![
Avatar::new(host_user.avatar_uri.clone()).into_any_element(),
Label::new(format!("Shared by @{}", host_user.github_login))
.color(Color::Muted)
.size(LabelSize::Small)
.into_any_element(),
]
} else {
vec![Label::new("Shared by host")
.color(Color::Muted)
.size(LabelSize::Small)
.into_any_element()]
}),
)
}
ContextMetadata::Saved(context) => div()
.flex()
.w_full()
.justify_between()
.gap_2()
.child(
h_flex()
.flex_1()
.child(Label::new(context.title.clone()).size(LabelSize::Small))
.overflow_x_hidden(),
)
.child(
Label::new(format_distance_from_now(
DateTimeType::Local(context.mtime),
false,
true,
true,
))
.color(Color::Muted)
.size(LabelSize::Small),
),
};
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(item),
)
}
}

View File

@@ -1,5 +1,5 @@
use crate::{
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata,
};
use anyhow::{anyhow, Context as _, Result};
@@ -13,9 +13,7 @@ use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use fs::Fs;
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use language::LanguageRegistry;
use paths::contexts_dir;
use project::Project;
@@ -33,7 +31,7 @@ use std::{
};
use util::{ResultExt, TryFutureExt};
pub fn init(client: &AnyProtoClient) {
pub(crate) fn init(client: &AnyProtoClient) {
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
client.add_model_request_handler(ContextStore::handle_open_context);
client.add_model_request_handler(ContextStore::handle_create_context);
@@ -50,7 +48,7 @@ pub struct RemoteContextMetadata {
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
context_server_manager: Model<ContextServerManager>,
context_server_manager: Entity<ContextServerManager>,
context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
host_contexts: Vec<RemoteContextMetadata>,
@@ -61,7 +59,7 @@ pub struct ContextStore {
telemetry: Arc<Telemetry>,
_watch_updates: Task<Option<()>>,
client: Arc<Client>,
project: Model<Project>,
project: Entity<Project>,
project_is_shared: bool,
client_subscription: Option<client::Subscription>,
_project_subscriptions: Vec<gpui::Subscription>,
@@ -75,19 +73,19 @@ pub enum ContextStoreEvent {
impl EventEmitter<ContextStoreEvent> for ContextStore {}
enum ContextHandle {
Weak(WeakModel<Context>),
Strong(Model<Context>),
Weak(WeakEntity<AssistantContext>),
Strong(Entity<AssistantContext>),
}
impl ContextHandle {
fn upgrade(&self) -> Option<Model<Context>> {
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
match self {
ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()),
}
}
fn downgrade(&self) -> WeakModel<Context> {
fn downgrade(&self) -> WeakEntity<AssistantContext> {
match self {
ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(),
@@ -97,12 +95,12 @@ impl ContextHandle {
impl ContextStore {
pub fn new(
project: Model<Project>,
project: Entity<Project>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
cx: &mut App,
) -> Task<Result<Entity<Self>>> {
let fs = project.read(cx).fs().clone();
let languages = project.read(cx).languages().clone();
let telemetry = project.read(cx).client().telemetry().clone();
@@ -110,10 +108,10 @@ impl ContextStore {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let this = cx.new(|cx: &mut Context<Self>| {
let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new_model(|cx| {
let context_server_manager = cx.new(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
let mut this = Self {
@@ -163,9 +161,9 @@ impl ContextStore {
}
async fn handle_advertise_contexts(
this: Model<Self>,
this: Entity<Self>,
envelope: TypedEnvelope<proto::AdvertiseContexts>,
mut cx: AsyncAppContext,
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.host_contexts = envelope
@@ -182,9 +180,9 @@ impl ContextStore {
}
async fn handle_open_context(
this: Model<Self>,
this: Entity<Self>,
envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncAppContext,
mut cx: AsyncApp,
) -> Result<proto::OpenContextResponse> {
let context_id = ContextId::from_proto(envelope.payload.context_id);
let operations = this.update(&mut cx, |this, cx| {
@@ -212,9 +210,9 @@ impl ContextStore {
}
async fn handle_create_context(
this: Model<Self>,
this: Entity<Self>,
_: TypedEnvelope<proto::CreateContext>,
mut cx: AsyncAppContext,
mut cx: AsyncApp,
) -> Result<proto::CreateContextResponse> {
let (context_id, operations) = this.update(&mut cx, |this, cx| {
if this.project.read(cx).is_via_collab() {
@@ -240,9 +238,9 @@ impl ContextStore {
}
async fn handle_update_context(
this: Model<Self>,
this: Entity<Self>,
envelope: TypedEnvelope<proto::UpdateContext>,
mut cx: AsyncAppContext,
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
let context_id = ContextId::from_proto(envelope.payload.context_id);
@@ -256,9 +254,9 @@ impl ContextStore {
}
async fn handle_synchronize_contexts(
this: Model<Self>,
this: Entity<Self>,
envelope: TypedEnvelope<proto::SynchronizeContexts>,
mut cx: AsyncAppContext,
mut cx: AsyncApp,
) -> Result<proto::SynchronizeContextsResponse> {
this.update(&mut cx, |this, cx| {
if this.project.read(cx).is_via_collab() {
@@ -299,7 +297,7 @@ impl ContextStore {
})?
}
fn handle_project_changed(&mut self, _: Model<Project>, cx: &mut ModelContext<Self>) {
fn handle_project_changed(&mut self, _: Entity<Project>, cx: &mut Context<Self>) {
let is_shared = self.project.read(cx).is_shared();
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
if is_shared == was_shared {
@@ -320,7 +318,7 @@ impl ContextStore {
.client
.subscribe_to_entity(remote_id)
.log_err()
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
.map(|subscription| subscription.set_model(&cx.entity(), &mut cx.to_async()));
self.advertise_contexts(cx);
} else {
self.client_subscription = None;
@@ -329,9 +327,9 @@ impl ContextStore {
fn handle_project_event(
&mut self,
_: Model<Project>,
_: Entity<Project>,
event: &project::Event,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
match event {
project::Event::Reshared => {
@@ -361,9 +359,9 @@ impl ContextStore {
}
}
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> {
let context = cx.new_model(|cx| {
Context::local(
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new(|cx| {
AssistantContext::local(
self.languages.clone(),
Some(self.project.clone()),
Some(self.telemetry.clone()),
@@ -379,8 +377,8 @@ impl ContextStore {
pub fn create_remote_context(
&mut self,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote")));
@@ -399,8 +397,8 @@ impl ContextStore {
let response = request.await?;
let context_id = ContextId::from_proto(response.context_id);
let context_proto = response.context.context("invalid context")?;
let context = cx.new_model(|cx| {
Context::new(
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
replica_id,
capability,
@@ -439,8 +437,8 @@ impl ContextStore {
pub fn open_local_context(
&mut self,
path: PathBuf,
cx: &ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
cx: &Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
return Task::ready(Ok(existing_context));
}
@@ -462,8 +460,8 @@ impl ContextStore {
cx.spawn(|this, mut cx| async move {
let saved_context = load.await?;
let context = cx.new_model(|cx| {
Context::deserialize(
let context = cx.new(|cx| {
AssistantContext::deserialize(
saved_context,
path.clone(),
languages,
@@ -486,7 +484,7 @@ impl ContextStore {
})
}
fn loaded_context_for_path(&self, path: &Path, cx: &AppContext) -> Option<Model<Context>> {
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).path() == Some(path) {
@@ -497,11 +495,11 @@ impl ContextStore {
})
}
pub(super) fn loaded_context_for_id(
pub fn loaded_context_for_id(
&self,
id: &ContextId,
cx: &AppContext,
) -> Option<Model<Context>> {
cx: &App,
) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).id() == id {
@@ -515,8 +513,8 @@ impl ContextStore {
pub fn open_remote_context(
&mut self,
context_id: ContextId,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote")));
@@ -541,8 +539,8 @@ impl ContextStore {
cx.spawn(|this, mut cx| async move {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
let context = cx.new_model(|cx| {
Context::new(
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
replica_id,
capability,
@@ -578,7 +576,7 @@ impl ContextStore {
})
}
fn register_context(&mut self, context: &Model<Context>, cx: &mut ModelContext<Self>) {
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
let handle = if self.project_is_shared {
ContextHandle::Strong(context.clone())
} else {
@@ -591,9 +589,9 @@ impl ContextStore {
fn handle_context_event(
&mut self,
context: Model<Context>,
context: Entity<AssistantContext>,
event: &ContextEvent,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
@@ -618,7 +616,7 @@ impl ContextStore {
}
}
fn advertise_contexts(&self, cx: &AppContext) {
fn advertise_contexts(&self, cx: &App) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
@@ -652,7 +650,7 @@ impl ContextStore {
.ok();
}
fn synchronize_contexts(&mut self, cx: &mut ModelContext<Self>) {
fn synchronize_contexts(&mut self, cx: &mut Context<Self>) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
@@ -707,7 +705,7 @@ impl ContextStore {
.detach_and_log_err(cx);
}
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> {
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
let metadata = self.contexts_metadata.clone();
let executor = cx.background_executor().clone();
cx.background_executor().spawn(async move {
@@ -741,7 +739,7 @@ impl ContextStore {
&self.host_contexts
}
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {
fs.create_dir(contexts_dir()).await?;
@@ -790,8 +788,8 @@ impl ContextStore {
})
}
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) {
cx.update_model(
pub fn restart_context_servers(&mut self, cx: &mut Context<Self>) {
cx.update_entity(
&self.context_server_manager,
|context_server_manager, cx| {
for server in context_server_manager.servers() {
@@ -803,7 +801,7 @@ impl ContextStore {
);
}
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
cx.subscribe(
&self.context_server_manager.clone(),
Self::handle_context_server_event,
@@ -813,9 +811,9 @@ impl ContextStore {
fn handle_context_server_event(
&mut self,
context_server_manager: Model<ContextServerManager>,
context_server_manager: Entity<ContextServerManager>,
event: &context_server::manager::Event,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) {
let slash_command_working_set = self.slash_commands.clone();
let tool_working_set = self.tools.clone();

View File

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

View File

@@ -1,11 +1,10 @@
use crate::assistant_panel::ContextEditor;
use crate::context_editor::ContextEditor;
use anyhow::Result;
use assistant_slash_command::AfterCompletion;
pub use assistant_slash_command::SlashCommand;
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
use gpui::{App, Context, Entity, Task, WeakEntity, Window};
use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
use parking_lot::Mutex;
use project::CompletionIntent;
@@ -21,25 +20,18 @@ use std::{
};
use workspace::Workspace;
pub(crate) struct SlashCommandCompletionProvider {
pub struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>,
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
}
pub(crate) struct SlashCommandLine {
/// The range within the line containing the command name.
pub name: Range<usize>,
/// Ranges within the line containing the command arguments.
pub arguments: Vec<Range<usize>>,
editor: Option<WeakEntity<ContextEditor>>,
workspace: Option<WeakEntity<Workspace>>,
}
impl SlashCommandCompletionProvider {
pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
editor: Option<WeakEntity<ContextEditor>>,
workspace: Option<WeakEntity<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
@@ -54,7 +46,8 @@ impl SlashCommandCompletionProvider {
command_name: &str,
command_range: Range<Anchor>,
name_range: Range<Anchor>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<project::Completion>>> {
let slash_commands = self.slash_commands.clone();
let candidates = slash_commands
@@ -66,7 +59,7 @@ impl SlashCommandCompletionProvider {
let command_name = command_name.to_string();
let editor = self.editor.clone();
let workspace = self.workspace.clone();
cx.spawn(|mut cx| async move {
window.spawn(cx, |mut cx| async move {
let matches = match_strings(
&candidates,
&command_name,
@@ -77,7 +70,7 @@ impl SlashCommandCompletionProvider {
)
.await;
cx.update(|cx| {
cx.update(|_, cx| {
matches
.into_iter()
.filter_map(|mat| {
@@ -99,28 +92,31 @@ impl SlashCommandCompletionProvider {
let editor = editor.clone();
let workspace = workspace.clone();
Arc::new(
move |intent: CompletionIntent, cx: &mut WindowContext| {
if !requires_argument
&& (!accepts_arguments || intent.is_complete())
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&[],
true,
workspace.clone(),
cx,
);
})
.ok();
false
} else {
requires_argument || accepts_arguments
}
},
) as Arc<_>
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
if !requires_argument
&& (!accepts_arguments || intent.is_complete())
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&[],
true,
workspace.clone(),
window,
cx,
);
})
.ok();
false
} else {
requires_argument || accepts_arguments
}
},
) as Arc<_>
});
Some(project::Completion {
old_range: name_range.clone(),
@@ -138,6 +134,7 @@ impl SlashCommandCompletionProvider {
})
}
#[allow(clippy::too_many_arguments)]
fn complete_command_argument(
&self,
command_name: &str,
@@ -145,7 +142,8 @@ impl SlashCommandCompletionProvider {
command_range: Range<Anchor>,
argument_range: Range<Anchor>,
last_argument_range: Range<Anchor>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<project::Completion>>> {
let new_cancel_flag = Arc::new(AtomicBool::new(false));
let mut flag = self.cancel_flag.lock();
@@ -156,6 +154,7 @@ impl SlashCommandCompletionProvider {
arguments,
new_cancel_flag.clone(),
self.workspace.clone(),
window,
cx,
);
let command_name: Arc<str> = command_name.into();
@@ -183,7 +182,9 @@ impl SlashCommandCompletionProvider {
let command_range = command_range.clone();
let command_name = command_name.clone();
move |intent: CompletionIntent, cx: &mut WindowContext| {
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
if new_argument.after_completion.run()
|| intent.is_complete()
{
@@ -195,6 +196,7 @@ impl SlashCommandCompletionProvider {
&completed_arguments,
true,
workspace.clone(),
window,
cx,
);
})
@@ -238,10 +240,11 @@ impl SlashCommandCompletionProvider {
impl CompletionProvider for SlashCommandCompletionProvider {
fn completions(
&self,
buffer: &Model<Buffer>,
buffer: &Entity<Buffer>,
buffer_position: Anchor,
_: editor::CompletionContext,
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Vec<project::Completion>>> {
let Some((name, arguments, command_range, last_argument_range)) =
buffer.update(cx, |buffer, _cx| {
@@ -296,30 +299,31 @@ impl CompletionProvider for SlashCommandCompletionProvider {
command_range,
argument_range,
last_argument_range,
window,
cx,
)
} else {
self.complete_command_name(&name, command_range, last_argument_range, cx)
self.complete_command_name(&name, command_range, last_argument_range, window, cx)
}
}
fn resolve_completions(
&self,
_: Model<Buffer>,
_: Entity<Buffer>,
_: Vec<usize>,
_: Rc<RefCell<Box<[project::Completion]>>>,
_: &mut ViewContext<Editor>,
_: &mut Context<Editor>,
) -> Task<Result<bool>> {
Task::ready(Ok(true))
}
fn is_completion_trigger(
&self,
buffer: &Model<Buffer>,
buffer: &Entity<Buffer>,
position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
cx: &mut ViewContext<Editor>,
cx: &mut Context<Editor>,
) -> bool {
let buffer = buffer.read(cx);
let position = position.to_point(buffer);
@@ -336,57 +340,3 @@ impl CompletionProvider for SlashCommandCompletionProvider {
false
}
}
impl SlashCommandLine {
pub(crate) fn parse(line: &str) -> Option<Self> {
let mut call: Option<Self> = None;
let mut ix = 0;
for c in line.chars() {
let next_ix = ix + c.len_utf8();
if let Some(call) = &mut call {
// The command arguments start at the first non-whitespace character
// after the command name, and continue until the end of the line.
if let Some(argument) = call.arguments.last_mut() {
if c.is_whitespace() {
if (*argument).is_empty() {
argument.start = next_ix;
argument.end = next_ix;
} else {
argument.end = ix;
call.arguments.push(next_ix..next_ix);
}
} else {
argument.end = next_ix;
}
}
// The command name ends at the first whitespace character.
else if !call.name.is_empty() {
if c.is_whitespace() {
call.arguments = vec![next_ix..next_ix];
} else {
call.name.end = next_ix;
}
}
// The command name must begin with a letter.
else if c.is_alphabetic() {
call.name.end = next_ix;
} else {
return None;
}
}
// Commands start with a slash.
else if c == '/' {
call = Some(SlashCommandLine {
name: next_ix..next_ix,
arguments: Vec::new(),
});
}
// The line can't contain anything before the slash except for whitespace.
else if !c.is_whitespace() {
return None;
}
ix = next_ix;
}
call
}
}

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ pub use crate::slash_command_working_set::*;
use anyhow::Result;
use futures::stream::{self, BoxStream};
use futures::StreamExt;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use gpui::{App, SharedString, Task, WeakEntity, Window};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role;
use serde::{Deserialize, Serialize};
@@ -18,7 +18,7 @@ use std::{
};
use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &mut App) {
SlashCommandRegistry::default_global(cx);
extension_slash_command::init(cx);
}
@@ -71,7 +71,7 @@ pub trait SlashCommand: 'static + Send + Sync {
fn icon(&self) -> IconName {
IconName::Slash
}
fn label(&self, _cx: &AppContext) -> CodeLabel {
fn label(&self, _cx: &App) -> CodeLabel {
CodeLabel::plain(self.name(), None)
}
fn description(&self) -> String;
@@ -80,35 +80,32 @@ pub trait SlashCommand: 'static + Send + Sync {
self: Arc<Self>,
arguments: &[String],
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
workspace: Option<WeakEntity<Workspace>>,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>>;
fn requires_argument(&self) -> bool;
fn accepts_arguments(&self) -> bool {
self.requires_argument()
}
#[allow(clippy::too_many_arguments)]
fn run(
self: Arc<Self>,
arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
workspace: WeakEntity<Workspace>,
// TODO: We're just using the `LspAdapterDelegate` here because that is
// what the extension API is already expecting.
//
// It may be that `LspAdapterDelegate` needs a more general name, or
// perhaps another kind of delegate is needed here.
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult>;
}
pub type RenderFoldPlaceholder = Arc<
dyn Send
+ Sync
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
>;
#[derive(Debug, PartialEq)]
pub enum SlashCommandContent {
Text {
@@ -268,6 +265,67 @@ impl SlashCommandOutputSection<language::Anchor> {
}
}
pub struct SlashCommandLine {
/// The range within the line containing the command name.
pub name: Range<usize>,
/// Ranges within the line containing the command arguments.
pub arguments: Vec<Range<usize>>,
}
impl SlashCommandLine {
pub fn parse(line: &str) -> Option<Self> {
let mut call: Option<Self> = None;
let mut ix = 0;
for c in line.chars() {
let next_ix = ix + c.len_utf8();
if let Some(call) = &mut call {
// The command arguments start at the first non-whitespace character
// after the command name, and continue until the end of the line.
if let Some(argument) = call.arguments.last_mut() {
if c.is_whitespace() {
if (*argument).is_empty() {
argument.start = next_ix;
argument.end = next_ix;
} else {
argument.end = ix;
call.arguments.push(next_ix..next_ix);
}
} else {
argument.end = next_ix;
}
}
// The command name ends at the first whitespace character.
else if !call.name.is_empty() {
if c.is_whitespace() {
call.arguments = vec![next_ix..next_ix];
} else {
call.name.end = next_ix;
}
}
// The command name must begin with a letter.
else if c.is_alphabetic() {
call.name.end = next_ix;
} else {
return None;
}
}
// Commands start with a slash.
else if c == '/' {
call = Some(SlashCommandLine {
name: next_ix..next_ix,
arguments: Vec::new(),
});
}
// The line can't contain anything before the slash except for whitespace.
else if !c.is_whitespace() {
return None;
}
ix = next_ix;
}
call
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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