Compare commits

..

77 Commits

Author SHA1 Message Date
Michael Sloan
2a55062dd1 Refactor multibuffer methods for multibuffer range -> buffer range
* Renames `excerpts_for_ranges` to `disjoint_ranges_to_buffer_ranges`,
and modifies it to return `MultiBufferExcerpt` since it provides more
information and was already being built.

* `range_to_buffer_ranges` now returns an iterator, should improve
efficiency in a few places.  Now implemented by just calling
`disjoint_ranges_to_buffer_ranges`.
2025-01-06 19:05:57 -07:00
Michael Sloan
1ef638d802 Remove unnecessary lifetimes on Buffer::diagnostic_group (#22698)
Release Notes:

- N/A
2025-01-06 06:12:35 +00:00
Michael Sloan
fcb03989d2 Remove unnecessary finding of primary diagnostic for diagnostic hover (#22697)
No need to find or store the primary range ahead of time as it's found
by `activate_diagnostics`.

Not entirely sure we should still even have the special case when the
popover is visible. It does support the keyboard interaction of opening
hover followed by jumping to the primary position, but that seems pretty
undiscoverable.

Support for clicking the hover to navigate to the primary diagnostic was
removed in https://github.com/zed-industries/zed/pull/3408

Release Notes:

- N/A
2025-01-06 06:08:17 +00:00
Burak Varlı
2a9fa0e2dc Ensure end >= start in lsp::Range (#22690)
Should resolve https://github.com/zed-industries/zed/issues/21714.

In some conditions that I'm not sure of, Zed sends LSP requests with
`start > end` position, and zls has an [assertion for end >=
start](f253553b82/src/offsets.zig (L492)),
and that causes zls to crash, like:

```bash
# first `textDocument/inlayHint` request with `end >= start`
[2025-01-05T19:33:09+00:00 TRACE lsp] outgoing message:{"jsonrpc":"2.0","id":1043,"method":"textDocument/inlayHint","params":{"textDocument":{"uri":"file:///Users/burak/Code/parzig/src/parquet/decoding.zig"},"range":{"start":{"line":0,"character":0},"end":{"line":24,"character":0}}}}
# successful response 
[2025-01-05T19:33:09+00:00 TRACE lsp::input_handler] incoming message: {"jsonrpc":"2.0","id":1043,"result":[{"position":{"line":0,"character":9},"label":": type","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":1,"character":22},"label":": type","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":4,"character":13},"label":": [](unknown type)","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":4,"character":30},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":4,"character":33},"label":"n:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nusize\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":5,"character":23},"label":": bool","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":6,"character":19},"label":": usize","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":9,"character":26},"label":": [](unknown type)","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":9,"character":43},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":9,"character":47},"label":"n:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nusize\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":21,"character":13},"label":": [](unknown type)","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":21,"character":30},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":21,"character":33},"label":"n:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nusize\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":33},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":36},"label":"buf:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\n[]T\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":41},"label":"bit_width:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nu8\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":52},"label":"reader:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nanytype\n```"},"paddingLeft":false,"paddingRight":true}]}
[2025-01-05T19:33:09+00:00 TRACE lsp] Took 14.855ms to receive response to "textDocument/inlayHint" id 1043
# problematic `textDocument/inlayHint` request with `start > end`
[2025-01-05T19:33:09+00:00 TRACE lsp] outgoing message:{"jsonrpc":"2.0","id":1044,"method":"textDocument/inlayHint","params":{"textDocument":{"uri":"file:///Users/burak/Code/parzig/src/parquet/decoding.zig"},"range":{"start":{"line":50,"character":25},"end":{"line":25,"character":0}}}}
# zls crashes here, and after this point, all LSP requests fail
[2025-01-05T19:33:09+00:00 TRACE lsp] incoming stderr message:thread 5391652 panic: reached unreachable code
[2025-01-05T19:33:09+00:00 ERROR lsp] cannot read LSP message headers
```

In LSP specification for
[`Range`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#range)
type, it says:
> ... If you want to specify a range that contains a line including the
line ending character(s) then use an end position denoting the start of
the next line.

I feel like zls's assertion is sensible, so I've updated the generic
`range_to_lsp` function rather than doing something specific to zls. But
let me know if this seems incorrect.

zls was crashing after 5-10 minutes of working with a Zig codebase
before, and after this change, I tested for an hour and didn't
experience any crashes.

Release Notes:

- Ensure `end >= start` in `lsp::Range`, which should fix Zig/zls
crashes.

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-01-06 05:22:28 +00:00
Michael Sloan
3ae6aa0e4d Start diagnostic group_id at 1 to handle non LS diagnostics (#22694)
In particular, `DiagnosticPopover` both:

* Supports moving the selection to a diagnostic when clicked, based on
`group_id`

* Provides Diagnostic values with `group_id: 0` providing informztion on
hover about invisible characters.

So, clicking such a popover would navigate to the very first error
produced by a language server. Really not a big deal of course, but
seems good to fix as it might result in surprising behavior in other
future circumstances

Release Notes:

- N/A
2025-01-06 05:18:56 +00:00
Michael Sloan
7506c0385b Fix a doc comment typo on DiagnosticEntry::to_lsp_diagnostic_stub (#22695)
Release Notes:

- N/A
2025-01-06 04:32:30 +00:00
Michael Sloan
570e6c80a8 Fix panic on diagnostic hover (#22693)
In #22620 `diagnostic_group` was modified to return results for
multibuffers, but was returning singleton buffer points. `hover_popover`
uses it to find the jump target for clicking the popup - which doesn't
seem to be working right now but that's a separate issue. Now that
`diagnostic_group` is returning values in multibuffers converting these
to anchors was crashing.

Also resolves a potential bug - if folding in multibuffers was supported
then "Go To Diagnostics" would not properly skip diagnostics from folded
regions.

Release Notes:

- N/A
2025-01-06 02:31:02 +00:00
tims
94ee2e1811 Fix ghost files appearing in the project panel when clicking relative paths in the terminal (#22688)
Closes #15705

When opening a file from the terminal, if the file path is relative, we
attempt to guess all possible paths where the file could be. This
involves generating paths for each worktree, the current terminal
directory, etc. For example, if we have two worktrees, `dotfiles` and
`example`, and `foo.txt` in `example/a`, the generated paths might look
like this:

- `/home/tims/dotfiles/../example/a/foo.txt` from the `dotfiles`
worktree
- `/home/tims/example/../example/a/foo.txt` from the `example` worktree
- `/home/tims/example/a/foo.txt` from the current terminal directory
(This is already canonicalized)

Note that there should only be a single path, but multiple paths are
created due to missing canonicalization.

Later, when opening these paths, the worktree prefix is stripped, and
the remaining path is used to open the file in its respective worktree.

As a result, the above three paths would resolve like this:

- `../example/a/foo.txt` as the filename in the `dotfiles` worktree
(Ghost file)
- `../example/a/foo.txt` as the filename in the `example` worktree
(Ghost file)
- `foo.txt` as the filename in the `a` directory of the `example`
worktree (This opens the file)

This PR fixes the issue by canonicalizing these paths before adding them
to the HashSet.

Before:

![before](https://github.com/user-attachments/assets/7cb98b86-1adf-462f-bcc6-9bff6a8425cd)

After:

![after](https://github.com/user-attachments/assets/44568167-2a5a-4022-ba98-b359d2c6e56b)


Release Notes:

- Fixed ghost files appearing in the project panel when clicking
relative paths in the terminal.
2025-01-05 21:49:32 +00:00
Piotr Osiewicz
299ae92ffb gpui: Do not derive serde::Deserialize for automatically generated Actions (#22687)
Closes #ISSUE

Release Notes:

- N/A
2025-01-05 17:25:00 +00:00
Cole Miller
de08e47e5b Improve panic report with reentrant SlotMap use (#22667)
`double_lease_panic` already does what we want, just extend it to the
indexing operation as well.

Release Notes:

- N/A
2025-01-04 20:37:40 +00:00
Kirill Bulatov
8151dc7696 Return back Rust completion details (#22648)
Closes https://github.com/zed-industries/zed/issues/22642

In Zed, Rust's label generators expected the details to come in ` (use
std.foo.Bar)` form, but recently, r-a started to send these details
without the leading whitespace which broke the code generation.

The PR makes LSP results parsing more lenient to work with both details'
forms.

Release Notes:

- Fixed Rust completion labels not showing the imports
2025-01-04 11:15:09 +00:00
Michael Sloan
5f1eee3c66 Fix inlay hints display reverting to settings value on theme change (#22605)
Closes #4276

Release Notes:

- Fixed inlay hints that have been manually enabled disappearing when
theme selector is used.
2025-01-04 08:26:08 +00:00
Mikayla Maki
9613084f59 Move git status out of Entry (#22224)
- [x] Rewrite worktree git handling
- [x] Fix tests
- [x] Fix `test_propagate_statuses_for_repos_under_project`
- [x] Replace `WorkDirectoryEntry` with `WorkDirectory` in
`RepositoryEntry`
- [x] Add a worktree event for capturing git status changes
- [x] Confirm that the local repositories are correctly updating the new
WorkDirectory field
- [x] Implement the git statuses query as a join when pulling entries
out of worktree
- [x] Use this new join to implement the project panel and outline
panel.
- [x] Synchronize git statuses over the wire for collab and remote dev
(use the existing `worktree_repository_statuses` table, adjust as
needed)
- [x] Only send changed statuses to collab

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.com>
Co-authored-by: Nathan <nathan@zed.dev>
2025-01-04 01:00:16 +00:00
Jason Lee
72057e5716 cli: Fix support for older macOS versions (#22515)
Close #22419

Release Notes:

- Fixed `zed` cli crash by `ScreenCaptureKit` library not loaded error
on macOS lower versions.

<img width="843" alt="image"
src="https://github.com/user-attachments/assets/9e0b615e-933f-4808-bf20-3e37e9e8bc6d"
/>

--- 

The main reason is the `cli` depends on `release_channel`, and it
depends on `gpui`.

```
$ cargo tree -p cli
├── release_channel v0.1.0 (/Users/jason/github/zed/crates/release_channel)
│   └── gpui v0.1.0 (/Users/jason/github/zed/crates/gpui)
│       ├── anyhow v1.0.95
│       ├── async-task v4.7.1
│       ├── block v0.1.6
│       ├── cocoa v0.26.0
```
2025-01-04 00:42:37 +00:00
tims
e25789893d linux: Fix issue where relative symlinks were not being watched using fs watch (#22608)
Closes #22607

Symlinks can be absolute or relative. When using
[stow](https://www.gnu.org/software/stow/) to manage dotfiles, it
creates relative symlinks to the target files.

For example:  

- Original file:  `/home/tims/dotfiles/zed/setting.json`  
- Symlink path: `/home/tims/.config/zed/setting.json`  
- Target path (relative to symlink): `../../dotfiles/zed/setting.json`  

The issue is that you can’t watch the symlink path because it’s relative
and doesn't include the base path it is relative to. This PR fixes that
by converting relative symlink paths to absolute paths.

- Absolute path (after parent join):
`/home/tims/.config/zed/../../dotfiles/zed/setting.json` (This works)
- Canonicalized path (from absolute path):
`/home/tims/dotfiles/zed/setting.json` (This works too, just more
cleaner)

Release Notes:

- Fix issue where items on the Welcome page could not be toggled on
Linux when using Stow to manage dotfiles
2025-01-04 00:12:20 +00:00
tims
b46b261f11 linux: Fix process PID to window mapping for X11 (#22348)
Closes #22326

This PR adds process PID information to window created by X11, so that
window manager can identify which process this window belongs to.
Without this property, the window manager would have no reliable way to
know which process created this window.

In original issue, `robotgo` throws error on `x, y, w, h :=
robotgo.GetBounds(pid)` this method. If we go deeper into the source
code of `robotgo`, it calls `GetXidFromPid` which goes through all
windows, and tries to check for provided pid. Hence, when it tries to do
that for Zed, it fails and returns `0, err` to caller.

```go
// Robotgo source code trying to look through all windows and query pid

// GetXidFromPid get the xid from pid
func GetXidFromPid(xu *xgbutil.XUtil, pid int) (xproto.Window, error) {
	windows, err := ewmh.ClientListGet(xu)
	if err != nil {
		return 0, err
	}

	for _, window := range windows {
		wmPid, err := ewmh.WmPidGet(xu, window)
		if err != nil {
			return 0, err
		}

		if uint(pid) == wmPid {
			return window, nil
		}
	}

	return 0, errors.New("failed to find a window with a matching pid.")
}
```

Querying for pid for active Zed window:

Before:
```sh
tims@lemon ~/w/go-repro [127]> xprop -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4e00002
tims@lemon ~/w/go-repro> xprop -id 0x4e00002 _NET_WM_PID
_NET_WM_PID:  not found.
```

After:
```sh
tims@lemon ~/w/go-repro> xprop -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4e00002
tims@lemon ~/w/go-repro> xprop -id 0x4e00002 _NET_WM_PID
_NET_WM_PID(CARDINAL) = 103548
tims@lemon ~/w/go-repro>
```

Correct zed process PID (below) assosiated with zed window (shown
above):

![image](https://github.com/user-attachments/assets/8b40128b-addb-4c88-944e-b1d26b908bf5)

Release Notes:

- Fix `robotgo` failing when Zed window is open on Linux
2025-01-04 00:10:36 +00:00
tims
71a0eb3b13 windows: Fix cursor style not changing when hovering over items in the title bar (#22580)
Closes #22578

Currently, the `hovered` boolean in the window state is only updated by
the `WM_MOUSELEAVE` event, which fires when the mouse cursor leaves the
window's working area. This means that when the user moves the cursor
from the window to the title bar, `hovered` is set to `false`. Later in
the code, this flag is used to determine the cursor style and check if
the cursor is over the correct window.

The `hovered` boolean should remain active even when the mouse is over
non-client items, such as the title bar or window borders. This PR fixes
that by using `WM_NCMOUSELEAVE` event, which is triggered when the mouse
leaves non-client items. This event is used to update the `hovered`
boolean accordingly.

Now, `hovered` is `true` when the mouse is over the window's working
area, as well as non-client areas like the title bar.

More context:

- Existing: `dwFlags: TME_LEAVE` tracks window area mouse leaves, which
is used in `handle_mouse_move_msg` func.
- New: `dwFlags: TME_LEAVE | TME_NONCLIENT` tracks non-client mouse
leaves, which is used in `handle_nc_mouse_move_msg` func.

Preview:


https://github.com/user-attachments/assets/b319303f-81b9-45cb-bf0c-535a59b96561

Release Notes:

- Fix cursor style not changing on hover over items in the title bar on
Windows
2025-01-04 00:01:29 +00:00
Marshall Bowers
dd75f85ecf elm: Extract to zed-extensions/elm repository (#22637)
This PR extracts the Elm extension to the
[zed-extensions/elm](https://github.com/zed-extensions/elm) repository.

Release Notes:

- N/A
2025-01-04 00:00:45 +00:00
Roy Williams
b1a6e2427f anthropic: Allow specifying additional beta headers for custom models (#20551)
Release Notes:

- Added the ability to specify additional beta headers for custom
Anthropic models.

---------

Co-authored-by: David Soria Parra <167242713+dsp-ant@users.noreply.github.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 23:46:32 +00:00
Marshall Bowers
bbe6bf9caf assistant: Remove unused AssistantSettings::update_file (#22636)
As a follow-up to #21672, this PR removes the
`AssistantSettings::update_file` method, as it was no longer used
anywhere.

Release Notes:

- N/A
2025-01-03 23:34:15 +00:00
Torrat
53cfb578e8 python: Adjust binary path based on OS (#22587)
Closes #ISSUE

- #21452 

Describe the bug / provide steps to reproduce it

Language server error: pylsp

failed to spawn command. path:
"C:\Users\AppData\Local\Zed\languages\pylsp\pylsp-venv\bin\pylsp",
working directory: "D:\Coding\Python", args: []
-- stderr--

Environment
- Windows 11
- python

Release Notes:

- Windows: Fixed the path building used to run `pip` commands in the
venv generated on Windows 11.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 22:56:31 +00:00
Drew Ridley
e6fe12d0e1 assistant: Remove outdated settings update path (#21672)
Removed a settings update that should have been removed in the 0.148.0
release.

I am not sure if there is a tracking issue, but I identified this check
for outdated settings that should not be needed anymore. I investigated
a bit and did not find any conflicts or UB as a result of removing this
code.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 22:52:08 +00:00
Marshall Bowers
04cf19d49a Upgrade strum to v0.26 (#22633)
This PR upgrades `strum` to v0.26.

Supersedes #21896.

Release Notes:

- N/A
2025-01-03 22:23:06 +00:00
Marshall Bowers
4bd5f0d355 racket: Extract to zed-extensions/racket repository (#22630)
This PR extracts the Racket extension to the
[zed-extensions/racket](https://github.com/zed-extensions/racket)
repository.

Release Notes:

- N/A
2025-01-03 22:04:50 +00:00
Marshall Bowers
fdbf3d0f25 clojure: Extract to zed-extensions/clojure repository (#22628)
This PR extracts the Clojure extension to the
[zed-extensions/clojure](https://github.com/zed-extensions/clojure)
repository.

Release Notes:

- N/A
2025-01-03 21:14:53 +00:00
Nils Koch
a1ef1d3f76 Add syntax highlighting for character literals in Haskell, PureScript, and Zig (#22609)
Closes #22480

Release Notes:

- N/A

| Before | After |
|----------|----------|
| <img width="344" alt="before"
src="https://github.com/user-attachments/assets/37f8daf7-c9a0-4259-8c03-bd1a4479abca"
/> | <img width="344" alt="after"
src="https://github.com/user-attachments/assets/0f7e4429-e48b-4b32-9797-a0da8487e23e"
/> |

Zig, Haskel, and PureScript define a character caputure name in
`highlights.scm`, but we did not define a color for that capture name in
the themes. The new character color is the same as the string color in
all themes.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 20:51:43 +00:00
Marshall Bowers
04518b11bc language_model_selector: Refresh the models when the providers change (#22624)
This PR fixes an issue introduced in #21939 where the list of models in
the language model selector could be outdated.

Since we're no longer recreating the picker each render, we now need to
make sure we are updating the list of models accordingly when there are
changes to the language model providers.

I noticed it specifically in Assistant1.

Release Notes:

- Fixed a staleness issue with the language model selector.
2025-01-03 19:38:08 +00:00
saahityaedams
e4eef725de Add support for Claude 3.5 Haiku model (#22323)
Partly Closes #22185

Release Notes:

- Added support for the Claude 3.5 Haiku model.

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 18:49:29 +00:00
Marshall Bowers
7c7eb98e64 astro: Extract to zed-extensions/astro repository (#22623)
This PR extracts the Astro extension to the
[zed-extensions/astro](https://github.com/zed-extensions/astro)
repository.

Release Notes:

- N/A
2025-01-03 18:34:33 +00:00
Marshall Bowers
7826d79a40 markdown: Make push_div work with Stateful<Div>s (#22622)
This PR updates the `push_div` method in the `MarkdownElementBuilder` to
support taking in a `Stateful<Div>`.

This is some groundwork for supporting horizontal scrolling in Markdown
code blocks.

Release Notes:

- N/A
2025-01-03 18:24:04 +00:00
tims
bb24c085be linux: Add keyboard shortcuts for menus (#22074)
Closes #19837

This PR is a continuation of [linux: Implement
Menus](https://github.com/zed-industries/zed/pull/21873) and should only
be reviewed once the existing PR is merged.

I created this as a separate PR as the existing PR was already reviewed
but is yet to merge, and also it was my initial plan to do it in
separate parts because of the scope of it. This will also help reviewing
code faster.

This PR adds two new types of keyboard shortcuts to make menu navigation
easier:

1. `Alt + Z` for Zed, `Alt + F` for File, `Alt + S` for Selection, and
so on to open a specific menu with this combination. This mimics VSCode
and IntelliJ.

2. `Arrow Left/Right` when any menu is open. This will trigger the
current menu to close, and the previous/next to open respectively. First
and last element cycling is handled.

`Arrow Up/Down` to navigate menu entries is already there in existing
work.



https://github.com/user-attachments/assets/976aea48-4e20-4c19-850d-4d205a4bead2


Release Notes:

- Added keyboard navigation for menus on Linux (left/right). If you wish
to open menus with keyboard shortcuts add the following to your user
keymap:
    ```json
      {
        "context": "Workspace",
        "bindings": {
          "alt-z": ["app_menu::OpenApplicationMenu", "Zed"],
          "alt-f": ["app_menu::OpenApplicationMenu", "File"],
          "alt-e": ["app_menu::OpenApplicationMenu", "Edit"],
          "alt-s": ["app_menu::OpenApplicationMenu", "Selection"],
          "alt-v": ["app_menu::OpenApplicationMenu", "View"],
          "alt-g": ["app_menu::OpenApplicationMenu", "Go"],
          "alt-w": ["app_menu::OpenApplicationMenu", "Window"],
          "alt-h": ["app_menu::OpenApplicationMenu", "Help"]
        }
      }
    ```

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-01-03 18:19:24 +00:00
Cole Miller
11ec25aedb Support diagnostic navigation in multibuffers (#22620)
cc @nathansobo 

Release Notes:

- Support diagnostic navigation in multibuffers
2025-01-03 18:07:56 +00:00
Marshall Bowers
39af06085a assistant2: Add an example thread to showcase long lines of code (#22621)
This PR adds another example thread to showcase a response with long
lines of code.

This example will be helpful when working to make the code blocks scroll
horizontally instead of wrapping.

Release Notes:

- N/A
2025-01-03 17:48:04 +00:00
Marshall Bowers
a49e394e51 assistant2: Remove single-letter variable name (#22618)
This PR removes a single-letter variable name in place of a full one,
for readability.

Release Notes:

- N/A
2025-01-03 17:04:07 +00:00
Peter Tripp
e5c3d5d626 Emacs keybinding improvements (2025-01-02) (#22590)
Various improvements to the emacs compatibility keybindings.

- See also: https://github.com/zed-industries/zed/issues/4856

Release Notes:

- Improvements to emacs keybindings:
- Better support for running emacs inside Zed terminal (e.g. `ctrl-x
ctrl-c` will quit emacs in terminal not zed)
  - `alt-^` Join Lines
  - `ctrl-/` Undo
  - `alt-.` GotoDefinition and `alt-,` GoBack
  - `ctrl-x h` SelectAll
  - `alt-<` / `alt->` Goto End/Beginning of Buffer
  - `ctrl-g` as Menu::cancel
2025-01-03 16:48:41 +00:00
Marshall Bowers
1aba459a0a csharp: Bump to v0.1.0 (#22617)
This PR bumps the C# extension to v0.1.0.

Changes:

- https://github.com/zed-industries/zed/pull/15175
- https://github.com/zed-industries/zed/pull/15885
- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/18869
- https://github.com/zed-industries/zed/pull/22599

Release Notes:

- N/A
2025-01-03 16:47:19 +00:00
Hossein Khosravi
8c253af451 linux: Fix regex patterns for detecting Fedora in script/linux (#22611)
In `script/linux` file, in order to install build dependencies we check
ID and VERSION_ID fields of `/etc/os-release` file for installing
os-specific packages. The regex patterns for those fields are wrong
because there's no `"` character after `ID=` or `VERSION_ID=`. This
causes `grep` to fail.
So I extended the pattern by adding `"?` after each `"` character to
bypass the cause of failure.

Release Notes:

- N/A

Signed-off-by: thehxdev <hossein.khosravi.ce@gmail.com>
2025-01-03 16:28:07 +00:00
renovate[bot]
3c207209cb Update Rust crate sea-orm to v1.1.3 (#22554)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v1.1.3`](https://redirect.github.com/SeaQL/sea-orm/blob/HEAD/CHANGELOG.md#113---2024-12-24)

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

##### New Features

- \[sea-orm-codegen] register seaography entity modules & active
enums[https://github.com/SeaQL/sea-orm/pull/2403](https://redirect.github.com/SeaQL/sea-orm/pull/2403)3

```rust
pub mod prelude;

pub mod sea_orm_active_enums;

pub mod baker;
pub mod bakery;
pub mod cake;
pub mod cakes_bakers;
pub mod customer;
pub mod lineitem;
pub mod order;

seaography::register_entity_modules!([
    baker,
    bakery,
    cake,
    cakes_bakers,
    customer,
    lineitem,
    order,
]);

seaography::register_active_enums!([
    sea_orm_active_enums::Tea,
    sea_orm_active_enums::Color,
]);
```

##### Enhancements

- Insert many allow active models to have different column set
[https://github.com/SeaQL/sea-orm/pull/2433](https://redirect.github.com/SeaQL/sea-orm/pull/2433)

```rust
// this previously panics
let apple = cake_filling::ActiveModel {
    cake_id: ActiveValue::set(2),
    filling_id: ActiveValue::NotSet,
};
let orange = cake_filling::ActiveModel {
    cake_id: ActiveValue::NotSet,
    filling_id: ActiveValue::set(3),
};
assert_eq!(
    Insert::<cake_filling::ActiveModel>::new()
        .add_many([apple, orange])
        .build(DbBackend::Postgres)
        .to_string(),
    r#"INSERT INTO "cake_filling" ("cake_id", "filling_id") VALUES (2, NULL), (NULL, 3)"#,
);
```

- \[sea-orm-cli] Added `MIGRATION_DIR` environment variable
[https://github.com/SeaQL/sea-orm/pull/2419](https://redirect.github.com/SeaQL/sea-orm/pull/2419)
- Added `ColumnDef::is_unique`
[https://github.com/SeaQL/sea-orm/pull/2401](https://redirect.github.com/SeaQL/sea-orm/pull/2401)
- Postgres: quote schema in `search_path`
[https://github.com/SeaQL/sea-orm/pull/2436](https://redirect.github.com/SeaQL/sea-orm/pull/2436)

##### Bug Fixes

- MySQL: fix transaction isolation level not respected when used with
access mode
[https://github.com/SeaQL/sea-orm/pull/2450](https://redirect.github.com/SeaQL/sea-orm/pull/2450)

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS44NS4wIiwidXBkYXRlZEluVmVyIjoiMzkuODUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 16:22:21 +00:00
Peter Tripp
663f5244ca Deploy script: Specify remote for new Preview branch (#22614)
Set the git remote tracking branch when the new Preview branch is
created by `script/bump-zed-minor-versions`. This only impacts the local
git branch configuration of the user who runs this script.

Release Notes:

- N/A
2025-01-03 15:25:15 +00:00
Agus Zubiaga
0599f0fcb6 Fix vertical alignment when jumping from multibuffers (#22613)
Clicking buffer headers and line numbers would sometimes take you to a
disorienting scroll position. This PR improves that so the destination
line is roughly at the same Y position as it appeared in the
multibuffer.



https://github.com/user-attachments/assets/3ad71537-cf26-4136-948f-c5a96df57178


**Note**: The alignment won't always be perfect because the multibuffer
and target buffer might start at a different absolute Y position
(because of open search, breadcrumbs, etc). I wanted to compensate for
that, but that requires a fundamental change that I'd prefer to make
separately.

Release Notes:

- Fix vertical alignment when jumping from multibuffers
2025-01-03 14:37:00 +00:00
Finn Evers
6e2b6258b1 csharp: Add bracket indents (#22599)
This PR adds an initial `indents.scm` to the C#-extension in order to
support auto-indentation with brackets.

Release Notes:

- N/A
2025-01-03 13:55:25 +00:00
Marshall Bowers
82492d74a8 assistant2: Tweak "Add Context" placeholder (#22596)
This PR tweaks the "Add Context" placeholder, as the text appeared to be
vertically misaligned.

#### Before

<img width="215" alt="Screenshot 2025-01-02 at 6 03 06 PM"
src="https://github.com/user-attachments/assets/1bac0deb-bd90-4ff3-b681-ee884cbe831d"
/>

#### After

<img width="189" alt="Screenshot 2025-01-02 at 6 03 20 PM"
src="https://github.com/user-attachments/assets/c9673fb0-11d6-42ac-8fec-9af269dfc73c"
/>


Release Notes:

- N/A
2025-01-02 23:18:32 +00:00
renovate[bot]
16ead69052 Update Rust crate serde_json to v1.0.134 (#22555)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

- Add `RawValue` associated constants for literal `null`, `true`,
`false`
([#&#8203;1221](https://redirect.github.com/serde-rs/json/issues/1221),
thanks [@&#8203;bheylin](https://redirect.github.com/bheylin))

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS44NS4wIiwidXBkYXRlZEluVmVyIjoiMzkuODUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 23:08:47 +00:00
Marshall Bowers
a53be7b4be collab_ui: Show the chat panel icon when the chat panel is active (#22593)
This PR is a follow-up to #22200 that makes it so the chat panel icon is
visible when the chat panel is active, even if not in a call (when using
the `when_in_call` setting).

Release Notes:

- N/A
2025-01-02 22:53:34 +00:00
renovate[bot]
a79def005d Update Rust crate quote to v1.0.38 (#22553)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [quote](https://redirect.github.com/dtolnay/quote) | dependencies |
patch | `1.0.37` -> `1.0.38` |

---

### Release Notes

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

###
[`v1.0.38`](https://redirect.github.com/dtolnay/quote/releases/tag/1.0.38)

[Compare
Source](https://redirect.github.com/dtolnay/quote/compare/1.0.37...1.0.38)

- Support interpolating arrays inside of arrays using a repetition
([#&#8203;286](https://redirect.github.com/dtolnay/quote/issues/286))

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS44NS4wIiwidXBkYXRlZEluVmVyIjoiMzkuODUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 22:45:55 +00:00
Michael Sloan
2d431e9b51 Improve truncate efficiency and fix OBOE in truncate_and_remove_front (#22591)
* Skip walking string for truncate when byte len is <= char limit

* Fix `truncate_and_remove_front` returning string that is `max_chars +
1` in length. Now more consistent with `truncate_and_trailoff` behavior.

* Fix `truncate_and_remove_front` adding ellipsis when max_chars == char
length

Release Notes:

- N/A
2025-01-02 22:35:36 +00:00
Michael Sloan
f9df8c1729 Use the same label for both string and bag in tasks modal fuzzy match (#22022)
#22592 tracks properly doing fuzzy match within the full label

Release Notes:

- N/A
2025-01-02 22:11:14 +00:00
Justin Su
898064e6b4 Fix a typo in default.json (#22589)
Release Notes:

- N/A
2025-01-02 21:44:51 +00:00
Josef Zoller
8cb397cf6c project_panel: Open rename file editor if pasted file was disambiguated (#19975)
Closes #19974.

When a file is pasted in the project panel at a location where a file
with that name already exists, the new file's name is disambiguated by
appending " copy" at the end. This happens on the paste and the
duplicate actions, as well as when Alt-dragging files.
With this PR, this will now open the file rename editor with the
disambiguator pre-selected.

Open question:
With this PR's current implementation, this won't always work when
pasting multiple files at once. In this case, the file rename editor
only opens for the last pasted file, if that file was disambiguated. If
only other files were disambiguated instead, it won't open.
This roughly mimics the previous paste behaviour, namely that only the
last pasted file was selected.

I see two options here: If multiple files were pasted and some of them
were disambiguated, we could select and open the rename editor for the
last file that was actually disambiguated (easy), or we could open a
kind of multi-editor for all files (hard, but maybe a multi-rename
editor could actually be interesting in general...).

Release Notes:

- Open rename file editor if pasted file was disambiguated
2025-01-02 21:33:51 +00:00
Agus Zubiaga
374c298bd5 assistant2: Suggest current thread in inline assistant (#22586)
Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.com>
2025-01-02 20:36:57 +00:00
Kirill Bulatov
0e75ca8603 Fix tooltips too eager to disappear when there's a gap between the tooltip source and the tooltip itself (#22583)
Follow-up of https://github.com/zed-industries/zed/pull/22548

Release Notes:

- N/A

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-01-02 19:45:47 +00:00
Marshall Bowers
2c2ca9e370 assistant2: Wire up the directory context picker (#22582)
This PR wires up the functionality of the directory context picker.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
2025-01-02 19:42:59 +00:00
Peter Tripp
3cf5ab16a9 racket: Bump Extension to v0.0.2 (#22584)
Includes:
- https://github.com/zed-industries/zed/pull/18728
2025-01-02 14:39:18 -05:00
Peter Tripp
53e1ab3c64 elixir: Bump to v0.1.3 (#22585)
Includes:
- https://github.com/zed-industries/zed/pull/22579
2025-01-02 14:38:51 -05:00
Peter Tripp
d0c4c0c240 elixir: Capture identifiers as @variable (#22579)
- Closes: https://github.com/zed-industries/zed/issues/19382

Before/After
<img width="329" alt="Screenshot 2025-01-02 at 13 08 46"
src="https://github.com/user-attachments/assets/ede36fd3-ed55-4436-912c-bb8b7ad9b0cd"
/><img width="329" alt="Screenshot 2025-01-02 at 13 08 18"
src="https://github.com/user-attachments/assets/eb784bdc-fd13-487d-b6ed-c960d8020d9b"
/>


Release Notes:

- N/A
2025-01-02 18:27:12 +00:00
Marshall Bowers
20c0d72fe4 ci: Make docs-only check a no-op in the merge queue (#22576)
This PR makes the docs-only check a no-op that defaults to `false` when
running in the merge queue.

I noticed that the current check did not work properly in the merge
queue, resulting in it always assuming a change was docs-only and not
running the requisite CI jobs.

Release Notes:

- N/A
2025-01-02 18:14:17 +00:00
renovate[bot]
d5f058d6e2 Update swatinem/rust-cache digest to f0deed1 (#22552)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [swatinem/rust-cache](https://redirect.github.com/swatinem/rust-cache)
| action | digest | `82a92a6` -> `f0deed1` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzOS44NS4wIiwidXBkYXRlZEluVmVyIjoiMzkuODUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 17:41:35 +00:00
Bennet Bo Fenner
fbef8c2b3b docs: Update scrollbar > diagnostics setting section (#22574)
This updates the docs after the setting was changed in #22364 

Release Notes:

- N/A
2025-01-02 17:27:17 +00:00
Bennet Bo Fenner
b009e72121 terminal: Support clicking on "file://" URLs with line numbers (#22559)
Closes #10325

Release Notes:

- Fixed an issue inside the integrated terminal where clicking on URLs
that started with `file://` would sometimes not work when the path
included a line number (e.g. `file:///Users/someuser/lorem.txt:221:22`)
2025-01-02 17:24:55 +00:00
Aaron Feickert
f55a3629b0 Add fine-grained control for scrollbar diagnostics (#22364)
This PR updates the scrollbar diagnostic setting to provide fine-grained
control over which indicators to show, based on severity level. This
allows the user to hide lower-severity diagnostics that can otherwise
clutter the scrollbar (for example, unused or disabled code).

The options are set such that the existing boolean setting has the same
effect: when `true` all diagnostics are shown, and when `false` no
diagnostics are shown.

Closes #22296.

Release Notes:

- Added fine-grained control of scrollbar diagnostic indicators.
2025-01-02 17:03:00 +00:00
renovate[bot]
3ac0aef211 Update Rust crate unicase to v2.8.1 (#22558)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [unicase](https://redirect.github.com/seanmonstar/unicase) |
dependencies | patch | `2.8.0` -> `2.8.1` |
| [unicase](https://redirect.github.com/seanmonstar/unicase) |
workspace.dependencies | patch | `2.8.0` -> `2.8.1` |

---

### Release Notes

<details>
<summary>seanmonstar/unicase (unicase)</summary>

###
[`v2.8.1`](https://redirect.github.com/seanmonstar/unicase/releases/tag/v2.8.1)

[Compare
Source](https://redirect.github.com/seanmonstar/unicase/compare/v2.8.0...v2.8.1)

##### What's Changed

- fix: hash for Unicode will call write_u8 like Ascii does by
[@&#8203;seanmonstar](https://redirect.github.com/seanmonstar) in
[https://github.com/seanmonstar/unicase/pull/73](https://redirect.github.com/seanmonstar/unicase/pull/73)
- fix: provide prefix-freedom in Hash impls by
[@&#8203;seanmonstar](https://redirect.github.com/seanmonstar) in
[https://github.com/seanmonstar/unicase/pull/74](https://redirect.github.com/seanmonstar/unicase/pull/74)

**Full Changelog**:
https://github.com/seanmonstar/unicase/compare/v2.8.0...v2.8.1

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 14:55:59 +00:00
Agus Zubiaga
59b5b9af90 assistant2: Suggest current file as context (#22526)
Suggest adding the current file as context in the new assistant panel.



https://github.com/user-attachments/assets/62bc267b-3dfe-4a3b-a6af-c89af2c779a8


Note: This doesn't include suggesting the current thread in the inline
assistant.

Release Notes:

- N/A
2025-01-02 13:33:47 +00:00
Piotr Osiewicz
b3e36c93b4 deps: Bump indexmap to 2.0 (#22567)
Closes #ISSUE

Release Notes:

- N/A
2025-01-02 12:07:46 +00:00
Piotr Osiewicz
5447821715 gpui/perf: Use SharedString on API boundary of line layout (#22566)
This commit is all about strings, not about line layout at all. When
laying out text, we use a line layout cache to avoid roundtrips to
system layout engine where possible. This makes it so that we might end
up not needing an owned version of text to insert into the cache, as we
might get a cached version.

The API boundary of line layout accepted text to be laid out as &str. It
then performed cache lookup (which didn't require having an owned
version) and only resorted to making an owned version when needed. As it
turned out though, exact cache hits are quite rare and we end up needing
owned version more often than not. The callers of line layout either
dealt with SharedStrings or owned Strings. Due to coercing them into
&str, we were ~always copying text into a new string (unless there was a
same-frame-hit). This is a bit wasteful, thus this PR generifies the API
a bit to make it easier to reuse existing string allocations if there
are any.

Benchmark scenario: scrolling down page-by-page through editor_tests (I
ran the same scenario twice):

![1](https://github.com/user-attachments/assets/8cd09692-2699-41d9-b211-83554d93902f)

![2](https://github.com/user-attachments/assets/d11f7c22-2315-4261-8189-2356baf5d2f7)


Release Notes:

- N/A
2025-01-02 11:06:01 +00:00
Michael Sloan
665717da9a Fuzzy match performance improvements redo (#22561)
Release Notes:

- N/A
2025-01-02 05:31:06 +00:00
Danilo Leal
28d1d2d939 assistant2: Add link styles for thread messages (#22560)
<img width="700" alt="Screenshot 2025-01-02 at 1 52 30 AM"
src="https://github.com/user-attachments/assets/8d2308c8-cdea-421f-b9ff-7893479dba3c"
/>


Release Notes:

- N/A
2025-01-02 05:09:33 +00:00
Hayashi Mikihiro
44af405fb0 pane: Turn off preview mode when pinning a tab (#22501)
I opened the tab in preview mode, pinned it, then I opened another file,
but its tab unexpectedly closed.


https://github.com/user-attachments/assets/b857382e-f0ad-4d5a-9036-19de01663c97

Pinning a tab now turns off preview mode.



https://github.com/user-attachments/assets/e34b7c7f-452b-4f36-99c1-e0c68429225c


Release Notes:

- Pinning a preview tab will now turn off preview mode

---------

Signed-off-by: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2025-01-02 04:09:35 +00:00
Kirill Bulatov
c11bde7bf4 Remove stuck tooltips (#22548)
Closes https://github.com/zed-industries/zed/issues/21657

Follow-up of https://github.com/zed-industries/zed/pull/22488
Previous PR broke git blame tooltips, which are expected to be open when
hovered, even if the mouse cursor is moved away from the actual blame
entry that caused the tooltip to appear.

Current version moves the invalidation logic into `prepaint_tooltip`,
where the new data about the tooltip origin is used to ensure we
invalidate only tooltips that have no mouse cursor in either origin
bounds or tooltip bounds (if it's hoverable).


Release Notes:

- Fixed tooltips getting stuck
2025-01-01 18:47:10 +00:00
Peter Tripp
0d423a7b37 Bump Zed to v0.169 (#22547)
Release Notes:

-N/A
2025-01-01 12:31:37 -05:00
Danilo Leal
a6f95a14b7 docs: Improve the Getting Started page (#22545)
- Little tweaks to wording and punctuation
- Remove redundancy on the Configuration and Key binding sections
- Ensure key bindings for the sections mentioned above appear

Release Notes:

- N/A
2025-01-01 17:19:10 +00:00
Peter Tripp
642dab82e5 Fix for extension search crash (revert #22524; revert #22525) (#22543)
Revert "Improve fuzzy match performance and fix corner case that omits
results (#22524)"
This reverts commit 6ef5d8f748.

Revert "Check cancel in multithreaded fuzzy matching (#22525)"
This reverts commit 51ac2d3667.

Fuzzy matching implemented in:
- https://github.com/zed-industries/zed/pull/22524
- https://github.com/zed-industries/zed/pull/22525

Caused a panic in the extension store search:
- Closes: https://github.com/zed-industries/zed/issues/22541

cc: @mgsloan 

Release Notes:

- N/A
2025-01-01 17:04:37 +00:00
Kirill Bulatov
38938ece49 Revert "Invalidate tooltips when mouse leaves element's hitbox (#22488)" (#22542)
This reverts commit 344284e013.

That change broke git blame tooltips, as Zed should also show tooltips
which are hovered, even though the mouse had left the origin element's
bounds.

Release Notes:

- N/A
2025-01-01 16:56:58 +00:00
Peter Tripp
8e12c679fc Revert "Have Zed cli output logs path to stderr" (#22540)
Removes noisy log location stderr output on every `zed` cli invocation.

Reverts zed-industries/zed#22509

Release Notes:

- N/A
2025-01-01 16:21:08 +00:00
Rusydy
4ce1ccfc40 Add support for block comments in Markdown configuration (#22352)
This pull request includes a small change to the
`crates/languages/src/markdown/config.toml` file. The change adds block
comment syntax for Markdown files.

*
[`crates/languages/src/markdown/config.toml`](diffhunk://#diff-4cf73d9af0f11f2ac8929bd8113ee76aa382dc96a731f18510c09fc3d0db1f9cR5):
Added block comment syntax `<!-- ` and ` -->` for Markdown files.

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

I have been testing it with the following:

1. create a simple markdown file containing following words

```markdown
good morning - English
Dzień dobry - polish
おはよう - japanese
صباح الخير - arabic
早安 - Chineses (Traditional)
```

2. comment using `cmd+/` and see if there are any errors



Release Notes:

- Added block comment syntax `<!-- ` and ` -->` for Markdown files.
2025-01-01 15:46:38 +00:00
Danilo Leal
eedbede939 docs: Ensure "Remote Projects" dialog keybinding is displayed (#22530)
Closes https://github.com/zed-industries/zed/issues/22429

Release Notes:

- N/A
2025-01-01 00:40:38 +00:00
Danilo Leal
30668589ed Improve project search design details (#22529)
Closes https://github.com/zed-industries/zed/issues/22469 — and, aside
from fixing that issue also took the opportunity to make the
`file_search` icon dimensions feel better in comparison the other icons
in the Project Search area.

Release Notes:

- N/A
2025-01-01 00:09:36 +00:00
Michael Sloan
51ac2d3667 Check cancel in multithreaded fuzzy matching (#22525)
For both the strings and paths multithreaded matching it would still
aggregate the response even though it is unneeded. It now checks cancel.

In the paths matcher, cancel is now checked within the loop, since it
was calling `match_candidates` even though no further results would be
computed.

Release Notes:

- N/A
2024-12-31 22:37:41 +00:00
557 changed files with 26151 additions and 32368 deletions

View File

@@ -35,7 +35,12 @@ jobs:
- name: Check for non-docs changes
id: check_changes
run: |
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
if [ "${{ github.event_name }}" == "merge_group" ]; then
# When we're running in a merge queue, never assume that the changes
# are docs-only, as there could be other PRs in the group that
# contain non-docs changes.
echo "docs_only=false" >> $GITHUB_OUTPUT
elif git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
echo "docs_only=false" >> $GITHUB_OUTPUT
else
echo "docs_only=true" >> $GITHUB_OUTPUT
@@ -175,7 +180,7 @@ jobs:
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -216,7 +221,7 @@ jobs:
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -247,7 +252,7 @@ jobs:
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"

View File

@@ -21,7 +21,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"

194
Cargo.lock generated
View File

@@ -245,7 +245,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.25.0",
"strum",
"thiserror 1.0.69",
"util",
]
@@ -435,7 +435,7 @@ dependencies = [
"similar",
"smallvec",
"smol",
"strum 0.25.0",
"strum",
"telemetry_events",
"terminal",
"terminal_view",
@@ -494,7 +494,6 @@ dependencies = [
"project",
"proto",
"rand 0.8.5",
"release_channel",
"rope",
"schemars",
"serde",
@@ -2152,7 +2151,7 @@ dependencies = [
"cap-primitives",
"cap-std",
"io-lifetimes 2.0.4",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -2180,7 +2179,7 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
"winx",
]
@@ -2275,7 +2274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [
"heck 0.4.1",
"indexmap 2.7.0",
"indexmap",
"log",
"proc-macro2",
"quote",
@@ -2716,7 +2715,7 @@ dependencies = [
"settings",
"sha2",
"sqlx",
"strum 0.25.0",
"strum",
"subtle",
"supermaven_api",
"telemetry_events",
@@ -2785,7 +2784,8 @@ dependencies = [
name = "collections"
version = "0.1.0"
dependencies = [
"rustc-hash 1.1.0",
"indexmap",
"rustc-hash 2.1.0",
]
[[package]]
@@ -2994,7 +2994,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"strum 0.25.0",
"strum",
"task",
"theme",
"ui",
@@ -4145,7 +4145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -4812,7 +4812,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
dependencies = [
"io-lifetimes 2.0.4",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -5102,7 +5102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
dependencies = [
"fallible-iterator",
"indexmap 2.7.0",
"indexmap",
"stable_deref_trait",
]
@@ -5194,6 +5194,7 @@ dependencies = [
"util",
"windows 0.58.0",
"workspace",
"worktree",
]
[[package]]
@@ -5274,7 +5275,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.25.0",
"strum",
]
[[package]]
@@ -5374,7 +5375,7 @@ dependencies = [
"slotmap",
"smallvec",
"smol",
"strum 0.25.0",
"strum",
"sum_tree",
"taffy",
"thiserror 1.0.69",
@@ -5434,7 +5435,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.7.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
@@ -5453,7 +5454,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.2.0",
"indexmap 2.7.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
@@ -6232,27 +6233,16 @@ dependencies = [
"heed",
"html_to_markdown",
"http_client",
"indexmap 1.9.3",
"indexmap",
"indoc",
"parking_lot",
"paths",
"pretty_assertions",
"serde",
"strum 0.25.0",
"strum",
"util",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.7.0"
@@ -6388,7 +6378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
dependencies = [
"io-lifetimes 2.0.4",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -6775,7 +6765,7 @@ dependencies = [
"serde",
"serde_json",
"smol",
"strum 0.25.0",
"strum",
"ui",
"util",
]
@@ -6821,7 +6811,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"strum 0.25.0",
"strum",
"telemetry_events",
"theme",
"thiserror 1.0.69",
@@ -6999,7 +6989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -7741,7 +7731,7 @@ dependencies = [
"cfg_aliases 0.1.1",
"codespan-reporting",
"hexf-parse",
"indexmap 2.7.0",
"indexmap",
"log",
"rustc-hash 1.1.0",
"spirv",
@@ -8373,7 +8363,7 @@ checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"crc32fast",
"hashbrown 0.15.2",
"indexmap 2.7.0",
"indexmap",
"memchr",
]
@@ -8478,7 +8468,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.25.0",
"strum",
]
[[package]]
@@ -9328,7 +9318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.7.0",
"indexmap",
]
[[package]]
@@ -9511,7 +9501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [
"base64 0.22.1",
"indexmap 2.7.0",
"indexmap",
"quick-xml 0.32.0",
"serde",
"time",
@@ -9829,7 +9819,7 @@ dependencies = [
"file_icons",
"git",
"gpui",
"indexmap 1.9.3",
"indexmap",
"language",
"menu",
"pretty_assertions",
@@ -9933,7 +9923,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes 1.9.0",
"heck 0.5.0",
"itertools 0.12.1",
"itertools 0.10.5",
"log",
"multimap 0.10.0",
"once_cell",
@@ -9966,7 +9956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [
"anyhow",
"itertools 0.12.1",
"itertools 0.10.5",
"proc-macro2",
"quote",
"syn 2.0.90",
@@ -10148,14 +10138,14 @@ dependencies = [
"once_cell",
"socket2 0.5.8",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@@ -10892,7 +10882,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"strum 0.25.0",
"strum",
"tracing",
"util",
"zstd",
@@ -11048,7 +11038,7 @@ dependencies = [
"libc",
"linux-raw-sys 0.4.14",
"once_cell",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -11223,7 +11213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [
"dyn-clone",
"indexmap 1.9.3",
"indexmap",
"schemars_derive",
"serde",
"serde_json",
@@ -11296,9 +11286,9 @@ dependencies = [
[[package]]
name = "sea-orm"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b24d72a69e89762982c29af249542b06c59fa131f87cc9d5b94be1f692b427a"
checksum = "0dbcf83248860dc632c46c7e81a221e041b50d0006191756cb001d9e8afc60a9"
dependencies = [
"async-stream",
"async-trait",
@@ -11314,7 +11304,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
"strum 0.26.3",
"strum",
"thiserror 1.0.69",
"time",
"tracing",
@@ -11324,9 +11314,9 @@ dependencies = [
[[package]]
name = "sea-orm-macros"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0497f4fd82ecb2a222bea5319b9048f8ab58d4e734d095b062987acbcdeecdda"
checksum = "49ce6f08134f3681b1ca92185b96fac898f26d9b4f5538d13f7032ef243d14b2"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@@ -11556,11 +11546,11 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.133"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"indexmap 2.7.0",
"indexmap",
"itoa",
"memchr",
"ryu",
@@ -11573,7 +11563,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540"
dependencies = [
"indexmap 2.7.0",
"indexmap",
"itoa",
"memchr",
"ryu",
@@ -12146,7 +12136,7 @@ dependencies = [
"hashbrown 0.14.5",
"hashlink 0.9.1",
"hex",
"indexmap 2.7.0",
"indexmap",
"log",
"memchr",
"once_cell",
@@ -12370,7 +12360,7 @@ dependencies = [
"settings",
"simplelog",
"story",
"strum 0.25.0",
"strum",
"theme",
"title_bar",
"ui",
@@ -12430,26 +12420,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.25.0"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
[[package]]
name = "strum_macros"
version = "0.25.3"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"proc-macro2",
"quote",
"rustversion",
@@ -12768,7 +12752,7 @@ dependencies = [
"fd-lock",
"io-lifetimes 2.0.4",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
"winx",
]
@@ -12900,7 +12884,7 @@ dependencies = [
"fastrand 2.3.0",
"once_cell",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -13025,7 +13009,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"indexmap 1.9.3",
"indexmap",
"log",
"palette",
"parking_lot",
@@ -13037,7 +13021,7 @@ dependencies = [
"serde_json_lenient",
"serde_repr",
"settings",
"strum 0.25.0",
"strum",
"util",
"uuid",
]
@@ -13060,7 +13044,7 @@ dependencies = [
"anyhow",
"clap",
"gpui",
"indexmap 1.9.3",
"indexmap",
"log",
"palette",
"rust-embed",
@@ -13069,7 +13053,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"simplelog",
"strum 0.25.0",
"strum",
"theme",
"vscode_theme",
]
@@ -13497,7 +13481,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.7.0",
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
@@ -14000,7 +13984,7 @@ dependencies = [
"settings",
"smallvec",
"story",
"strum 0.25.0",
"strum",
"theme",
"ui_macros",
"windows 0.58.0",
@@ -14029,9 +14013,9 @@ dependencies = [
[[package]]
name = "unicase"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-bidi"
@@ -14579,7 +14563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2"
dependencies = [
"anyhow",
"indexmap 2.7.0",
"indexmap",
"serde",
"serde_derive",
"serde_json",
@@ -14608,7 +14592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
dependencies = [
"bitflags 2.6.0",
"indexmap 2.7.0",
"indexmap",
"semver",
]
@@ -14621,7 +14605,7 @@ dependencies = [
"ahash 0.8.11",
"bitflags 2.6.0",
"hashbrown 0.14.5",
"indexmap 2.7.0",
"indexmap",
"semver",
"serde",
]
@@ -14651,7 +14635,7 @@ dependencies = [
"cfg-if",
"encoding_rs",
"hashbrown 0.14.5",
"indexmap 2.7.0",
"indexmap",
"libc",
"libm",
"log",
@@ -14772,7 +14756,7 @@ dependencies = [
"cranelift-bitset",
"cranelift-entity",
"gimli 0.29.0",
"indexmap 2.7.0",
"indexmap",
"log",
"object",
"postcard",
@@ -14902,7 +14886,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190"
dependencies = [
"anyhow",
"heck 0.4.1",
"indexmap 2.7.0",
"indexmap",
"wit-parser 0.215.0",
]
@@ -15203,7 +15187,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]]
@@ -15652,7 +15636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
dependencies = [
"bitflags 2.6.0",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -15699,7 +15683,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3"
dependencies = [
"anyhow",
"heck 0.4.1",
"indexmap 2.7.0",
"indexmap",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -15727,7 +15711,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
dependencies = [
"anyhow",
"bitflags 2.6.0",
"indexmap 2.7.0",
"indexmap",
"log",
"serde",
"serde_derive",
@@ -15746,7 +15730,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6"
dependencies = [
"anyhow",
"id-arena",
"indexmap 2.7.0",
"indexmap",
"log",
"semver",
"serde",
@@ -15764,7 +15748,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f"
dependencies = [
"anyhow",
"id-arena",
"indexmap 2.7.0",
"indexmap",
"log",
"semver",
"serde",
@@ -15820,7 +15804,7 @@ dependencies = [
"settings",
"smallvec",
"sqlez",
"strum 0.25.0",
"strum",
"task",
"tempfile",
"theme",
@@ -16204,7 +16188,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.168.0"
version = "0.169.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16336,24 +16320,9 @@ dependencies = [
"serde",
]
[[package]]
name = "zed_astro"
version = "0.1.2"
dependencies = [
"serde",
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_clojure"
version = "0.0.3"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_csharp"
version = "0.0.2"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16372,13 +16341,6 @@ dependencies = [
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_elm"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_emmet"
version = "0.0.3"

View File

@@ -149,12 +149,9 @@ members = [
# Extensions
#
"extensions/astro",
"extensions/clojure",
"extensions/csharp",
"extensions/deno",
"extensions/elixir",
"extensions/elm",
"extensions/emmet",
"extensions/erlang",
"extensions/glsl",
@@ -392,7 +389,7 @@ hyper = "0.14"
http = "1.1"
ignore = "0.4.22"
image = "0.25.1"
indexmap = { version = "1.6.2", features = ["serde"] }
indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
itertools = "0.13.0"
jsonwebtoken = "9.3"
@@ -443,9 +440,10 @@ runtimelib = { version = "0.24.0", default-features = false, features = [
] }
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = "0.21.12"
rustls-native-certs = "0.8.0"
schemars = { version = "0.8", features = ["impl_json_schema"] }
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
@@ -465,7 +463,7 @@ smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
sqlformat = "0.2"
strsim = "0.11"
strum = { version = "0.25.0", features = ["derive"] }
strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0"
sys-locale = "0.3.1"
sysinfo = "0.31.0"

View File

@@ -1 +1,5 @@
<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-file-search"><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3"/><path d="m9 18-1.5-1.5"/><circle cx="5" cy="14" r="3"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.2345 20.1C5.38772 20.373 5.60794 20.5998 5.87313 20.7577C6.13832 20.9157 6.43919 20.9992 6.74562 21H17.25C17.7141 21 18.1592 20.8104 18.4874 20.4728C18.8156 20.1352 19 19.6774 19 19.2V7.5L14.625 3H6.75C6.28587 3 5.84075 3.18964 5.51256 3.52721C5.18437 3.86477 5 4.32261 5 4.8V6.5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 16.8182L8.5 15.3182" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 15.8182C7.65685 15.8182 9 14.475 9 12.8182C9 11.1613 7.65685 9.81818 6 9.81818C4.34315 9.81818 3 11.1613 3 12.8182C3 14.475 4.34315 15.8182 6 15.8182Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 837 B

View File

@@ -377,6 +377,7 @@
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
"alt-ctrl-o": "projects::OpenRecent",
"alt-ctrl-shift-o": "projects::OpenRemote",
"alt-ctrl-shift-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"ctrl-s": "workspace::Save",
@@ -434,6 +435,13 @@
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
}
},
{
"context": "ApplicationMenu",
"bindings": {
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
}
},
// Bindings from Sublime Text
{
"context": "Editor",

View File

@@ -4,55 +4,70 @@
// from the command palette.
[
{
"context": "Editor",
"bindings": {
"ctrl-g": "editor::Cancel",
"ctrl-shift-g": "go_to_line::Toggle",
//"ctrl-space": "editor::SetMark",
"ctrl-x u": "editor::Undo",
"ctrl-x ctrl-u": "editor::Redo",
"ctrl-f": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown",
"ctrl-p": "editor::MoveUp",
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-f": "editor::MoveToNextSubwordEnd",
"alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-d": "editor::Delete",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-w": "editor::Cut",
"alt-w": "editor::Copy",
"ctrl-y": "editor::Paste",
"ctrl-_": "editor::Undo",
"ctrl-v": "editor::MovePageDown",
"alt-v": "editor::MovePageUp",
"ctrl-x ]": "editor::MoveToEnd",
"ctrl-x [": "editor::MoveToBeginning",
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
"ctrl-s": "buffer_search::Deploy",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-shift-r": "editor::Rename"
"ctrl-g": "menu::Cancel"
}
},
{
"context": "Workspace",
"context": "Editor",
"bindings": {
"ctrl-x k": "pane::CloseActiveItem",
"ctrl-x ctrl-c": "workspace::CloseWindow",
"ctrl-x o": "workspace::ActivateNextPane",
"ctrl-x b": "tab_switcher::Toggle",
"ctrl-x 0": "pane::CloseActiveItem",
"ctrl-x 1": "pane::CloseInactiveItems",
"ctrl-x 2": "pane::SplitVertical",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-x ctrl-s": "workspace::Save",
"ctrl-x ctrl-w": "workspace::SaveAs",
"ctrl-x s": "workspace::SaveAll",
"shift shift": "file_finder::Toggle"
"ctrl-g": "editor::Cancel",
"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-f": "editor::MoveRight", // forward-char
"ctrl-b": "editor::MoveLeft", // backward-char
"ctrl-n": "editor::MoveDown", // next-line
"ctrl-p": "editor::MoveUp", // previous-line
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"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
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-x ctrl-;": "editor::ToggleComments",
"alt-.": "editor::GoToDefinition", // xref-find-definitions
"alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
"ctrl-k": "editor::KillRingCut", // kill-line
"ctrl-w": "editor::Cut", // kill-region
"alt-w": "editor::Copy", // kill-ring-save
"ctrl-y": "editor::KillRingYank", // yank
"ctrl-_": "editor::Undo", // undo
"ctrl-/": "editor::Undo", // undo
"ctrl-x u": "editor::Undo", // undo
"ctrl-v": "editor::MovePageDown", // scroll-up
"alt-v": "editor::MovePageUp", // scroll-down
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
"ctrl-x ]": "editor::MoveToEnd", // end-of-buffer
"alt-<": "editor::MoveToBeginning", // beginning-of-buffer
"alt->": "editor::MoveToEnd", // end-of-buffer
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
"ctrl-s": "buffer_search::Deploy", // isearch-forward
"alt-^": "editor::JoinLines" // join-line
}
},
{
"context": "Workspace && !Terminal",
"bindings": {
"ctrl-x ctrl-c": "workspace::CloseWindow", // kill-emacs
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
}
},
{

View File

@@ -4,55 +4,70 @@
// from the command palette.
[
{
"context": "Editor",
"bindings": {
"ctrl-g": "editor::Cancel",
"ctrl-shift-g": "go_to_line::Toggle",
//"ctrl-space": "editor::SetMark",
"ctrl-x u": "editor::Undo",
"ctrl-x ctrl-u": "editor::Redo",
"ctrl-f": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown",
"ctrl-p": "editor::MoveUp",
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-f": "editor::MoveToNextSubwordEnd",
"alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-d": "editor::Delete",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-w": "editor::Cut",
"alt-w": "editor::Copy",
"ctrl-y": "editor::Paste",
"ctrl-_": "editor::Undo",
"ctrl-v": "editor::MovePageDown",
"alt-v": "editor::MovePageUp",
"ctrl-x ]": "editor::MoveToEnd",
"ctrl-x [": "editor::MoveToBeginning",
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
"ctrl-s": "buffer_search::Deploy",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-shift-r": "editor::Rename"
"ctrl-g": "menu::Cancel"
}
},
{
"context": "Workspace",
"context": "Editor",
"bindings": {
"ctrl-x k": "pane::CloseActiveItem",
"ctrl-x ctrl-c": "workspace::CloseWindow",
"ctrl-x o": "workspace::ActivateNextPane",
"ctrl-x b": "tab_switcher::Toggle",
"ctrl-x 0": "pane::CloseActiveItem",
"ctrl-x 1": "pane::CloseInactiveItems",
"ctrl-x 2": "pane::SplitVertical",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-x ctrl-s": "workspace::Save",
"ctrl-x ctrl-w": "workspace::SaveAs",
"ctrl-x s": "workspace::SaveAll",
"shift shift": "file_finder::Toggle"
"ctrl-g": "editor::Cancel",
"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-f": "editor::MoveRight", // forward-char
"ctrl-b": "editor::MoveLeft", // backward-char
"ctrl-n": "editor::MoveDown", // next-line
"ctrl-p": "editor::MoveUp", // previous-line
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"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
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-x ctrl-;": "editor::ToggleComments",
"alt-.": "editor::GoToDefinition", // xref-find-definitions
"alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
"ctrl-k": "editor::KillRingCut", // kill-line
"ctrl-w": "editor::Cut", // kill-region
"alt-w": "editor::Copy", // kill-ring-save
"ctrl-y": "editor::KillRingYank", // yank
"ctrl-_": "editor::Undo", // undo
"ctrl-/": "editor::Undo", // undo
"ctrl-x u": "editor::Undo", // undo
"ctrl-v": "editor::MovePageDown", // scroll-up
"alt-v": "editor::MovePageUp", // scroll-down
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
"ctrl-x ]": "editor::MoveToEnd", // end-of-buffer
"alt-<": "editor::MoveToBeginning", // beginning-of-buffer
"alt->": "editor::MoveToEnd", // end-of-buffer
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
"ctrl-s": "buffer_search::Deploy", // isearch-forward
"alt-^": "editor::JoinLines" // join-line
}
},
{
"context": "Workspace && !Terminal",
"bindings": {
"ctrl-x ctrl-c": "workspace::CloseWindow", // kill-emacs
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
}
},
{

View File

@@ -256,8 +256,13 @@
"search_results": true,
// Whether to show selected symbol occurrences in the scrollbar.
"selected_symbol": true,
// Whether to show diagnostic indicators in the scrollbar.
"diagnostics": true,
// Which diagnostic indicators to show in the scrollbar:
// - "none" or false: do not show diagnostics
// - "error": show only errors
// - "warning": show only errors and warnings
// - "information": show only errors, warnings, and information
// - "all" or true: show all diagnostics
"diagnostics": "all",
/// Forcefully enable or disable the scrollbar for each axis
"axes": {
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
@@ -741,7 +746,7 @@
// Delay is restarted with every cursor movement.
// "delay_ms": 600
//
// Whether or not do display the git commit summary on the same line.
// Whether or not to display the git commit summary on the same line.
// "show_commit_summary": false
//
// The minimum column number to show the inline blame information at

View File

@@ -4,8 +4,8 @@ use extension_host::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
InteractiveElement as _, Model, ModelContext, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, Window,
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
use lsp::LanguageServerName;
@@ -46,25 +46,22 @@ struct PendingWork<'a> {
struct Content {
icon: Option<gpui::AnyElement>,
message: String,
on_click: Option<
Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut ModelContext<ActivityIndicator>)>,
>,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
}
impl ActivityIndicator {
pub fn new(
workspace: &mut Workspace,
languages: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Model<ActivityIndicator> {
cx: &mut ViewContext<Workspace>,
) -> View<ActivityIndicator> {
let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx);
let this = cx.new_model(|cx| {
let this = cx.new_view(|cx: &mut ViewContext<Self>| {
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: &mut ActivityIndicator, cx| {
this.update(&mut cx, |this, cx| {
this.statuses.retain(|s| s.name != name);
this.statuses.push(LspStatus { name, status });
cx.notify();
@@ -73,7 +70,6 @@ impl ActivityIndicator {
anyhow::Ok(())
})
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
if let Some(auto_updater) = auto_updater.as_ref() {
@@ -88,13 +84,13 @@ impl ActivityIndicator {
}
});
cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
cx.subscribe(&this, move |_, _, event, 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_in(window, |workspace, mut cx| async move {
cx.spawn(|workspace, mut cx| async move {
let buffer = create_buffer.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.edit(
@@ -107,14 +103,13 @@ impl ActivityIndicator {
);
buffer.set_capability(language::Capability::ReadOnly, cx);
})?;
workspace.update_in(&mut cx, |workspace, window, cx| {
workspace.update(&mut cx, |workspace, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new_model(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
Box::new(cx.new_view(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), cx)
})),
None,
true,
window,
cx,
);
})?;
@@ -128,12 +123,7 @@ impl ActivityIndicator {
this
}
fn show_error_message(
&mut self,
_: &ShowErrorMessage,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) {
self.statuses.retain(|status| {
if let LanguageServerBinaryStatus::Failed { error } = &status.status {
cx.emit(Event::ShowError {
@@ -149,12 +139,7 @@ impl ActivityIndicator {
cx.notify();
}
fn dismiss_error_message(
&mut self,
_: &DismissErrorMessage,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
if let Some(updater) = &self.auto_updater {
updater.update(cx, |updater, cx| {
updater.dismiss_error(cx);
@@ -198,7 +183,7 @@ impl ActivityIndicator {
self.project.read(cx).shell_environment_errors(cx)
}
fn content_to_render(&mut self, cx: &mut ModelContext<Self>) -> Option<Content> {
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
return Some(Content {
@@ -208,11 +193,11 @@ impl ActivityIndicator {
.into_any_element(),
),
message: error.0.clone(),
on_click: Some(Arc::new(move |this, window, cx| {
on_click: Some(Arc::new(move |this, cx| {
this.project.update(cx, |project, cx| {
project.remove_environment_error(cx, worktree_id);
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
cx.dispatch_action(Box::new(workspace::OpenLog));
})),
});
}
@@ -295,10 +280,10 @@ impl ActivityIndicator {
}
)
),
on_click: Some(Arc::new(move |this, window, cx| {
on_click: Some(Arc::new(move |this, cx| {
this.statuses
.retain(|status| !downloading.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
});
}
@@ -323,10 +308,10 @@ impl ActivityIndicator {
}
),
),
on_click: Some(Arc::new(move |this, window, cx| {
on_click: Some(Arc::new(move |this, cx| {
this.statuses
.retain(|status| !checking_for_update.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, window, cx)
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
});
}
@@ -351,8 +336,8 @@ impl ActivityIndicator {
acc
}),
),
on_click: Some(Arc::new(|this, window, cx| {
this.show_error_message(&Default::default(), window, cx)
on_click: Some(Arc::new(|this, cx| {
this.show_error_message(&Default::default(), cx)
})),
});
}
@@ -366,11 +351,11 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|indicator, window, cx| {
on_click: Some(Arc::new(|indicator, cx| {
indicator.project.update(cx, |project, cx| {
project.reset_last_formatting_failure(cx);
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
cx.dispatch_action(Box::new(workspace::OpenLog));
})),
});
}
@@ -385,8 +370,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Checking for Zed updates…".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Downloading => Some(Content {
@@ -396,8 +381,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Downloading Zed update…".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Installing => Some(Content {
@@ -407,8 +392,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Installing Zed update…".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Updated { binary_path } => Some(Content {
@@ -418,7 +403,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 {
@@ -428,8 +413,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
}),
AutoUpdateStatus::Idle => None,
@@ -447,8 +432,8 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Updating {extension_id} extension…"),
on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, window, cx)
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx)
})),
});
}
@@ -457,12 +442,8 @@ impl ActivityIndicator {
None
}
fn toggle_language_server_work_context_menu(
&mut self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.context_menu_handle.toggle(window, cx);
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
self.context_menu_handle.toggle(cx);
}
}
@@ -471,7 +452,7 @@ impl EventEmitter<Event> for ActivityIndicator {}
const MAX_MESSAGE_LEN: usize = 50;
impl Render for ActivityIndicator {
fn render(&mut self, _window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let result = h_flex()
.id("activity-indicator")
.on_action(cx.listener(Self::show_error_message))
@@ -479,7 +460,7 @@ impl Render for ActivityIndicator {
let Some(content) = self.content_to_render(cx) else {
return result;
};
let this = cx.model().downgrade();
let this = cx.view().downgrade();
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
result.gap_2().child(
PopoverMenu::new("activity-indicator-popover")
@@ -499,24 +480,24 @@ impl Render for ActivityIndicator {
))
.size(LabelSize::Small),
)
.tooltip(Tooltip::text(content.message))
.tooltip(move |cx| Tooltip::text(&content.message, cx))
} else {
button.child(Label::new(content.message).size(LabelSize::Small))
}
})
.when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, window, cx| {
handler(this, window, cx);
this.on_click(cx.listener(move |this, _, cx| {
handler(this, cx);
}))
.cursor(CursorStyle::PointingHand)
}),
),
)
.anchor(gpui::Corner::BottomLeft)
.menu(move |window, cx| {
.menu(move |cx| {
let strong_this = this.upgrade()?;
let mut has_work = false;
let menu = ContextMenu::build(window, cx, |mut menu, _, cx| {
let menu = ContextMenu::build(cx, |mut menu, cx| {
for work in strong_this.read(cx).pending_language_server_work(cx) {
has_work = true;
let this = this.clone();
@@ -532,7 +513,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()
@@ -540,7 +521,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(
@@ -573,11 +554,5 @@ impl Render for ActivityIndicator {
}
impl StatusItemView for ActivityIndicator {
fn set_active_pane_item(
&mut self,
_: Option<&dyn ItemHandle>,
_window: &mut Window,
_: &mut ModelContext<Self>,
) {
}
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
}

View File

@@ -30,6 +30,8 @@ pub enum Model {
#[default]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
@@ -48,6 +50,8 @@ pub enum Model {
cache_configuration: Option<AnthropicModelCacheConfiguration>,
max_output_tokens: Option<u32>,
default_temperature: Option<f32>,
#[serde(default)]
extra_beta_headers: Vec<String>,
},
}
@@ -55,6 +59,8 @@ impl Model {
pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-3-5-sonnet") {
Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-5-haiku") {
Ok(Self::Claude3_5Haiku)
} else if id.starts_with("claude-3-opus") {
Ok(Self::Claude3Opus)
} else if id.starts_with("claude-3-sonnet") {
@@ -69,6 +75,7 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-latest",
Model::Claude3Haiku => "claude-3-haiku-latest",
@@ -79,6 +86,7 @@ impl Model {
pub fn display_name(&self) -> &str {
match self {
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
@@ -90,11 +98,13 @@ impl Model {
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
match self {
Self::Claude3_5Sonnet | Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048,
should_speculate: true,
max_cache_anchors: 4,
}),
Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048,
should_speculate: true,
max_cache_anchors: 4,
})
}
Self::Custom {
cache_configuration,
..
@@ -106,6 +116,7 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200_000,
@@ -116,7 +127,7 @@ impl Model {
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
Self::Claude3_5Sonnet => 8_192,
Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
Self::Custom {
max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096),
@@ -126,6 +137,7 @@ impl Model {
pub fn default_temperature(&self) -> f32 {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 1.0,
@@ -136,6 +148,24 @@ impl Model {
}
}
pub fn beta_headers(&self) -> String {
let mut headers = vec!["prompt-caching-2024-07-31".to_string()];
if let Self::Custom {
extra_beta_headers, ..
} = self
{
headers.extend(
extra_beta_headers
.iter()
.filter(|header| !header.trim().is_empty())
.cloned(),
);
}
headers.join(",")
}
pub fn tool_model_id(&self) -> &str {
if let Self::Custom {
tool_override: Some(tool_override),
@@ -156,11 +186,12 @@ pub async fn complete(
request: Request,
) -> Result<Response, AnthropicError> {
let uri = format!("{api_url}/v1/messages");
let model = Model::from_id(&request.model)?;
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", "prompt-caching-2024-07-31")
.header("Anthropic-Beta", model.beta_headers())
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
@@ -271,14 +302,12 @@ 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 request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header(
"Anthropic-Beta",
"tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
)
.header("Anthropic-Beta", model.beta_headers())
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
let serialized_request =

View File

@@ -37,7 +37,7 @@ pub use prompts::PromptBuilder;
use prompts::PromptLoadingParams;
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use settings::{Settings, SettingsStore};
use slash_command::search_command::SearchSlashCommandFeatureFlag;
use slash_command::{
auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
@@ -199,16 +199,6 @@ pub fn init(
AssistantSettings::register(cx);
SlashCommandSettings::register(cx);
// TODO: remove this when 0.148.0 is released.
if AssistantSettings::get_global(cx).using_outdated_settings_version {
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
let fs = fs.clone();
|content, cx| {
content.update_file(fs, cx);
}
});
}
cx.spawn(|mut cx| {
let client = client.clone();
async move {

File diff suppressed because it is too large Load Diff

View File

@@ -3,18 +3,12 @@ use std::sync::Arc;
use ::open_ai::Model as OpenAiModel;
use anthropic::Model as AnthropicModel;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::{AppContext, Pixels};
use language_model::{CloudModel, LanguageModel};
use language_models::{
provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent,
AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent,
OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent,
};
use ollama::Model as OllamaModel;
use schemars::{schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsSources};
use settings::{Settings, SettingsSources};
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -106,96 +100,6 @@ impl AssistantSettingsContent {
}
}
pub fn update_file(&mut self, fs: Arc<dyn Fs>, cx: &AppContext) {
if let AssistantSettingsContent::Versioned(settings) = self {
if let VersionedAssistantSettingsContent::V1(settings) = settings {
if let Some(provider) = settings.provider.clone() {
match provider {
AssistantProviderContentV1::Anthropic { api_url, .. } => {
update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.anthropic.is_none() {
content.anthropic =
Some(AnthropicSettingsContent::Versioned(
VersionedAnthropicSettingsContent::V1(
AnthropicSettingsContentV1 {
api_url,
available_models: None,
},
),
));
}
},
)
}
AssistantProviderContentV1::Ollama { api_url, .. } => {
update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.ollama.is_none() {
content.ollama = Some(OllamaSettingsContent {
api_url,
available_models: None,
});
}
},
)
}
AssistantProviderContentV1::OpenAi {
api_url,
available_models,
..
} => update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.openai.is_none() {
let available_models = available_models.map(|models| {
models
.into_iter()
.filter_map(|model| match model {
OpenAiModel::Custom {
name,
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens: None,
} => Some(open_ai::AvailableModel {
name,
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens: None,
}),
_ => None,
})
.collect::<Vec<_>>()
});
content.openai = Some(OpenAiSettingsContent::Versioned(
VersionedOpenAiSettingsContent::V1(
OpenAiSettingsContentV1 {
api_url,
available_models,
},
),
));
}
},
),
_ => {}
}
}
}
}
*self = AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(
self.upgrade(),
));
}
fn upgrade(&self) -> AssistantSettingsContentV2 {
match self {
AssistantSettingsContent::Versioned(settings) => match settings {
@@ -534,6 +438,7 @@ fn merge<T>(target: &mut T, value: Option<T>) {
#[cfg(test)]
mod tests {
use fs::Fs;
use gpui::{ReadGlobal, TestAppContext};
use super::*;

View File

@@ -17,7 +17,7 @@ use futures::{
channel::mpsc,
stream::{self, StreamExt},
};
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakModel};
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakView};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
@@ -35,7 +35,7 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
use ui::{IconName, Window};
use ui::{IconName, WindowContext};
use unindent::Unindent;
use util::{
test::{generate_marked_text, marked_text_ranges},
@@ -1642,9 +1642,8 @@ impl SlashCommand for FakeSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(vec![]))
}
@@ -1658,10 +1657,9 @@ impl SlashCommand for FakeSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
_cx: &mut AppContext,
_cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0),

View File

@@ -320,7 +320,7 @@ impl ContextStore {
.client
.subscribe_to_entity(remote_id)
.log_err()
.map(|subscription| subscription.set_model(&cx.model(), &mut cx.to_async()));
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
self.advertise_contexts(cx);
} else {
self.client_subscription = None;

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ use futures::{
use fuzzy::StringMatchCandidate;
use gpui::{
actions, point, size, transparent_black, Action, AppContext, BackgroundExecutor, Bounds,
EventEmitter, Focusable, Global, Model, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
TitlebarOptions, UpdateGlobal, WindowBounds, WindowHandle, WindowOptions,
EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TextStyle, TitlebarOptions,
UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
};
use heed::{
types::{SerdeBincode, SerdeJson, Str},
@@ -38,8 +38,8 @@ use std::{
use text::LineEnding;
use theme::ThemeSettings;
use ui::{
div, prelude::*, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ModelContext,
ParentElement, Render, SharedString, Styled, Tooltip, Window,
div, prelude::*, IconButtonShape, KeyBinding, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, Tooltip, ViewContext, VisualContext,
};
use util::{ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -88,7 +88,7 @@ pub fn open_prompt_library(
.find_map(|window| window.downcast::<PromptLibrary>());
if let Some(existing_window) = existing_window {
existing_window
.update(cx, |_, window, _| window.activate_window())
.update(cx, |_, cx| cx.activate_window())
.ok();
Task::ready(Ok(existing_window))
} else {
@@ -109,9 +109,7 @@ pub fn open_prompt_library(
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|window, cx| {
cx.new_model(|cx| PromptLibrary::new(store, language_registry, window, cx))
},
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
)
})?
})
@@ -123,14 +121,14 @@ pub struct PromptLibrary {
language_registry: Arc<LanguageRegistry>,
prompt_editors: HashMap<PromptId, PromptEditor>,
active_prompt_id: Option<PromptId>,
picker: Model<Picker<PromptPickerDelegate>>,
picker: View<Picker<PromptPickerDelegate>>,
pending_load: Task<()>,
_subscriptions: Vec<Subscription>,
}
struct PromptEditor {
title_editor: Model<Editor>,
body_editor: Model<Editor>,
title_editor: View<Editor>,
body_editor: View<Editor>,
token_count: Option<usize>,
pending_token_count: Task<Option<()>>,
next_title_and_body_to_save: Option<(String, Rope)>,
@@ -160,7 +158,7 @@ impl PickerDelegate for PromptPickerDelegate {
self.matches.len()
}
fn no_matches_text(&self, _window: &mut Window, _cx: &mut AppContext) -> SharedString {
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
if self.store.prompt_count() == 0 {
"No prompts.".into()
} else {
@@ -172,12 +170,7 @@ impl PickerDelegate for PromptPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
if let Some(prompt) = self.matches.get(self.selected_index) {
cx.emit(PromptPickerEvent::Selected {
@@ -186,19 +179,14 @@ impl PickerDelegate for PromptPickerDelegate {
}
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search...".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search = self.store.search(query);
let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let (matches, selected_index) = cx
.background_executor()
.spawn(async move {
@@ -213,16 +201,16 @@ impl PickerDelegate for PromptPickerDelegate {
})
.await;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate.matches = matches;
this.delegate.set_selected_index(selected_index, window, cx);
this.delegate.set_selected_index(selected_index, cx);
cx.notify();
})
.ok();
})
}
fn confirm(&mut self, _secondary: bool, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(prompt) = self.matches.get(self.selected_index) {
cx.emit(PromptPickerEvent::Confirmed {
prompt_id: prompt.id,
@@ -230,14 +218,13 @@ impl PickerDelegate for PromptPickerDelegate {
}
}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut ModelContext<Picker<Self>>) {}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let prompt = self.matches.get(ix)?;
let default = prompt.default;
@@ -254,8 +241,8 @@ impl PickerDelegate for PromptPickerDelegate {
.toggle_state(true)
.icon_color(Color::Accent)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text("Remove from Default Prompt"))
.on_click(cx.listener(move |_, _, _, cx| {
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
}))
}))
@@ -266,12 +253,11 @@ impl PickerDelegate for PromptPickerDelegate {
div()
.id("built-in-prompt")
.child(Icon::new(IconName::FileLock).color(Color::Muted))
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::with_meta(
"Built-in prompt",
None,
BUILT_IN_TOOLTIP_TEXT,
window,
cx,
)
})
@@ -280,8 +266,8 @@ impl PickerDelegate for PromptPickerDelegate {
IconButton::new("delete-prompt", IconName::Trash)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text("Delete Prompt"))
.on_click(cx.listener(move |_, _, _, cx| {
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::Deleted { prompt_id })
}))
.into_any_element()
@@ -292,12 +278,17 @@ impl PickerDelegate for PromptPickerDelegate {
.selected_icon(IconName::SparkleFilled)
.icon_color(if default { Color::Accent } else { Color::Muted })
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text(if default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
}))
.on_click(cx.listener(move |_, _, _, cx| {
.tooltip(move |cx| {
Tooltip::text(
if default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
})),
),
@@ -305,12 +296,7 @@ impl PickerDelegate for PromptPickerDelegate {
Some(element)
}
fn render_editor(
&self,
editor: &Model<Editor>,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Div {
fn render_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Picker<Self>>) -> Div {
h_flex()
.bg(cx.theme().colors().editor_background)
.rounded_md()
@@ -327,8 +313,7 @@ impl PromptLibrary {
fn new(
store: Arc<PromptStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = PromptPickerDelegate {
store: store.clone(),
@@ -336,11 +321,11 @@ impl PromptLibrary {
matches: Vec::new(),
};
let picker = cx.new_model(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
let picker = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx)
.modal(false)
.max_height(None);
picker.focus(window, cx);
picker.focus(cx);
picker
});
Self {
@@ -349,63 +334,54 @@ impl PromptLibrary {
prompt_editors: HashMap::default(),
active_prompt_id: None,
pending_load: Task::ready(()),
_subscriptions: vec![cx.subscribe_in(&picker, window, Self::handle_picker_event)],
_subscriptions: vec![cx.subscribe(&picker, Self::handle_picker_event)],
picker,
}
}
fn handle_picker_event(
&mut self,
_: &Model<Picker<PromptPickerDelegate>>,
_: View<Picker<PromptPickerDelegate>>,
event: &PromptPickerEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
PromptPickerEvent::Selected { prompt_id } => {
self.load_prompt(*prompt_id, false, window, cx);
self.load_prompt(*prompt_id, false, cx);
}
PromptPickerEvent::Confirmed { prompt_id } => {
self.load_prompt(*prompt_id, true, window, cx);
self.load_prompt(*prompt_id, true, cx);
}
PromptPickerEvent::ToggledDefault { prompt_id } => {
self.toggle_default_for_prompt(*prompt_id, window, cx);
self.toggle_default_for_prompt(*prompt_id, cx);
}
PromptPickerEvent::Deleted { prompt_id } => {
self.delete_prompt(*prompt_id, window, cx);
self.delete_prompt(*prompt_id, cx);
}
}
}
pub fn new_prompt(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
pub fn new_prompt(&mut self, cx: &mut ViewContext<Self>) {
// If we already have an untitled prompt, use that instead
// of creating a new one.
if let Some(metadata) = self.store.first() {
if metadata.title.is_none() {
self.load_prompt(metadata.id, true, window, cx);
self.load_prompt(metadata.id, true, cx);
return;
}
}
let prompt_id = PromptId::new();
let save = self.store.save(prompt_id, None, false, "".into());
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.spawn_in(window, |this, mut cx| async move {
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
save.await?;
this.update_in(&mut cx, |this, window, cx| {
this.load_prompt(prompt_id, true, window, cx)
})
this.update(&mut cx, |this, cx| this.load_prompt(prompt_id, true, cx))
})
.detach_and_log_err(cx);
}
pub fn save_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn save_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
const SAVE_THROTTLE: Duration = Duration::from_millis(500);
if prompt_id.is_built_in() {
@@ -431,7 +407,7 @@ impl PromptLibrary {
prompt_editor.next_title_and_body_to_save = Some((title, body));
if prompt_editor.pending_save.is_none() {
prompt_editor.pending_save = Some(cx.spawn_in(window, |this, mut cx| {
prompt_editor.pending_save = Some(cx.spawn(|this, mut cx| {
async move {
loop {
let title_and_body = this.update(&mut cx, |this, _| {
@@ -451,9 +427,8 @@ impl PromptLibrary {
.save(prompt_id, title, prompt_metadata.default, body)
.await
.log_err();
this.update_in(&mut cx, |this, window, cx| {
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
this.update(&mut cx, |this, cx| {
this.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.notify();
})?;
@@ -474,77 +449,61 @@ impl PromptLibrary {
}
}
pub fn delete_active_prompt(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
pub fn delete_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.delete_prompt(active_prompt_id, window, cx);
self.delete_prompt(active_prompt_id, cx);
}
}
pub fn duplicate_active_prompt(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.duplicate_prompt(active_prompt_id, window, cx);
self.duplicate_prompt(active_prompt_id, cx);
}
}
pub fn toggle_default_for_active_prompt(
&mut self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.toggle_default_for_prompt(active_prompt_id, window, cx);
self.toggle_default_for_prompt(active_prompt_id, cx);
}
}
pub fn toggle_default_for_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn toggle_default_for_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
self.store
.save_metadata(prompt_id, prompt_metadata.title, !prompt_metadata.default)
.detach_and_log_err(cx);
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.notify();
}
}
pub fn load_prompt(
&mut self,
prompt_id: PromptId,
focus: bool,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn load_prompt(&mut self, prompt_id: PromptId, focus: bool, cx: &mut ViewContext<Self>) {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
if focus {
prompt_editor
.body_editor
.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)));
.update(cx, |editor, cx| editor.focus(cx));
}
self.set_active_prompt(Some(prompt_id), window, cx);
self.set_active_prompt(Some(prompt_id), cx);
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
let language_registry = self.language_registry.clone();
let prompt = self.store.load(prompt_id);
self.pending_load = cx.spawn_in(window, |this, mut cx| async move {
self.pending_load = cx.spawn(|this, mut cx| async move {
let prompt = prompt.await;
let markdown = language_registry.language_for_name("Markdown").await;
this.update_in(&mut cx, |this, window, cx| match prompt {
this.update(&mut cx, |this, cx| match prompt {
Ok(prompt) => {
let title_editor = cx.new_model(|cx| {
let mut editor = Editor::auto_width(window, cx);
let title_editor = cx.new_view(|cx| {
let mut editor = Editor::auto_width(cx);
editor.set_placeholder_text("Untitled", cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), window, cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
if prompt_id.is_built_in() {
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), window, cx);
editor.set_show_inline_completions(Some(false), cx);
}
editor
});
let body_editor = cx.new_model(|cx| {
let body_editor = cx.new_view(|cx| {
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
@@ -552,10 +511,10 @@ impl PromptLibrary {
buffer
});
let mut editor = Editor::for_buffer(buffer, None, window, cx);
let mut editor = Editor::for_buffer(buffer, None, cx);
if prompt_id.is_built_in() {
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), window, cx);
editor.set_show_inline_completions(Some(false), cx);
}
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
@@ -571,29 +530,17 @@ impl PromptLibrary {
),
)));
if focus {
window.focus(&editor.focus_handle(cx));
editor.focus(cx);
}
editor
});
let _subscriptions = vec![
cx.subscribe_in(
&title_editor,
window,
move |this, editor, event, window, cx| {
this.handle_prompt_title_editor_event(
prompt_id, editor, event, window, cx,
)
},
),
cx.subscribe_in(
&body_editor,
window,
move |this, editor, event, window, cx| {
this.handle_prompt_body_editor_event(
prompt_id, editor, event, window, cx,
)
},
),
cx.subscribe(&title_editor, move |this, editor, event, cx| {
this.handle_prompt_title_editor_event(prompt_id, editor, event, cx)
}),
cx.subscribe(&body_editor, move |this, editor, event, cx| {
this.handle_prompt_body_editor_event(prompt_id, editor, event, cx)
}),
];
this.prompt_editors.insert(
prompt_id,
@@ -607,8 +554,8 @@ impl PromptLibrary {
_subscriptions,
},
);
this.set_active_prompt(Some(prompt_id), window, cx);
this.count_tokens(prompt_id, window, cx);
this.set_active_prompt(Some(prompt_id), cx);
this.count_tokens(prompt_id, cx);
}
Err(error) => {
// TODO: we should show the error in the UI.
@@ -620,12 +567,7 @@ impl PromptLibrary {
}
}
fn set_active_prompt(
&mut self,
prompt_id: Option<PromptId>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn set_active_prompt(&mut self, prompt_id: Option<PromptId>, cx: &mut ViewContext<Self>) {
self.active_prompt_id = prompt_id;
self.picker.update(cx, |picker, cx| {
if let Some(prompt_id) = prompt_id {
@@ -643,24 +585,19 @@ impl PromptLibrary {
.iter()
.position(|mat| mat.id == prompt_id)
{
picker.set_selected_index(ix, true, window, cx);
picker.set_selected_index(ix, true, cx);
}
}
} else {
picker.focus(window, cx);
picker.focus(cx);
}
});
cx.notify();
}
pub fn delete_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn delete_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(metadata) = self.store.metadata(prompt_id) {
let confirmation = window.prompt(
let confirmation = cx.prompt(
PromptLevel::Warning,
&format!(
"Are you sure you want to delete {}",
@@ -668,19 +605,17 @@ impl PromptLibrary {
),
None,
&["Delete", "Cancel"],
cx,
);
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
if confirmation.await.ok() == Some(0) {
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
if this.active_prompt_id == Some(prompt_id) {
this.set_active_prompt(None, window, cx);
this.set_active_prompt(None, cx);
}
this.prompt_editors.remove(&prompt_id);
this.store.delete(prompt_id).detach_and_log_err(cx);
this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
this.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.notify();
})?;
}
@@ -690,12 +625,7 @@ impl PromptLibrary {
}
}
pub fn duplicate_prompt(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
const DUPLICATE_SUFFIX: &str = " copy";
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
@@ -725,38 +655,31 @@ impl PromptLibrary {
let save = self
.store
.save(new_id, Some(title.into()), false, body.into());
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
cx.spawn_in(window, |this, mut cx| async move {
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
save.await?;
this.update_in(&mut cx, |prompt_library, window, cx| {
prompt_library.load_prompt(new_id, true, window, cx)
this.update(&mut cx, |prompt_library, cx| {
prompt_library.load_prompt(new_id, true, cx)
})
})
.detach_and_log_err(cx);
}
}
fn focus_active_prompt(&mut self, _: &Tab, window: &mut Window, cx: &mut ModelContext<Self>) {
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
if let Some(active_prompt) = self.active_prompt_id {
self.prompt_editors[&active_prompt]
.body_editor
.update(cx, |editor, cx| window.focus(&editor.focus_handle(cx)));
.update(cx, |editor, cx| editor.focus(cx));
cx.stop_propagation();
}
}
fn focus_picker(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut ModelContext<Self>) {
self.picker
.update(cx, |picker, cx| picker.focus(window, cx));
fn focus_picker(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| picker.focus(cx));
}
pub fn inline_assist(
&mut self,
action: &InlineAssist,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub fn inline_assist(&mut self, action: &InlineAssist, cx: &mut ViewContext<Self>) {
let Some(active_prompt_id) = self.active_prompt_id else {
cx.propagate();
return;
@@ -770,15 +693,15 @@ impl PromptLibrary {
let initial_prompt = action.prompt.clone();
if provider.is_authenticated(cx) {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
assistant.assist(&prompt_editor, None, None, initial_prompt, cx)
})
} else {
for window in cx.windows() {
if let Some(workspace) = window.downcast::<Workspace>() {
let panel = workspace
.update(cx, |workspace, window, cx| {
window.activate_window();
workspace.focus_panel::<AssistantPanel>(window, cx)
.update(cx, |workspace, cx| {
cx.activate_window();
workspace.focus_panel::<AssistantPanel>(cx)
})
.ok()
.flatten();
@@ -790,28 +713,18 @@ impl PromptLibrary {
}
}
fn move_down_from_title(
&mut self,
_: &editor::actions::MoveDown,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn move_down_from_title(&mut self, _: &editor::actions::MoveDown, cx: &mut ViewContext<Self>) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
window.focus(&prompt_editor.body_editor.focus_handle(cx));
cx.focus_view(&prompt_editor.body_editor);
}
}
}
fn move_up_from_body(
&mut self,
_: &editor::actions::MoveUp,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn move_up_from_body(&mut self, _: &editor::actions::MoveUp, cx: &mut ViewContext<Self>) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
window.focus(&prompt_editor.title_editor.focus_handle(cx));
cx.focus_view(&prompt_editor.title_editor);
}
}
}
@@ -819,19 +732,18 @@ impl PromptLibrary {
fn handle_prompt_title_editor_event(
&mut self,
prompt_id: PromptId,
title_editor: &Model<Editor>,
title_editor: View<Editor>,
event: &EditorEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, window, cx);
self.count_tokens(prompt_id, window, cx);
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
EditorEvent::Blurred => {
title_editor.update(cx, |title_editor, cx| {
title_editor.change_selections(None, window, cx, |selections| {
title_editor.change_selections(None, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
@@ -844,19 +756,18 @@ impl PromptLibrary {
fn handle_prompt_body_editor_event(
&mut self,
prompt_id: PromptId,
body_editor: &Model<Editor>,
body_editor: View<Editor>,
event: &EditorEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, window, cx);
self.count_tokens(prompt_id, window, cx);
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
EditorEvent::Blurred => {
body_editor.update(cx, |body_editor, cx| {
body_editor.change_selections(None, window, cx, |selections| {
body_editor.change_selections(None, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
@@ -866,12 +777,7 @@ impl PromptLibrary {
}
}
fn count_tokens(
&mut self,
prompt_id: PromptId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn count_tokens(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
@@ -879,13 +785,13 @@ impl PromptLibrary {
let editor = &prompt.body_editor.read(cx);
let buffer = &editor.buffer().read(cx).as_singleton().unwrap().read(cx);
let body = buffer.as_rope().clone();
prompt.pending_token_count = cx.spawn_in(window, |this, mut cx| {
prompt.pending_token_count = cx.spawn(|this, mut cx| {
async move {
const DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
cx.background_executor().timer(DEBOUNCE_TIMEOUT).await;
let token_count = cx
.update(|_, cx| {
.update(|cx| {
model.count_tokens(
LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
@@ -913,7 +819,7 @@ impl PromptLibrary {
}
}
fn render_prompt_list(&mut self, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render_prompt_list(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.id("prompt-list")
.capture_action(cx.listener(Self::focus_active_prompt))
@@ -933,21 +839,16 @@ impl PromptLibrary {
IconButton::new("new-prompt", IconName::Plus)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action("New Prompt", &NewPrompt, window, cx)
})
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(NewPrompt), cx);
.tooltip(move |cx| Tooltip::for_action("New Prompt", &NewPrompt, cx))
.on_click(|_, cx| {
cx.dispatch_action(Box::new(NewPrompt));
}),
),
)
.child(div().flex_grow().child(self.picker.clone()))
}
fn render_active_prompt(
&mut self,
cx: &mut ModelContext<PromptLibrary>,
) -> gpui::Stateful<Div> {
fn render_active_prompt(&mut self, cx: &mut ViewContext<PromptLibrary>) -> gpui::Stateful<Div> {
div()
.w_2_3()
.h_full()
@@ -972,8 +873,8 @@ impl PromptLibrary {
.overflow_hidden()
.pl(DynamicSpacing::Base16.rems(cx))
.pt(DynamicSpacing::Base08.rems(cx))
.on_click(cx.listener(move |_, _, window, _| {
window.focus(&focus_handle);
.on_click(cx.listener(move |_, _, cx| {
cx.focus(&focus_handle);
}))
.child(
h_flex()
@@ -1056,7 +957,7 @@ impl PromptLibrary {
h_flex()
.id("token_count")
.tooltip(move |window, cx| {
.tooltip(move |cx| {
let token_count =
token_count.clone();
@@ -1075,7 +976,6 @@ impl PromptLibrary {
.0)
.unwrap_or_default()
),
window,
cx,
)
})
@@ -1095,12 +995,11 @@ impl PromptLibrary {
Icon::new(IconName::FileLock)
.color(Color::Muted),
)
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::with_meta(
"Built-in prompt",
None,
BUILT_IN_TOOLTIP_TEXT,
window,
cx,
)
})
@@ -1114,19 +1013,15 @@ impl PromptLibrary {
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
window,
cx,
)
})
.on_click(|_, window, cx| {
window.dispatch_action(
Box::new(DeletePrompt),
cx,
);
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
})
.into_any_element()
})
@@ -1139,19 +1034,17 @@ impl PromptLibrary {
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::for_action(
"Duplicate Prompt",
&DuplicatePrompt,
window,
cx,
)
})
.on_click(|_, window, cx| {
window.dispatch_action(
Box::new(DuplicatePrompt),
cx,
);
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
DuplicatePrompt,
));
}),
)
.child(
@@ -1169,18 +1062,20 @@ impl PromptLibrary {
})
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
))
.on_click(|_, window, cx| {
window.dispatch_action(
Box::new(ToggleDefaultPrompt),
.tooltip(move |cx| {
Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
);
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
ToggleDefaultPrompt,
));
}),
),
),
@@ -1201,24 +1096,18 @@ impl PromptLibrary {
}
impl Render for PromptLibrary {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
let theme = cx.theme().clone();
h_flex()
.id("prompt-manager")
.key_context("PromptLibrary")
.on_action(cx.listener(|this, &NewPrompt, window, cx| this.new_prompt(window, cx)))
.on_action(
cx.listener(|this, &DeletePrompt, window, cx| {
this.delete_active_prompt(window, cx)
}),
)
.on_action(cx.listener(|this, &DuplicatePrompt, window, cx| {
this.duplicate_active_prompt(window, cx)
}))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, window, cx| {
this.toggle_default_for_active_prompt(window, cx)
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
.on_action(cx.listener(|this, &DuplicatePrompt, cx| this.duplicate_active_prompt(cx)))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
this.toggle_default_for_active_prompt(cx)
}))
.size_full()
.overflow_hidden()
@@ -1260,13 +1149,10 @@ impl Render for PromptLibrary {
Button::new("create-prompt", "New Prompt")
.full_width()
.key_binding(KeyBinding::for_action(
&NewPrompt, window,
&NewPrompt, cx,
))
.on_click(|_, window, cx| {
window.dispatch_action(
NewPrompt.boxed_clone(),
cx,
)
.on_click(|_, cx| {
cx.dispatch_action(NewPrompt.boxed_clone())
}),
),
)

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::AfterCompletion;
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{AppContext, Model, ModelContext, Task, WeakModel, Window};
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
use parking_lot::Mutex;
use project::CompletionIntent;
@@ -43,8 +43,8 @@ pub mod terminal_command;
pub(crate) struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>,
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakModel<ContextEditor>>,
workspace: Option<WeakModel<Workspace>>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
}
pub(crate) struct SlashCommandLine {
@@ -57,8 +57,8 @@ pub(crate) struct SlashCommandLine {
impl SlashCommandCompletionProvider {
pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakModel<ContextEditor>>,
workspace: Option<WeakModel<Workspace>>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
@@ -73,8 +73,7 @@ impl SlashCommandCompletionProvider {
command_name: &str,
command_range: Range<Anchor>,
name_range: Range<Anchor>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<Result<Vec<project::Completion>>> {
let slash_commands = self.slash_commands.clone();
let candidates = slash_commands
@@ -86,7 +85,7 @@ impl SlashCommandCompletionProvider {
let command_name = command_name.to_string();
let editor = self.editor.clone();
let workspace = self.workspace.clone();
window.spawn(cx, |mut cx| async move {
cx.spawn(|mut cx| async move {
let matches = match_strings(
&candidates,
&command_name,
@@ -97,7 +96,7 @@ impl SlashCommandCompletionProvider {
)
.await;
cx.update(|_, cx| {
cx.update(|cx| {
matches
.into_iter()
.filter_map(|mat| {
@@ -119,31 +118,28 @@ impl SlashCommandCompletionProvider {
let editor = editor.clone();
let workspace = workspace.clone();
Arc::new(
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut AppContext| {
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<_>
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<_>
});
Some(project::Completion {
old_range: name_range.clone(),
@@ -168,8 +164,7 @@ impl SlashCommandCompletionProvider {
command_range: Range<Anchor>,
argument_range: Range<Anchor>,
last_argument_range: Range<Anchor>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<Result<Vec<project::Completion>>> {
let new_cancel_flag = Arc::new(AtomicBool::new(false));
let mut flag = self.cancel_flag.lock();
@@ -180,7 +175,6 @@ impl SlashCommandCompletionProvider {
arguments,
new_cancel_flag.clone(),
self.workspace.clone(),
window,
cx,
);
let command_name: Arc<str> = command_name.into();
@@ -208,30 +202,27 @@ impl SlashCommandCompletionProvider {
let command_range = command_range.clone();
let command_name = command_name.clone();
move |intent: CompletionIntent,
window: &mut Window,
cx: &mut AppContext| {
if new_argument.after_completion.run()
|| intent.is_complete()
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&completed_arguments,
true,
workspace.clone(),
window,
cx,
);
})
.ok();
false
} else {
!new_argument.after_completion.run()
move |intent: CompletionIntent, cx: &mut WindowContext| {
if new_argument.after_completion.run()
|| intent.is_complete()
{
editor
.update(cx, |editor, cx| {
editor.run_command(
command_range.clone(),
&command_name,
&completed_arguments,
true,
workspace.clone(),
cx,
);
})
.ok();
false
} else {
!new_argument.after_completion.run()
}
}
}
}) as Arc<_>
});
@@ -269,8 +260,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
buffer: &Model<Buffer>,
buffer_position: Anchor,
_: editor::CompletionContext,
window: &mut Window,
cx: &mut ModelContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Vec<project::Completion>>> {
let Some((name, arguments, command_range, last_argument_range)) =
buffer.update(cx, |buffer, _cx| {
@@ -325,11 +315,10 @@ impl CompletionProvider for SlashCommandCompletionProvider {
command_range,
argument_range,
last_argument_range,
window,
cx,
)
} else {
self.complete_command_name(&name, command_range, last_argument_range, window, cx)
self.complete_command_name(&name, command_range, last_argument_range, cx)
}
}
@@ -338,8 +327,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
_: Model<Buffer>,
_: Vec<usize>,
_: Rc<RefCell<Box<[project::Completion]>>>,
_window: &mut Window,
_: &mut ModelContext<Editor>,
_: &mut ViewContext<Editor>,
) -> Task<Result<bool>> {
Task::ready(Ok(true))
}
@@ -350,8 +338,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
_: &mut Window,
cx: &mut ModelContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> bool {
let buffer = buffer.read(cx);
let position = position.to_point(buffer);

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakModel, Window};
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
@@ -53,9 +53,8 @@ impl SlashCommand for AutoCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
cx: &mut AppContext,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> 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.
@@ -97,10 +96,9 @@ impl SlashCommand for AutoCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -117,7 +115,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("no project indexer")));
};
let task = window.spawn(cx, |cx| async move {
let task = cx.spawn(|cx: AsyncWindowContext| async move {
let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?;

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult,
};
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakModel};
use gpui::{AppContext, Model, Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use std::{
@@ -107,9 +107,8 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -123,10 +122,9 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> 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, WeakModel, Window};
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -77,9 +77,8 @@ impl SlashCommand for ContextServerSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> 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")));
@@ -129,10 +128,9 @@ impl SlashCommand for ContextServerSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone();

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakModel};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::{
fmt::Write,
@@ -36,9 +36,8 @@ impl SlashCommand for DefaultSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -48,10 +47,9 @@ impl SlashCommand for DefaultSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> 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::{AppContext, Task, WeakModel, Window};
use gpui::{Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use text::OffsetRangeExt;
@@ -40,9 +40,8 @@ impl SlashCommand for DeltaSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -52,10 +51,9 @@ impl SlashCommand for DeltaSlashCommand {
_arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new();
@@ -79,7 +77,6 @@ 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, WeakModel};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
OffsetRangeExt, ToOffset,
@@ -30,7 +30,7 @@ impl DiagnosticsSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Model<Workspace>,
workspace: &View<Workspace>,
cx: &mut AppContext,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
@@ -118,9 +118,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -173,10 +172,9 @@ impl SlashCommand for DiagnosticsSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -186,7 +184,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
window.spawn(cx, move |_| async move {
cx.spawn(move |_| async move {
task.await?
.map(|output| output.to_event_stream())
.ok_or_else(|| anyhow!("No diagnostics found"))

View File

@@ -8,7 +8,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakModel};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
@@ -43,7 +43,7 @@ impl DocsSlashCommand {
/// access the workspace so we can read the project.
fn ensure_rust_doc_providers_are_registered(
&self,
workspace: Option<WeakModel<Workspace>>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
@@ -164,9 +164,8 @@ impl SlashCommand for DocsSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
self.ensure_rust_doc_providers_are_registered(workspace, cx);
@@ -273,10 +272,9 @@ impl SlashCommand for DocsSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> 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, WeakModel};
use gpui::{Task, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -124,9 +124,8 @@ impl SlashCommand for FetchSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -136,10 +135,9 @@ impl SlashCommand for FetchSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> 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, WeakModel};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
@@ -28,7 +28,7 @@ impl FileSlashCommand {
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Model<Workspace>,
workspace: &View<Workspace>,
cx: &mut AppContext,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
@@ -134,9 +134,8 @@ impl SlashCommand for FileSlashCommand {
self: Arc<Self>,
arguments: &[String],
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
@@ -188,10 +187,9 @@ impl SlashCommand for FileSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakModel};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use ui::prelude::*;
@@ -37,9 +37,8 @@ impl SlashCommand for PromptSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_: &mut Window,
cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let store = PromptStore::global(cx);
let query = arguments.to_owned().join(" ");
@@ -65,10 +64,9 @@ impl SlashCommand for PromptSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> 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, WeakModel};
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use semantic_index::{LoadedSearchResult, SemanticDb};
use std::{
@@ -58,9 +58,8 @@ impl SlashCommand for SearchSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -70,10 +69,9 @@ impl SlashCommand for SearchSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@@ -109,7 +107,7 @@ impl SlashCommand for SearchSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
};
window.spawn(cx, |cx| async move {
cx.spawn(|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,11 +5,11 @@ use assistant_slash_command::{
SlashCommandOutputSection, SlashCommandResult,
};
use futures::StreamExt;
use gpui::{AppContext, Task, WeakModel};
use gpui::{AppContext, Task, WeakView};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use ui::{IconName, SharedString, Window};
use ui::{IconName, SharedString, WindowContext};
use workspace::Workspace;
pub(crate) struct SelectionCommand;
@@ -47,9 +47,8 @@ impl SlashCommand for SelectionCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -59,10 +58,9 @@ impl SlashCommand for SelectionCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let mut events = vec![];

View File

@@ -9,7 +9,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::channel::mpsc;
use gpui::{Task, WeakModel};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use smol::stream::StreamExt;
use smol::Timer;
@@ -45,9 +45,8 @@ impl SlashCommand for StreamingExampleSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
@@ -57,10 +56,9 @@ impl SlashCommand for StreamingExampleSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> 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, WeakModel};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool};
use ui::{AppContext, IconName, Window};
use ui::{IconName, WindowContext};
use workspace::Workspace;
pub(crate) struct OutlineSlashCommand;
@@ -34,9 +34,8 @@ impl SlashCommand for OutlineSlashCommand {
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
@@ -50,10 +49,9 @@ impl SlashCommand for OutlineSlashCommand {
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else {

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakModel};
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
@@ -10,7 +10,7 @@ use crate::SlashCommandWorkingSet;
#[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakModel<ContextEditor>,
active_context_editor: WeakView<ContextEditor>,
trigger: T,
}
@@ -27,8 +27,8 @@ enum SlashCommandEntry {
Info(SlashCommandInfo),
Advert {
name: SharedString,
renderer: fn(&mut Window, &mut AppContext) -> AnyElement,
on_confirm: fn(&mut Window, &mut AppContext),
renderer: fn(&mut WindowContext) -> AnyElement,
on_confirm: fn(&mut WindowContext),
},
}
@@ -44,14 +44,14 @@ impl AsRef<str> for SlashCommandEntry {
pub(crate) struct SlashCommandDelegate {
all_commands: Vec<SlashCommandEntry>,
filtered_commands: Vec<SlashCommandEntry>,
active_context_editor: WeakModel<ContextEditor>,
active_context_editor: WeakView<ContextEditor>,
selected_index: usize,
}
impl<T: PopoverTrigger> SlashCommandSelector<T> {
pub(crate) fn new(
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakModel<ContextEditor>,
active_context_editor: WeakView<ContextEditor>,
trigger: T,
) -> Self {
SlashCommandSelector {
@@ -73,28 +73,18 @@ impl PickerDelegate for SlashCommandDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
cx.notify();
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Select a command...".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let all_commands = self.all_commands.clone();
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let filtered_commands = cx
.background_executor()
.spawn(async move {
@@ -114,9 +104,9 @@ impl PickerDelegate for SlashCommandDelegate {
})
.await;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate.filtered_commands = filtered_commands;
this.delegate.set_selected_index(0, window, cx);
this.delegate.set_selected_index(0, cx);
cx.notify();
})
.ok();
@@ -149,30 +139,25 @@ impl PickerDelegate for SlashCommandDelegate {
ret
}
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<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, window, cx)
context_editor.insert_command(&info.name, cx)
})
.ok();
}
SlashCommandEntry::Advert { on_confirm, .. } => {
on_confirm(window, cx);
on_confirm(cx);
}
}
cx.emit(DismissEvent);
}
}
fn dismissed(&mut self, _window: &mut Window, _cx: &mut ModelContext<Picker<Self>>) {}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn editor_position(&self) -> PickerEditorPosition {
PickerEditorPosition::End
@@ -182,8 +167,7 @@ impl PickerDelegate for SlashCommandDelegate {
&self,
ix: usize,
selected: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let command_info = self.filtered_commands.get(ix)?;
@@ -195,7 +179,7 @@ impl PickerDelegate for SlashCommandDelegate {
.toggle_state(selected)
.tooltip({
let description = info.description.clone();
move |_, cx| cx.new_model(|_| Tooltip::new(description.clone())).into()
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
})
.child(
v_flex()
@@ -245,14 +229,14 @@ impl PickerDelegate for SlashCommandDelegate {
.inset(true)
.spacing(ListItemSpacing::Dense)
.toggle_state(selected)
.child(renderer(window, cx)),
.child(renderer(cx)),
),
}
}
}
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
fn render(self, window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let all_models = self
.working_set
.featured_command_names(cx)
@@ -275,7 +259,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(
@@ -309,7 +293,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<_>>();
@@ -320,9 +304,8 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
selected_index: 0,
};
let picker_view = cx.new_model(|cx| {
let picker =
Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()));
let picker_view = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
picker
});
@@ -331,7 +314,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
.update(cx, |this, _| this.slash_menu_handle.clone())
.ok();
PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(picker_view.clone()))
.menu(move |_cx| Some(picker_view.clone()))
.trigger(self.trigger)
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)

View File

@@ -13,8 +13,8 @@ use editor::{
use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{
AppContext, Context, EventEmitter, FocusHandle, Focusable, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, WeakModel,
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,
};
use language::Buffer;
use language_model::{
@@ -87,12 +87,11 @@ impl TerminalInlineAssistant {
pub fn assist(
&mut self,
terminal_view: &Model<TerminalView>,
workspace: Option<WeakModel<Workspace>>,
assistant_panel: Option<&Model<AssistantPanel>>,
terminal_view: &View<TerminalView>,
workspace: Option<WeakView<Workspace>>,
assistant_panel: Option<&View<AssistantPanel>>,
initial_prompt: Option<String>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc();
@@ -101,7 +100,7 @@ impl TerminalInlineAssistant {
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_editor = cx.new_model(|cx| {
let prompt_editor = cx.new_view(|cx| {
PromptEditor::new(
assist_id,
self.prompt_history.clone(),
@@ -110,7 +109,6 @@ impl TerminalInlineAssistant {
assistant_panel,
workspace.clone(),
self.fs.clone(),
window,
cx,
)
});
@@ -120,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, window, cx);
terminal_view.set_block_below_cursor(block, cx);
});
let terminal_assistant = TerminalInlineAssist::new(
@@ -129,27 +127,21 @@ impl TerminalInlineAssistant {
assistant_panel.is_some(),
prompt_editor,
workspace.clone(),
window,
cx,
);
self.assists.insert(assist_id, terminal_assistant);
self.focus_assist(assist_id, window, cx);
self.focus_assist(assist_id, cx);
}
fn focus_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut AppContext,
) {
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
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| {
window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, window, cx);
editor.focus(cx);
editor.select_all(&SelectAll, cx);
});
});
}
@@ -157,10 +149,9 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: Model<PromptEditor>,
prompt_editor: View<PromptEditor>,
event: &PromptEditorEvent,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
let assist_id = prompt_editor.read(cx).id;
match event {
@@ -171,21 +162,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx);
}
PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, window, cx);
self.finish_assist(assist_id, false, *execute, cx);
}
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, window, cx);
self.finish_assist(assist_id, true, false, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
}
}
}
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -223,7 +214,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
}
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -236,7 +227,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist(
&self,
assist_id: TerminalInlineAssistId,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?;
@@ -306,17 +297,16 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId,
undo: bool,
execute: bool,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, 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(window);
this.focus_handle(cx).focus(cx);
})
.log_err();
@@ -359,8 +349,7 @@ impl TerminalInlineAssistant {
fn dismiss_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else {
return false;
@@ -373,7 +362,7 @@ impl TerminalInlineAssistant {
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(window);
this.focus_handle(cx).focus(cx);
})
.is_ok()
}
@@ -382,8 +371,7 @@ impl TerminalInlineAssistant {
&mut self,
assist_id: TerminalInlineAssistId,
height: u8,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@@ -395,7 +383,7 @@ impl TerminalInlineAssistant {
height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
};
terminal.set_block_below_cursor(block, window, cx);
terminal.set_block_below_cursor(block, cx);
})
.log_err();
}
@@ -404,10 +392,10 @@ impl TerminalInlineAssistant {
}
struct TerminalInlineAssist {
terminal: WeakModel<TerminalView>,
prompt_editor: Option<Model<PromptEditor>>,
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor>>,
codegen: Model<Codegen>,
workspace: Option<WeakModel<Workspace>>,
workspace: Option<WeakView<Workspace>>,
include_context: bool,
_subscriptions: Vec<Subscription>,
}
@@ -415,12 +403,11 @@ struct TerminalInlineAssist {
impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &Model<TerminalView>,
terminal: &View<TerminalView>,
include_context: bool,
prompt_editor: Model<PromptEditor>,
workspace: Option<WeakModel<Workspace>>,
window: &mut Window,
cx: &mut AppContext,
prompt_editor: View<PromptEditor>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen.clone();
Self {
@@ -430,12 +417,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(),
include_context,
_subscriptions: vec![
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| {
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
this.handle_prompt_editor_event(prompt_editor, event, cx)
})
}),
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
cx.subscribe(&codegen, move |codegen, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) {
@@ -468,7 +455,7 @@ impl TerminalInlineAssist {
}
if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, window, cx);
this.finish_assist(assist_id, false, false, cx);
}
}
})
@@ -490,8 +477,8 @@ enum PromptEditorEvent {
struct PromptEditor {
id: TerminalInlineAssistId,
height_in_lines: u8,
editor: Model<Editor>,
language_model_selector: Model<LanguageModelSelector>,
editor: View<Editor>,
language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
@@ -502,13 +489,13 @@ struct PromptEditor {
pending_token_count: Task<Result<()>>,
token_count: Option<usize>,
_token_count_subscriptions: Vec<Subscription>,
workspace: Option<WeakModel<Workspace>>,
workspace: Option<WeakView<Workspace>>,
}
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status;
let buttons = match status {
CodegenStatus::Idle => {
@@ -516,20 +503,16 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, 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(|window, cx| {
Tooltip::for_action("Generate", &menu::Confirm, window, cx)
})
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
),
]
}
@@ -538,24 +521,23 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text("Cancel Assist"))
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.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(|window, cx| {
.tooltip(|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)),
),
]
}
@@ -563,12 +545,8 @@ impl Render for PromptEditor {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
);
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
@@ -577,16 +555,15 @@ impl Render for PromptEditor {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|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);
})),
]
@@ -596,29 +573,23 @@ impl Render for PromptEditor {
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
Tooltip::for_action(
"Accept Generated Command",
&menu::Confirm,
window,
cx,
)
.tooltip(|cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, 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(|window, cx| {
.tooltip(|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 });
})),
]
@@ -649,7 +620,7 @@ impl Render for PromptEditor {
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
.tooltip(move |cx| {
Tooltip::with_meta(
format!(
"Using {}",
@@ -660,7 +631,6 @@ impl Render for PromptEditor {
),
None,
"Change Model",
window,
cx,
)
}),
@@ -671,7 +641,7 @@ impl Render for PromptEditor {
Some(
div()
.id("error")
.tooltip(Tooltip::text(error_message))
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
@@ -694,7 +664,7 @@ impl Render for PromptEditor {
}
}
impl Focusable for PromptEditor {
impl FocusableView for PromptEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
@@ -709,13 +679,12 @@ impl PromptEditor {
prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<Codegen>,
assistant_panel: Option<&Model<AssistantPanel>>,
workspace: Option<WeakModel<Workspace>>,
assistant_panel: Option<&View<AssistantPanel>>,
workspace: Option<WeakView<Workspace>>,
fs: Arc<dyn Fs>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let prompt_editor = cx.new_model(|cx| {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -723,28 +692,24 @@ 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(window), cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor
});
let mut token_count_subscriptions = Vec::new();
if let Some(assistant_panel) = assistant_panel {
token_count_subscriptions.push(cx.subscribe_in(
assistant_panel,
window,
Self::handle_assistant_panel_event,
));
token_count_subscriptions
.push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
}
let mut this = Self {
id,
height_in_lines: 1,
editor: prompt_editor,
language_model_selector: cx.new_model(|cx| {
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
@@ -754,7 +719,6 @@ impl PromptEditor {
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
@@ -762,7 +726,7 @@ impl PromptEditor {
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
_codegen_subscription: cx.observe_in(&codegen, window, Self::handle_codegen_changed),
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(),
codegen,
pending_token_count: Task::ready(Ok(())),
@@ -776,15 +740,15 @@ impl PromptEditor {
this
}
fn placeholder_text(window: &mut Window) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, window)
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} • ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut ModelContext<Self>) {
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
@@ -796,7 +760,7 @@ impl PromptEditor {
self.editor.read(cx).text(cx)
}
fn count_lines(&mut self, cx: &mut ModelContext<Self>) {
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
@@ -814,16 +778,15 @@ impl PromptEditor {
fn handle_assistant_panel_event(
&mut self,
_: &Model<AssistantPanel>,
_: View<AssistantPanel>,
event: &AssistantPanelEvent,
_: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
let AssistantPanelEvent::ContextEdited { .. } = event;
self.count_tokens(cx);
}
fn count_tokens(&mut self, cx: &mut ModelContext<Self>) {
fn count_tokens(&mut self, cx: &mut ViewContext<Self>) {
let assist_id = self.id;
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
@@ -843,15 +806,15 @@ impl PromptEditor {
})
}
fn handle_prompt_editor_changed(&mut self, _: Model<Editor>, cx: &mut ModelContext<Self>) {
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
self.count_lines(cx);
}
fn handle_prompt_editor_events(
&mut self,
_: Model<Editor>,
_: View<Editor>,
event: &EditorEvent,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
@@ -874,12 +837,7 @@ impl PromptEditor {
}
}
fn handle_codegen_changed(
&mut self,
_: Model<Codegen>,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
self.editor
@@ -897,7 +855,7 @@ impl PromptEditor {
}
}
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut ModelContext<Self>) {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
@@ -908,7 +866,7 @@ impl PromptEditor {
}
}
fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut ModelContext<Self>) {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
if !self.editor.read(cx).text(cx).trim().is_empty() {
@@ -931,58 +889,53 @@ impl PromptEditor {
}
}
fn secondary_confirm(
&mut self,
_: &menu::SecondaryConfirm,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}
}
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut ModelContext<Self>) {
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<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, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), 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, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut ModelContext<Self>) {
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<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, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
}
}
}
fn render_token_count(&self, cx: &mut ModelContext<Self>) -> Option<impl IntoElement> {
fn render_token_count(&self, cx: &mut ViewContext<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();
@@ -1012,35 +965,34 @@ impl PromptEditor {
);
if let Some(workspace) = self.workspace.clone() {
token_count = token_count
.tooltip(|window, cx| {
.tooltip(|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 |_, window, cx| {
.on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
cx.stop_propagation();
workspace
.update(cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(window, cx)
workspace.focus_panel::<AssistantPanel>(cx)
})
.ok();
});
} else {
token_count = token_count
.cursor_default()
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
.tooltip(|cx| Tooltip::text("Tokens Used by Inline Assistant", cx));
}
Some(token_count)
}
fn render_prompt_editor(&self, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {

View File

@@ -50,7 +50,6 @@ parking_lot.workspace = true
picker.workspace = true
project.workspace = true
proto.workspace = true
release_channel.workspace = true
rope.workspace = true
schemars.workspace = true
serde.workspace = true

View File

@@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::{
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, WeakModel,
TextStyleRefinement, UnderlineStyle, View, WeakView,
};
use language::LanguageRegistry;
use language_model::Role;
@@ -19,13 +19,13 @@ use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
use crate::ui::ContextPill;
pub struct ActiveThread {
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
thread: Model<Thread>,
pub(crate) thread: Model<Thread>,
messages: Vec<MessageId>,
list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Model<Markdown>>,
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>,
}
@@ -33,15 +33,14 @@ pub struct ActiveThread {
impl ActiveThread {
pub fn new(
thread: Model<Thread>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&thread, window, Self::handle_thread_event),
cx.subscribe(&thread, Self::handle_thread_event),
];
let mut this = Self {
@@ -52,8 +51,8 @@ impl ActiveThread {
messages: Vec::new(),
rendered_messages_by_id: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.model().downgrade();
move |ix, _: &mut Window, cx: &mut AppContext| {
let this = cx.view().downgrade();
move |ix, cx: &mut WindowContext| {
this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
}
@@ -63,7 +62,7 @@ impl ActiveThread {
};
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
this.push_message(&message.id, message.text.clone(), window, cx);
this.push_message(&message.id, message.text.clone(), cx);
}
this
@@ -85,13 +84,7 @@ impl ActiveThread {
self.last_error.take();
}
fn push_message(
&mut self,
id: &MessageId,
text: String,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn push_message(&mut self, id: &MessageId, text: String, cx: &mut ViewContext<Self>) {
let old_len = self.messages.len();
self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1);
@@ -100,7 +93,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 = window.text_style();
let mut text_style = cx.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
@@ -147,16 +140,24 @@ impl ActiveThread {
background_color: Some(colors.editor_foreground.opacity(0.01)),
..Default::default()
},
link: TextStyleRefinement {
background_color: Some(colors.editor_foreground.opacity(0.025)),
underline: Some(UnderlineStyle {
color: Some(colors.text_accent.opacity(0.5)),
thickness: px(1.),
..Default::default()
}),
..Default::default()
},
..Default::default()
};
let markdown = cx.new_model(|cx| {
let markdown = cx.new_view(|cx| {
Markdown::new(
text,
markdown_style,
Some(self.language_registry.clone()),
None,
window,
cx,
)
});
@@ -169,10 +170,9 @@ impl ActiveThread {
fn handle_thread_event(
&mut self,
_: &Model<Thread>,
_: Model<Thread>,
event: &ThreadEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
ThreadEvent::ShowError(error) => {
@@ -183,7 +183,7 @@ impl ActiveThread {
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
markdown.update(cx, |markdown, cx| {
markdown.append(text, window, cx);
markdown.append(text, cx);
});
}
}
@@ -194,7 +194,7 @@ impl ActiveThread {
.message(*message_id)
.map(|message| message.text.clone())
{
self.push_message(message_id, message_text, window, cx);
self.push_message(message_id, message_text, cx);
}
cx.notify();
@@ -211,7 +211,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(), window, cx);
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(
@@ -228,7 +228,7 @@ impl ActiveThread {
}
}
fn render_message(&self, ix: usize, cx: &mut ModelContext<Self>) -> AnyElement {
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
@@ -283,12 +283,11 @@ impl ActiveThread {
.when_some(context, |parent, context| {
if !context.is_empty() {
parent.child(
h_flex()
.flex_wrap()
.gap_1()
.px_1p5()
.pb_1p5()
.children(context.iter().map(|c| ContextPill::new(c.clone()))),
h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
context
.iter()
.map(|context| ContextPill::new(context.clone())),
),
)
} else {
parent
@@ -300,7 +299,7 @@ impl ActiveThread {
}
impl Render for ActiveThread {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
list(self.list_state.clone()).flex_1().py_1()
}
}

View File

@@ -1,5 +1,5 @@
use fs::Fs;
use gpui::{Focusable, Model};
use gpui::View;
use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file;
@@ -9,7 +9,7 @@ use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::{assistant_settings::AssistantSettings, ToggleModelSelector};
pub struct AssistantModelSelector {
selector: Model<LanguageModelSelector>,
selector: View<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
}
@@ -17,11 +17,10 @@ impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Self {
Self {
selector: cx.new_model(|cx| {
selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
@@ -31,7 +30,6 @@ impl AssistantModelSelector {
move |settings, _cx| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
@@ -41,7 +39,7 @@ impl AssistantModelSelector {
}
impl Render for AssistantModelSelector {
fn render(&mut self, _window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.selector.focus_handle(cx).clone();
@@ -78,14 +76,8 @@ impl Render for AssistantModelSelector {
.size(IconSize::XSmall),
),
)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
}),
)
.with_handle(self.menu_handle.clone())

View File

@@ -6,7 +6,8 @@ use client::zed_urls;
use fs::Fs;
use gpui::{
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter,
FocusHandle, Focusable, FontWeight, Model, ModelContext, Pixels, Task, WeakModel, Window,
FocusHandle, FocusableView, FontWeight, Model, Pixels, Task, View, ViewContext, WeakView,
WindowContext,
};
use language::LanguageRegistry;
use settings::Settings;
@@ -18,28 +19,28 @@ use workspace::Workspace;
use crate::active_thread::ActiveThread;
use crate::assistant_settings::{AssistantDockPosition, AssistantSettings};
use crate::message_editor::MessageEditor;
use crate::thread::{ThreadError, ThreadId};
use crate::thread::{Thread, ThreadError, ThreadId};
use crate::thread_history::{PastThread, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::{NewThread, OpenHistory, ToggleFocus};
pub fn init(cx: &mut AppContext) {
cx.observe_new_models(
|workspace: &mut Workspace, _window, _cx: &mut ModelContext<Workspace>| {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace
.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
})
.register_action(|workspace, _: &NewThread, window, cx| {
.register_action(|workspace, _: &NewThread, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| panel.new_thread(window, cx));
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.new_thread(cx));
workspace.focus_panel::<AssistantPanel>(cx);
}
})
.register_action(|workspace, _: &OpenHistory, window, cx| {
.register_action(|workspace, _: &OpenHistory, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_history(window, cx));
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_history(cx));
}
});
},
@@ -53,25 +54,25 @@ enum ActiveView {
}
pub struct AssistantPanel {
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
thread_store: Model<ThreadStore>,
thread: Model<ActiveThread>,
message_editor: Model<MessageEditor>,
thread: View<ActiveThread>,
message_editor: View<MessageEditor>,
tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset,
active_view: ActiveView,
history: Model<ThreadHistory>,
history: View<ThreadHistory>,
width: Option<Pixels>,
height: Option<Pixels>,
}
impl AssistantPanel {
pub fn load(
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<Model<Self>>> {
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default());
let thread_store = workspace
@@ -81,8 +82,8 @@ impl AssistantPanel {
})?
.await?;
workspace.update_in(&mut cx, |workspace, window, cx| {
cx.new_model(|cx| Self::new(workspace, thread_store, tools, window, cx))
workspace.update(&mut cx, |workspace, cx| {
cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
})
})
}
@@ -91,14 +92,13 @@ impl AssistantPanel {
workspace: &Workspace,
thread_store: Model<ThreadStore>,
tools: Arc<ToolWorkingSet>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone();
let language_registry = workspace.project().read(cx).languages().clone();
let workspace = workspace.weak_handle();
let weak_self = cx.model().downgrade();
let weak_self = cx.view().downgrade();
Self {
active_view: ActiveView::Thread,
@@ -106,23 +106,21 @@ impl AssistantPanel {
fs: fs.clone(),
language_registry: language_registry.clone(),
thread_store: thread_store.clone(),
thread: cx.new_model(|cx| {
thread: cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
workspace.clone(),
language_registry,
tools.clone(),
window,
cx,
)
}),
message_editor: cx.new_model(|cx| {
message_editor: cx.new_view(|cx| {
MessageEditor::new(
fs.clone(),
workspace,
thread_store.downgrade(),
thread.clone(),
window,
cx,
)
}),
@@ -131,7 +129,7 @@ impl AssistantPanel {
chrono::Local::now().offset().local_minus_utc(),
)
.unwrap(),
history: cx.new_model(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
width: None,
height: None,
}
@@ -145,47 +143,40 @@ impl AssistantPanel {
&self.thread_store
}
fn new_thread(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
let thread = self
.thread_store
.update(cx, |this, cx| this.create_thread(cx));
self.active_view = ActiveView::Thread;
self.thread = cx.new_model(|cx| {
self.thread = cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
self.workspace.clone(),
self.language_registry.clone(),
self.tools.clone(),
window,
cx,
)
});
self.message_editor = cx.new_model(|cx| {
self.message_editor = cx.new_view(|cx| {
MessageEditor::new(
self.fs.clone(),
self.workspace.clone(),
self.thread_store.downgrade(),
thread,
window,
cx,
)
});
self.message_editor.focus_handle(cx).focus(window);
self.message_editor.focus_handle(cx).focus(cx);
}
fn open_history(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(window);
self.history.focus_handle(cx).focus(cx);
cx.notify();
}
pub(crate) fn open_thread(
&mut self,
thread_id: &ThreadId,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
let Some(thread) = self
.thread_store
.update(cx, |this, cx| this.open_thread(thread_id, cx))
@@ -194,36 +185,38 @@ impl AssistantPanel {
};
self.active_view = ActiveView::Thread;
self.thread = cx.new_model(|cx| {
self.thread = cx.new_view(|cx| {
ActiveThread::new(
thread.clone(),
self.workspace.clone(),
self.language_registry.clone(),
self.tools.clone(),
window,
cx,
)
});
self.message_editor = cx.new_model(|cx| {
self.message_editor = cx.new_view(|cx| {
MessageEditor::new(
self.fs.clone(),
self.workspace.clone(),
self.thread_store.downgrade(),
thread,
window,
cx,
)
});
self.message_editor.focus_handle(cx).focus(window);
self.message_editor.focus_handle(cx).focus(cx);
}
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ModelContext<Self>) {
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
self.thread.read(cx).thread.clone()
}
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
self.thread_store
.update(cx, |this, cx| this.delete_thread(thread_id, cx));
}
}
impl Focusable for AssistantPanel {
impl FocusableView for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx),
@@ -239,7 +232,7 @@ impl Panel for AssistantPanel {
"AssistantPanel2"
}
fn position(&self, _window: &Window, _cx: &AppContext) -> DockPosition {
fn position(&self, _cx: &WindowContext) -> DockPosition {
DockPosition::Right
}
@@ -247,12 +240,7 @@ impl Panel for AssistantPanel {
true
}
fn set_position(
&mut self,
position: DockPosition,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
settings::update_settings_file::<AssistantSettings>(
self.fs.clone(),
cx,
@@ -267,9 +255,9 @@ impl Panel for AssistantPanel {
);
}
fn size(&self, window: &Window, cx: &AppContext) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
let settings = AssistantSettings::get_global(cx);
match self.position(window, cx) {
match self.position(cx) {
DockPosition::Left | DockPosition::Right => {
self.width.unwrap_or(settings.default_width)
}
@@ -277,25 +265,25 @@ impl Panel for AssistantPanel {
}
}
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut ModelContext<Self>) {
match self.position(window, cx) {
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
match self.position(cx) {
DockPosition::Left | DockPosition::Right => self.width = size,
DockPosition::Bottom => self.height = size,
}
cx.notify();
}
fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut ModelContext<Self>) {}
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
fn remote_id() -> Option<proto::PanelId> {
Some(proto::PanelId::AssistantPanel)
}
fn icon(&self, _window: &Window, _cx: &AppContext) -> Option<IconName> {
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
Some(IconName::ZedAssistant2)
}
fn icon_tooltip(&self, _window: &Window, _cx: &AppContext) -> Option<&'static str> {
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
Some("Assistant Panel")
}
@@ -309,7 +297,7 @@ impl Panel for AssistantPanel {
}
impl AssistantPanel {
fn render_toolbar(&self, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
h_flex()
@@ -335,18 +323,17 @@ impl AssistantPanel {
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |cx| {
Tooltip::for_action_in(
"New Thread",
&NewThread,
&focus_handle,
window,
cx,
)
}
})
.on_click(move |_event, window, cx| {
window.dispatch_action(NewThread.boxed_clone(), cx);
.on_click(move |_event, cx| {
cx.dispatch_action(NewThread.boxed_clone());
}),
)
.child(
@@ -355,51 +342,40 @@ impl AssistantPanel {
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |cx| {
Tooltip::for_action_in(
"Open History",
&OpenHistory,
&focus_handle,
window,
cx,
)
}
})
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
}),
)
.child(
IconButton::new("configure-assistant", IconName::Settings)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(Tooltip::text("Configure Assistant"))
.on_click(move |_event, _window, _cx| {
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
.on_click(move |_event, _cx| {
println!("Configure Assistant");
}),
),
)
}
fn render_active_thread_or_empty_state(
&self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> AnyElement {
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
if self.thread.read(cx).is_empty() {
return self
.render_thread_empty_state(window, cx)
.into_any_element();
return self.render_thread_empty_state(cx).into_any_element();
}
self.thread.clone().into_any_element()
self.thread.clone().into_any()
}
fn render_thread_empty_state(
&self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> impl IntoElement {
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let recent_threads = self
.thread_store
.update(cx, |this, cx| this.recent_threads(3, cx));
@@ -430,7 +406,7 @@ impl AssistantPanel {
v_flex().mx_auto().w_4_5().gap_2().children(
recent_threads
.into_iter()
.map(|thread| PastThread::new(thread, cx.model().downgrade())),
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
),
)
.child(
@@ -441,17 +417,17 @@ impl AssistantPanel {
.key_binding(KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
))
.on_click(move |_event, window, cx| {
window.dispatch_action(OpenHistory.boxed_clone(), cx);
.on_click(move |_event, cx| {
cx.dispatch_action(OpenHistory.boxed_clone());
}),
),
)
})
}
fn render_last_error(&self, cx: &mut ModelContext<Self>) -> Option<AnyElement> {
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let last_error = self.thread.read(cx).last_error()?;
Some(
@@ -477,7 +453,7 @@ impl AssistantPanel {
)
}
fn render_payment_required_error(&self, cx: &mut ModelContext<Self>) -> AnyElement {
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
v_flex()
@@ -501,7 +477,7 @@ impl AssistantPanel {
.justify_end()
.mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -511,7 +487,7 @@ impl AssistantPanel {
},
)))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -523,7 +499,7 @@ impl AssistantPanel {
.into_any()
}
fn render_max_monthly_spend_reached_error(&self, cx: &mut ModelContext<Self>) -> AnyElement {
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
v_flex()
@@ -548,7 +524,7 @@ impl AssistantPanel {
.mt_1()
.child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, _, cx| {
cx.listener(|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -559,7 +535,7 @@ impl AssistantPanel {
),
)
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -574,7 +550,7 @@ impl AssistantPanel {
fn render_error_message(
&self,
error_message: &SharedString,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> AnyElement {
v_flex()
.gap_0p5()
@@ -600,7 +576,7 @@ impl AssistantPanel {
.justify_end()
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, _, cx| {
|this, _, cx| {
this.thread.update(cx, |this, _cx| {
this.clear_last_error();
});
@@ -614,21 +590,21 @@ impl AssistantPanel {
}
impl Render for AssistantPanel {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.key_context("AssistantPanel2")
.justify_between()
.size_full()
.on_action(cx.listener(|this, _: &NewThread, window, cx| {
this.new_thread(window, cx);
.on_action(cx.listener(|this, _: &NewThread, cx| {
this.new_thread(cx);
}))
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
this.open_history(window, cx);
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
this.open_history(cx);
}))
.child(self.render_toolbar(cx))
.map(|parent| match self.active_view {
ActiveView::Thread => parent
.child(self.render_active_thread_or_empty_state(window, cx))
.child(self.render_active_thread_or_empty_state(cx))
.child(
h_flex()
.border_t_1()

View File

@@ -257,17 +257,21 @@ impl CodegenAlternative {
) -> Self {
let snapshot = buffer.read(cx).snapshot(cx);
let (old_buffer, _, _) = buffer
.read(cx)
.range_to_buffer_ranges(range.clone(), cx)
.pop()
// TODO: Could be more efficient by using a reverse iterator.
let (old_excerpt, _) = snapshot
.range_to_buffer_ranges(range.clone())
.last()
.unwrap();
let old_buffer = cx.new_model(|cx| {
let old_buffer = old_buffer.read(cx);
let text = old_buffer.as_rope().clone();
let line_ending = old_buffer.line_ending();
let language = old_buffer.language().cloned();
let language_registry = old_buffer.language_registry();
let text = old_excerpt.buffer().as_rope().clone();
let line_ending = old_excerpt.buffer().line_ending();
let language = old_excerpt.buffer().language().cloned();
let language_registry = buffer
.read(cx)
.buffer(old_excerpt.buffer_id())
.unwrap()
.read(cx)
.language_registry();
let mut buffer = Buffer::local_normalized(text, line_ending, cx);
buffer.set_language(language, cx);
@@ -471,10 +475,11 @@ impl CodegenAlternative {
let telemetry = self.telemetry.clone();
let language_name = {
let multibuffer = self.buffer.read(cx);
let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx);
let snapshot = multibuffer.snapshot(cx);
let mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.next()
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
};

View File

@@ -1,8 +1,11 @@
use gpui::SharedString;
use language_model::{LanguageModelRequestMessage, MessageContent};
use project::ProjectEntryId;
use serde::{Deserialize, Serialize};
use util::post_inc;
use crate::thread::ThreadId;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct ContextId(pub(crate) usize);
@@ -21,12 +24,12 @@ pub struct Context {
pub text: SharedString,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContextKind {
File,
File(ProjectEntryId),
Directory,
FetchedUrl,
Thread,
Thread(ThreadId),
}
pub fn attach_context_to_message(
@@ -40,7 +43,7 @@ pub fn attach_context_to_message(
for context in context.into_iter() {
match context.kind {
ContextKind::File => {
ContextKind::File(_) => {
file_context.push_str(&context.text);
file_context.push('\n');
}
@@ -54,7 +57,7 @@ pub fn attach_context_to_message(
fetch_context.push_str(&context.text);
fetch_context.push('\n');
}
ContextKind::Thread => {
ContextKind::Thread(_) => {
thread_context.push_str(&context.name);
thread_context.push('\n');
thread_context.push_str(&context.text);

View File

@@ -6,16 +6,14 @@ mod thread_context_picker;
use std::sync::Arc;
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, Focusable, Model, SharedString, Task,
WeakModel,
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, SharedString, Task, View,
WeakModel, WeakView,
};
use picker::{Picker, PickerDelegate};
use release_channel::ReleaseChannel;
use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
use crate::context_picker::fetch_context_picker::FetchContextPicker;
use crate::context_picker::file_context_picker::FileContextPicker;
@@ -32,58 +30,52 @@ pub enum ConfirmBehavior {
#[derive(Debug, Clone)]
enum ContextPickerMode {
Default,
File(Model<FileContextPicker>),
Directory(Model<DirectoryContextPicker>),
Fetch(Model<FetchContextPicker>),
Thread(Model<ThreadContextPicker>),
File(View<FileContextPicker>),
Directory(View<DirectoryContextPicker>),
Fetch(View<FetchContextPicker>),
Thread(View<ThreadContextPicker>),
}
pub(super) struct ContextPicker {
mode: ContextPickerMode,
picker: Model<Picker<ContextPickerDelegate>>,
picker: View<Picker<ContextPickerDelegate>>,
}
impl ContextPicker {
pub fn new(
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let mut entries = Vec::new();
entries.push(ContextPickerEntry {
name: "File".into(),
kind: ContextKind::File,
kind: ContextPickerEntryKind::File,
icon: IconName::File,
});
let release_channel = ReleaseChannel::global(cx);
// The directory context picker isn't fully implemented yet, so limit it
// to development builds.
if release_channel == ReleaseChannel::Dev {
entries.push(ContextPickerEntry {
name: "Folder".into(),
kind: ContextKind::Directory,
icon: IconName::Folder,
});
}
entries.push(ContextPickerEntry {
name: "Folder".into(),
kind: ContextPickerEntryKind::Directory,
icon: IconName::Folder,
});
entries.push(ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,
kind: ContextPickerEntryKind::FetchedUrl,
icon: IconName::Globe,
});
if thread_store.is_some() {
entries.push(ContextPickerEntry {
name: "Thread".into(),
kind: ContextKind::Thread,
kind: ContextPickerEntryKind::Thread,
icon: IconName::MessageCircle,
});
}
let delegate = ContextPickerDelegate {
context_picker: cx.model().downgrade(),
context_picker: cx.view().downgrade(),
workspace,
thread_store,
context_store,
@@ -92,9 +84,8 @@ impl ContextPicker {
selected_ix: 0,
};
let picker = cx.new_model(|cx| {
Picker::nonsearchable_uniform_list(delegate, window, cx)
.max_height(Some(rems(20.).into()))
let picker = cx.new_view(|cx| {
Picker::nonsearchable_uniform_list(delegate, cx).max_height(Some(rems(20.).into()))
});
ContextPicker {
@@ -110,7 +101,7 @@ impl ContextPicker {
impl EventEmitter<DismissEvent> for ContextPicker {}
impl Focusable for ContextPicker {
impl FocusableView for ContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match &self.mode {
ContextPickerMode::Default => self.picker.focus_handle(cx),
@@ -123,7 +114,7 @@ impl Focusable for ContextPicker {
}
impl Render for ContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.w(px(400.))
.min_w(px(400.))
@@ -142,13 +133,21 @@ impl Render for ContextPicker {
#[derive(Clone)]
struct ContextPickerEntry {
name: SharedString,
kind: ContextKind,
kind: ContextPickerEntryKind,
icon: IconName,
}
#[derive(Debug, Clone)]
enum ContextPickerEntryKind {
File,
Directory,
FetchedUrl,
Thread,
}
pub(crate) struct ContextPickerDelegate {
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
@@ -167,84 +166,65 @@ impl PickerDelegate for ContextPickerDelegate {
self.selected_ix
}
fn set_selected_index(
&mut self,
ix: usize,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_ix = ix.min(self.entries.len().saturating_sub(1));
cx.notify();
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Select a context source…".into()
}
fn update_matches(
&mut self,
_query: String,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
Task::ready(())
}
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(entry) = self.entries.get(self.selected_ix) {
self.context_picker
.update(cx, |this, cx| {
match entry.kind {
ContextKind::File => {
this.mode = ContextPickerMode::File(cx.new_model(|cx| {
ContextPickerEntryKind::File => {
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
FileContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Directory => {
this.mode = ContextPickerMode::Directory(cx.new_model(|cx| {
ContextPickerEntryKind::Directory => {
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
DirectoryContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::FetchedUrl => {
this.mode = ContextPickerMode::Fetch(cx.new_model(|cx| {
ContextPickerEntryKind::FetchedUrl => {
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
FetchContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
}
ContextKind::Thread => {
ContextPickerEntryKind::Thread => {
if let Some(thread_store) = self.thread_store.as_ref() {
this.mode = ContextPickerMode::Thread(cx.new_model(|cx| {
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
ThreadContextPicker::new(
thread_store.clone(),
self.context_picker.clone(),
self.context_store.clone(),
self.confirm_behavior,
window,
cx,
)
}));
@@ -252,13 +232,13 @@ impl PickerDelegate for ContextPickerDelegate {
}
}
cx.focus_self(window);
cx.focus_self();
})
.log_err();
}
}
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| match this.mode {
ContextPickerMode::Default => cx.emit(DismissEvent),
@@ -274,8 +254,7 @@ impl PickerDelegate for ContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let entry = &self.entries[ix];

View File

@@ -1,34 +1,32 @@
// TODO: Remove this when we finish the implementation.
#![allow(unused)]
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::anyhow;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, WorktreeId};
use project::{PathMatchCandidateSet, ProjectPath, Worktree, WorktreeId};
use ui::{prelude::*, ListItem};
use util::ResultExt as _;
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::file_context_picker::codeblock_fence_for_path;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct DirectoryContextPicker {
picker: Model<Picker<DirectoryContextPickerDelegate>>,
picker: View<Picker<DirectoryContextPickerDelegate>>,
}
impl DirectoryContextPicker {
pub fn new(
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = DirectoryContextPickerDelegate::new(
context_picker,
@@ -36,27 +34,27 @@ impl DirectoryContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl Focusable for DirectoryContextPicker {
impl FocusableView for DirectoryContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for DirectoryContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct DirectoryContextPickerDelegate {
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
@@ -65,8 +63,8 @@ pub struct DirectoryContextPickerDelegate {
impl DirectoryContextPickerDelegate {
pub fn new(
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -84,9 +82,8 @@ impl DirectoryContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Model<Workspace>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -152,32 +149,22 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search folders…".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, window, cx);
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let mut paths = search_task.await;
let empty_path = Path::new("");
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
@@ -189,12 +176,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
})
}
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
@@ -209,15 +191,62 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
let path = mat.path.clone();
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn_in(window, |this, mut cx| async move {
this.update_in(&mut cx, |this, window, cx| {
cx.spawn(|this, mut cx| async move {
let worktree = project.update(&mut cx, |project, cx| {
project
.worktree_for_id(worktree_id, cx)
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
})??;
let files = worktree.update(&mut cx, |worktree, _cx| {
collect_files_in_path(worktree, &path)
})?;
let open_buffer_tasks = project.update(&mut cx, |project, cx| {
files
.into_iter()
.map(|file_path| {
project.open_buffer(
ProjectPath {
worktree_id,
path: file_path.clone(),
},
cx,
)
})
.collect::<Vec<_>>()
})?;
let open_all_buffers_tasks = cx.background_executor().spawn(async move {
let mut buffers = Vec::with_capacity(open_buffer_tasks.len());
for open_buffer_task in open_buffer_tasks {
let buffer = open_buffer_task.await?;
buffers.push(buffer);
}
anyhow::Ok(buffers)
});
let buffers = open_all_buffers_tasks.await?;
this.update(&mut cx, |this, cx| {
let mut text = String::new();
// TODO: Add the files from the selected directory.
for buffer in buffers {
text.push_str(&codeblock_fence_for_path(Some(&path), None));
text.push_str(&buffer.read(cx).text());
if !text.ends_with('\n') {
text.push('\n');
}
text.push_str("```\n");
}
this.delegate
.context_store
.update(cx, |context_store, cx| {
.update(cx, |context_store, _cx| {
context_store.insert_context(
ContextKind::Directory,
path.to_string_lossy().to_string(),
@@ -227,7 +256,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
@@ -238,7 +267,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
.detach_and_log_err(cx)
}
fn dismissed(&mut self, window: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -251,8 +280,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
let directory_name = path_match.path.to_string_lossy().to_string();
@@ -265,3 +293,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
)
}
}
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
let mut files = Vec::new();
for entry in worktree.child_entries(path) {
if entry.is_dir() {
files.extend(collect_files_in_path(worktree, &entry.path));
} else if entry.is_file() {
files.push(entry.path.clone());
}
}
files
}

View File

@@ -4,11 +4,11 @@ use std::sync::Arc;
use anyhow::{bail, Context as _, Result};
use futures::AsyncReadExt as _;
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClientWithUrl};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, ModelContext, Window};
use ui::{prelude::*, ListItem, ViewContext};
use workspace::Workspace;
use crate::context::ContextKind;
@@ -16,17 +16,16 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FetchContextPicker {
picker: Model<Picker<FetchContextPickerDelegate>>,
picker: View<Picker<FetchContextPickerDelegate>>,
}
impl FetchContextPicker {
pub fn new(
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FetchContextPickerDelegate::new(
context_picker,
@@ -34,20 +33,20 @@ impl FetchContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl Focusable for FetchContextPicker {
impl FocusableView for FetchContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FetchContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -60,8 +59,8 @@ enum ContentType {
}
pub struct FetchContextPickerDelegate {
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
url: String,
@@ -69,8 +68,8 @@ pub struct FetchContextPickerDelegate {
impl FetchContextPickerDelegate {
pub fn new(
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -167,7 +166,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
}
}
fn no_matches_text(&self, _window: &mut Window, _cx: &mut AppContext) -> SharedString {
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
"Enter the URL that you would like to fetch".into()
}
@@ -175,35 +174,19 @@ impl PickerDelegate for FetchContextPickerDelegate {
0
}
fn set_selected_index(
&mut self,
_ix: usize,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) {
}
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Enter a URL…".into()
}
fn update_matches(
&mut self,
query: String,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
self.url = query;
Task::ready(())
}
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
@@ -211,10 +194,10 @@ 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_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let text = Self::build_message(http_client, &url).await?;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, _cx| {
@@ -223,7 +206,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
@@ -234,7 +217,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -247,8 +230,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
Some(
ListItem::new(ix)

View File

@@ -5,9 +5,9 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, WorktreeId};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{prelude::*, ListItem};
use util::ResultExt as _;
use workspace::Workspace;
@@ -17,17 +17,16 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FileContextPicker {
picker: Model<Picker<FileContextPickerDelegate>>,
picker: View<Picker<FileContextPickerDelegate>>,
}
impl FileContextPicker {
pub fn new(
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FileContextPickerDelegate::new(
context_picker,
@@ -35,27 +34,27 @@ impl FileContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl Focusable for FileContextPicker {
impl FocusableView for FileContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FileContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
pub struct FileContextPickerDelegate {
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
@@ -64,8 +63,8 @@ pub struct FileContextPickerDelegate {
impl FileContextPickerDelegate {
pub fn new(
context_picker: WeakModel<ContextPicker>,
workspace: WeakModel<Workspace>,
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -83,9 +82,8 @@ impl FileContextPickerDelegate {
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &Model<Workspace>,
cx: &mut ModelContext<Picker<Self>>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
@@ -167,32 +165,22 @@ impl PickerDelegate for FileContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search files…".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<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_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
// TODO: This should be probably be run in the background.
let paths = search_task.await;
@@ -203,12 +191,7 @@ impl PickerDelegate for FileContextPickerDelegate {
})
}
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
@@ -223,19 +206,28 @@ impl PickerDelegate for FileContextPickerDelegate {
let path = mat.path.clone();
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn_in(window, |this, mut cx| async move {
let Some(open_buffer_task) = project
cx.spawn(|this, mut cx| async move {
let Some((entry_id, open_buffer_task)) = project
.update(&mut cx, |project, cx| {
project.open_buffer((worktree_id, path.clone()), cx)
let project_path = ProjectPath {
worktree_id,
path: path.clone(),
};
let entry_id = project.entry_for_path(&project_path, cx)?.id;
let task = project.open_buffer(project_path, cx);
Some((entry_id, task))
})
.ok()
.flatten()
else {
return anyhow::Ok(());
};
let buffer = open_buffer_task.await?;
this.update_in(&mut cx, |this, window, cx| {
this.update(&mut cx, |this, cx| {
this.delegate
.context_store
.update(cx, |context_store, cx| {
@@ -249,7 +241,7 @@ impl PickerDelegate for FileContextPickerDelegate {
text.push_str("```\n");
context_store.insert_context(
ContextKind::File,
ContextKind::File(entry_id),
path.to_string_lossy().to_string(),
text,
);
@@ -257,7 +249,7 @@ impl PickerDelegate for FileContextPickerDelegate {
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
@@ -268,7 +260,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.detach_and_log_err(cx);
}
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -281,8 +273,7 @@ impl PickerDelegate for FileContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
@@ -325,7 +316,10 @@ impl PickerDelegate for FileContextPickerDelegate {
}
}
fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<RangeInclusive<u32>>) -> String {
pub(crate) fn codeblock_fence_for_path(
path: Option<&Path>,
row_range: Option<RangeInclusive<u32>>,
) -> String {
let mut text = String::new();
write!(text, "```").unwrap();

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, DismissEvent, FocusHandle, Focusable, Model, Task, WeakModel};
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem};
@@ -12,17 +12,16 @@ use crate::thread::ThreadId;
use crate::thread_store::ThreadStore;
pub struct ThreadContextPicker {
picker: Model<Picker<ThreadContextPickerDelegate>>,
picker: View<Picker<ThreadContextPickerDelegate>>,
}
impl ThreadContextPicker {
pub fn new(
thread_store: WeakModel<ThreadStore>,
context_picker: WeakModel<ContextPicker>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = ThreadContextPickerDelegate::new(
thread_store,
@@ -30,20 +29,20 @@ impl ThreadContextPicker {
context_store,
confirm_behavior,
);
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
ThreadContextPicker { picker }
}
}
impl Focusable for ThreadContextPicker {
impl FocusableView for ThreadContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ThreadContextPicker {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
@@ -56,7 +55,7 @@ struct ThreadContextEntry {
pub struct ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakModel<ContextPicker>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>,
@@ -66,7 +65,7 @@ pub struct ThreadContextPickerDelegate {
impl ThreadContextPickerDelegate {
pub fn new(
thread_store: WeakModel<ThreadStore>,
context_picker: WeakModel<ContextPicker>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
@@ -92,25 +91,15 @@ impl PickerDelegate for ThreadContextPickerDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search threads…".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
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)
.into_iter()
@@ -153,7 +142,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
});
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
let matches = search_task.await;
this.update(&mut cx, |this, cx| {
this.delegate.matches = matches;
@@ -164,12 +153,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
})
}
fn confirm(
&mut self,
_secondary: bool,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
@@ -185,35 +169,21 @@ impl PickerDelegate for ThreadContextPickerDelegate {
self.context_store
.update(cx, |context_store, cx| {
let text = thread.update(cx, |thread, _cx| {
let mut text = String::new();
for message in thread.messages() {
text.push_str(match message.role {
language_model::Role::User => "User:",
language_model::Role::Assistant => "Assistant:",
language_model::Role::System => "System:",
});
text.push('\n');
text.push_str(&message.text);
text.push('\n');
}
text
});
context_store.insert_context(ContextKind::Thread, entry.summary.clone(), text);
context_store.insert_context(
ContextKind::Thread(thread.read(cx).id().clone()),
entry.summary.clone(),
thread.read(cx).text(),
);
})
.ok();
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(window, cx),
ConfirmBehavior::Close => self.dismissed(cx),
}
}
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
@@ -226,8 +196,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_window: &mut Window,
_cx: &mut ModelContext<Picker<Self>>,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let thread = &self.matches[ix];

View File

@@ -1,6 +1,10 @@
use gpui::SharedString;
use project::ProjectEntryId;
use crate::context::{Context, ContextId, ContextKind};
use crate::{
context::{Context, ContextId, ContextKind},
thread::ThreadId,
};
pub struct ContextStore {
context: Vec<Context>,
@@ -44,4 +48,18 @@ impl ContextStore {
pub fn remove_context(&mut self, id: &ContextId) {
self.context.retain(|context| context.id != *id);
}
pub fn contains_project_entry(&self, entry_id: ProjectEntryId) -> bool {
self.context.iter().any(|probe| match probe.kind {
ContextKind::File(probe_entry_id) => probe_entry_id == entry_id,
ContextKind::Directory | ContextKind::FetchedUrl | ContextKind::Thread(_) => false,
})
}
pub fn contains_thread(&self, thread_id: &ThreadId) -> bool {
self.context.iter().any(|probe| match probe.kind {
ContextKind::Thread(ref probe_thread_id) => probe_thread_id == thread_id,
ContextKind::File(_) | ContextKind::Directory | ContextKind::FetchedUrl => false,
})
}
}

View File

@@ -1,75 +1,143 @@
use std::rc::Rc;
use gpui::{FocusHandle, Model, WeakModel};
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
use editor::Editor;
use gpui::{AppContext, FocusHandle, Model, View, WeakModel, WeakView};
use language::Buffer;
use project::ProjectEntryId;
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::thread::{Thread, ThreadId};
use crate::thread_store::ThreadStore;
use crate::ui::ContextPill;
use crate::ToggleContextPicker;
use settings::Settings;
use crate::{AssistantPanel, ToggleContextPicker};
pub struct ContextStrip {
context_store: Model<ContextStore>,
context_picker: Model<ContextPicker>,
context_picker: View<ContextPicker>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle,
suggest_context_kind: SuggestContextKind,
workspace: WeakView<Workspace>,
}
impl ContextStrip {
pub fn new(
context_store: Model<ContextStore>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
focus_handle: FocusHandle,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
window: &mut Window,
cx: &mut ModelContext<Self>,
suggest_context_kind: SuggestContextKind,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
context_store: context_store.clone(),
context_picker: cx.new_model(|cx| {
context_picker: cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
thread_store.clone(),
context_store.downgrade(),
ConfirmBehavior::KeepOpen,
window,
cx,
)
}),
context_picker_menu_handle,
focus_handle,
suggest_context_kind,
workspace,
}
}
fn suggested_context(&self, cx: &ViewContext<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> {
let workspace = self.workspace.upgrade()?;
let active_item = workspace.read(cx).active_item(cx)?;
let entry_id = *active_item.project_entry_ids(cx).first()?;
if self.context_store.read(cx).contains_project_entry(entry_id) {
return None;
}
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
let active_buffer = editor.buffer().read(cx).as_singleton()?;
let file = active_buffer.read(cx).file()?;
let title = file.path().to_string_lossy().into_owned().into();
Some(SuggestedContext::File {
entry_id,
title,
buffer: active_buffer.downgrade(),
})
}
fn suggested_thread(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
let workspace = self.workspace.upgrade()?;
let active_thread = workspace
.read(cx)
.panel::<AssistantPanel>(cx)?
.read(cx)
.active_thread(cx);
let weak_active_thread = active_thread.downgrade();
let active_thread = active_thread.read(cx);
if self
.context_store
.read(cx)
.contains_thread(active_thread.id())
{
return None;
}
Some(SuggestedContext::Thread {
id: active_thread.id().clone(),
title: active_thread.summary().unwrap_or("Active Thread".into()),
thread: weak_active_thread,
})
}
}
impl Render for ContextStrip {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let context = self.context_store.read(cx).context().clone();
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let context_store = self.context_store.read(cx);
let context = context_store.context().clone();
let context_picker = self.context_picker.clone();
let focus_handle = self.focus_handle.clone();
let suggested_context = self.suggested_context(cx);
h_flex()
.flex_wrap()
.gap_1()
.child(
PopoverMenu::new("context-picker")
.menu(move |_window, _cx| Some(context_picker.clone()))
.menu(move |_cx| Some(context_picker.clone()))
.trigger(
IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
cx,
)
}
}),
)
.attach(gpui::Corner::TopLeft)
@@ -80,26 +148,22 @@ impl Render for ContextStrip {
})
.with_handle(self.context_picker_menu_handle.clone()),
)
.when(context.is_empty(), {
.when(context.is_empty() && suggested_context.is_none(), {
|parent| {
parent.child(
h_flex()
.id("no-content-info")
.ml_1p5()
.gap_2()
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::Small.rems(cx))
.text_color(cx.theme().colors().text_muted)
.child("Add Context")
.children(
ui::KeyBinding::for_action_in(
&ToggleContextPicker,
&self.focus_handle,
window,
)
.map(|binding| binding.into_any_element()),
.child(
Label::new("Add Context")
.size(LabelSize::Small)
.color(Color::Muted),
)
.opacity(0.5),
.opacity(0.5)
.children(
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
),
)
}
})
@@ -107,7 +171,7 @@ impl Render for ContextStrip {
ContextPill::new(context.clone()).on_remove({
let context = context.clone();
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, _, cx| {
Rc::new(cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| {
this.remove_context(&context.id);
});
@@ -115,15 +179,39 @@ impl Render for ContextStrip {
}))
})
}))
.when_some(suggested_context, |el, suggested| {
el.child(
Button::new("add-suggested-context", suggested.title().clone())
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, cx| {
context_store.update(cx, |context_store, cx| {
suggested.accept(context_store, cx);
});
cx.notify();
})
})
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.style(ButtonStyle::Filled)
.tooltip(|cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
}),
)
})
.when(!context.is_empty(), {
move |parent| {
parent.child(
IconButton::new("remove-all-context", IconName::Eraser)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Remove All Context"))
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, _, cx| {
cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| this.clear());
cx.notify();
})
@@ -133,3 +221,63 @@ impl Render for ContextStrip {
})
}
}
pub enum SuggestContextKind {
File,
Thread,
}
#[derive(Clone)]
pub enum SuggestedContext {
File {
entry_id: ProjectEntryId,
title: SharedString,
buffer: WeakModel<Buffer>,
},
Thread {
id: ThreadId,
title: SharedString,
thread: WeakModel<Thread>,
},
}
impl SuggestedContext {
pub fn title(&self) -> &SharedString {
match self {
Self::File { title, .. } => title,
Self::Thread { title, .. } => title,
}
}
pub fn accept(&self, context_store: &mut ContextStore, cx: &mut AppContext) {
match self {
Self::File {
entry_id,
title,
buffer,
} => {
let Some(buffer) = buffer.upgrade() else {
return;
};
let text = buffer.read(cx).text();
context_store.insert_context(
ContextKind::File(*entry_id),
title.clone(),
text.clone(),
);
}
Self::Thread { id, title, thread } => {
let Some(thread) = thread.upgrade() else {
return;
};
context_store.insert_context(
ContextKind::Thread(id.clone()),
title.clone(),
thread.read(cx).text(),
);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ use crate::assistant_model_selector::AssistantModelSelector;
use crate::buffer_codegen::BufferCodegen;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::context_strip::{ContextStrip, SuggestContextKind};
use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
@@ -17,8 +17,8 @@ use feature_flags::{FeatureFlagAppExt as _, ZedPro};
use fs::Fs;
use gpui::{
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
FocusHandle, Focusable, FontWeight, Model, ModelContext, Subscription, TextStyle, WeakModel,
Window,
FocusHandle, FocusableView, FontWeight, Model, Subscription, TextStyle, View, ViewContext,
WeakModel, WeakView, WindowContext,
};
use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
@@ -34,11 +34,11 @@ use util::ResultExt;
use workspace::Workspace;
pub struct PromptEditor<T> {
pub editor: Model<Editor>,
pub editor: View<Editor>,
mode: PromptEditorMode,
context_strip: Model<ContextStrip>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Model<AssistantModelSelector>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
@@ -53,7 +53,7 @@ pub struct PromptEditor<T> {
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
impl<T: 'static> Render for PromptEditor<T> {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let mut buttons = Vec::new();
let left_gutter_spacing = match &self.mode {
@@ -83,7 +83,7 @@ impl<T: 'static> Render for PromptEditor<T> {
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
};
buttons.extend(self.render_buttons(window, cx));
buttons.extend(self.render_buttons(cx));
v_flex()
.key_context("PromptEditor")
@@ -154,7 +154,9 @@ impl<T: 'static> Render for PromptEditor<T> {
el.child(
div()
.id("error")
.tooltip(Tooltip::text(error_message))
.tooltip(move |cx| {
Tooltip::text(error_message.clone(), cx)
})
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
@@ -168,7 +170,7 @@ impl<T: 'static> Render for PromptEditor<T> {
h_flex()
.w_full()
.justify_between()
.child(div().flex_1().child(self.render_editor(window, cx)))
.child(div().flex_1().child(self.render_editor(cx)))
.child(h_flex().gap_1().children(buttons)),
),
)
@@ -186,7 +188,7 @@ impl<T: 'static> Render for PromptEditor<T> {
}
}
impl<T: 'static> Focusable for PromptEditor<T> {
impl<T: 'static> FocusableView for PromptEditor<T> {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
@@ -202,47 +204,40 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn subscribe_to_editor(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions.push(cx.subscribe_in(
&self.editor,
window,
Self::handle_prompt_editor_events,
));
self.editor_subscriptions
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
}
pub fn set_show_cursor_when_unfocused(
&mut self,
show_cursor_when_unfocused: bool,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
});
}
pub fn unlink(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
pub fn unlink(&mut self, cx: &mut ViewContext<Self>) {
let prompt = self.prompt(cx);
let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
self.editor = cx.new_model(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, 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);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(&self.mode, window, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&self.mode, cx), cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor.set_text(prompt, window, cx);
editor.set_text(prompt, cx);
if focus {
window.focus(&editor.focus_handle(cx));
editor.focus(cx);
}
editor
});
self.subscribe_to_editor(window, cx);
self.subscribe_to_editor(cx);
}
pub fn placeholder_text(
mode: &PromptEditorMode,
window: &mut Window,
cx: &mut AppContext,
) -> String {
pub fn placeholder_text(mode: &PromptEditorMode, cx: &WindowContext) -> String {
let action = match mode {
PromptEditorMode::Buffer { codegen, .. } => {
if codegen.read(cx).is_insertion {
@@ -254,7 +249,7 @@ impl<T: 'static> PromptEditor<T> {
PromptEditorMode::Terminal { .. } => "Generate",
};
let assistant_panel_keybinding = ui::text_for_action(&crate::ToggleFocus, window)
let assistant_panel_keybinding = ui::text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} to chat ― "))
.unwrap_or_default();
@@ -265,31 +260,25 @@ impl<T: 'static> PromptEditor<T> {
self.editor.read(cx).text(cx)
}
fn toggle_rate_limit_notice(
&mut self,
_: &ClickEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
self.show_rate_limit_notice = !self.show_rate_limit_notice;
if self.show_rate_limit_notice {
window.focus(&self.editor.focus_handle(cx));
cx.focus_view(&self.editor);
}
cx.notify();
}
fn handle_prompt_editor_events(
&mut self,
_: &Model<Editor>,
_: View<Editor>,
event: &EditorEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, _, cx| {
.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
@@ -323,30 +312,15 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn toggle_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx);
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx);
}
fn cancel(
&mut self,
_: &editor::actions::Cancel,
_window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
@@ -357,7 +331,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut ModelContext<Self>) {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle => {
cx.emit(PromptEditorEvent::StartRequested);
@@ -378,47 +352,47 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut ModelContext<Self>) {
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<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, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), 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, window, cx);
editor.move_to_beginning(&Default::default(), window, cx);
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut ModelContext<Self>) {
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<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, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), window, cx)
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
}
}
}
fn render_buttons(&self, _window: &mut Window, cx: &mut ModelContext<Self>) -> Vec<AnyElement> {
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
let mode = match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
let codegen = codegen.read(cx);
@@ -440,22 +414,21 @@ 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 |window, cx| {
.tooltip(move |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(_));
@@ -463,16 +436,15 @@ impl<T: 'static> PromptEditor<T> {
vec![IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |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 +452,10 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
.tooltip(move |cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element();
@@ -494,15 +466,14 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| {
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}))
.into_any_element(),
@@ -514,12 +485,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn cycle_prev(
&mut self,
_: &CyclePreviousInlineAssist,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_prev(cx));
@@ -530,12 +496,7 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn cycle_next(
&mut self,
_: &CycleNextInlineAssist,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext<Self>) {
match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_next(cx));
@@ -546,20 +507,16 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn render_close_button(&self, cx: &mut ModelContext<Self>) -> AnyElement {
fn render_close_button(&self, cx: &ViewContext<Self>) -> AnyElement {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text("Close Assistant"))
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element()
}
fn render_cycle_controls(
&self,
codegen: &BufferCodegen,
cx: &ModelContext<Self>,
) -> AnyElement {
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &ViewContext<Self>) -> AnyElement {
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
let model_registry = LanguageModelRegistry::read_global(cx);
@@ -599,13 +556,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
cx.new_model(|_| {
move |cx| {
cx.new_view(|cx| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
window,
cx,
),
);
if !disabled && current_index != 0 {
@@ -616,8 +573,8 @@ impl<T: 'static> PromptEditor<T> {
.into()
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, window, cx);
.on_click(cx.listener(|this, _, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, cx);
})),
)
.child(
@@ -640,13 +597,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
cx.new_model(|_| {
move |cx| {
cx.new_view(|cx| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
window,
cx,
),
);
if !disabled && current_index != total_models - 1 {
@@ -657,14 +614,14 @@ impl<T: 'static> PromptEditor<T> {
.into()
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.cycle_next(&CycleNextInlineAssist, window, cx)
})),
.on_click(
cx.listener(|this, _, cx| this.cycle_next(&CycleNextInlineAssist, cx)),
),
)
.into_any_element()
}
fn render_rate_limit_notice(&self, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render_rate_limit_notice(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
Popover::new().child(
v_flex()
.occlude()
@@ -688,7 +645,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,
@@ -707,11 +664,10 @@ impl<T: 'static> PromptEditor<T> {
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
)
.child(Button::new("more-info", "More Info").on_click(
|_event, window, cx| {
window.dispatch_action(
Box::new(zed_actions::OpenAccountSettings),
cx,
)
|_event, cx| {
cx.dispatch_action(Box::new(
zed_actions::OpenAccountSettings,
))
},
)),
),
@@ -719,9 +675,9 @@ impl<T: 'static> PromptEditor<T> {
)
}
fn render_editor(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> AnyElement {
fn render_editor(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
div()
.key_context("MessageEditor")
@@ -796,10 +752,9 @@ impl PromptEditor<BufferCodegen> {
codegen: Model<BufferCodegen>,
fs: Arc<dyn Fs>,
context_store: Model<ContextStore>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
window: &mut Window,
cx: &mut ModelContext<PromptEditor<BufferCodegen>>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
) -> PromptEditor<BufferCodegen> {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Buffer {
@@ -808,7 +763,7 @@ impl PromptEditor<BufferCodegen> {
gutter_dimensions,
};
let prompt_editor = cx.new_model(|cx| {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -816,7 +771,6 @@ impl PromptEditor<BufferCodegen> {
prompt_buffer,
None,
false,
window,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
@@ -824,7 +778,7 @@ 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, window, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
@@ -832,20 +786,20 @@ impl PromptEditor<BufferCodegen> {
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
editor: prompt_editor.clone(),
context_strip: cx.new_model(|cx| {
context_strip: cx.new_view(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
window,
SuggestContextKind::Thread,
cx,
)
}),
context_picker_menu_handle,
model_selector: cx.new_model(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), window, cx)
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
}),
model_selector_menu_handle,
edited_since_done: false,
@@ -859,14 +813,14 @@ impl PromptEditor<BufferCodegen> {
_phantom: Default::default(),
};
this.subscribe_to_editor(window, cx);
this.subscribe_to_editor(cx);
this
}
fn handle_codegen_changed(
&mut self,
_: Model<BufferCodegen>,
cx: &mut ModelContext<PromptEditor<BufferCodegen>>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
) {
match self.codegen_status(cx) {
CodegenStatus::Idle => {
@@ -942,10 +896,9 @@ impl PromptEditor<TerminalCodegen> {
codegen: Model<TerminalCodegen>,
fs: Arc<dyn Fs>,
context_store: Model<ContextStore>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Terminal {
@@ -954,7 +907,7 @@ impl PromptEditor<TerminalCodegen> {
height_in_lines: 1,
};
let prompt_editor = cx.new_model(|cx| {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
@@ -962,11 +915,10 @@ 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, window, cx), cx);
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
@@ -974,20 +926,20 @@ impl PromptEditor<TerminalCodegen> {
let mut this = Self {
editor: prompt_editor.clone(),
context_strip: cx.new_model(|cx| {
context_strip: cx.new_view(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
window,
SuggestContextKind::Thread,
cx,
)
}),
context_picker_menu_handle,
model_selector: cx.new_model(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), window, cx)
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
}),
model_selector_menu_handle,
edited_since_done: false,
@@ -1001,11 +953,11 @@ impl PromptEditor<TerminalCodegen> {
_phantom: Default::default(),
};
this.count_lines(cx);
this.subscribe_to_editor(window, cx);
this.subscribe_to_editor(cx);
this
}
fn count_lines(&mut self, cx: &mut ModelContext<Self>) {
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
@@ -1029,7 +981,7 @@ impl PromptEditor<TerminalCodegen> {
}
}
fn handle_codegen_changed(&mut self, _: Model<TerminalCodegen>, cx: &mut ModelContext<Self>) {
fn handle_codegen_changed(&mut self, _: Model<TerminalCodegen>, cx: &mut ViewContext<Self>) {
match &self.codegen().read(cx).status {
CodegenStatus::Idle => {
self.editor

View File

@@ -2,7 +2,10 @@ use std::sync::Arc;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs;
use gpui::{AppContext, DismissEvent, Focusable, Model, Subscription, TextStyle, WeakModel};
use gpui::{
AppContext, DismissEvent, FocusableView, Model, Subscription, TextStyle, View, WeakModel,
WeakView,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
use rope::Point;
@@ -17,20 +20,20 @@ use workspace::Workspace;
use crate::assistant_model_selector::AssistantModelSelector;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::context_strip::{ContextStrip, SuggestContextKind};
use crate::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore;
use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor {
thread: Model<Thread>,
editor: Model<Editor>,
editor: View<Editor>,
context_store: Model<ContextStore>,
context_strip: Model<ContextStrip>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: Model<ContextPicker>,
inline_context_picker: View<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Model<AssistantModelSelector>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
@@ -39,39 +42,36 @@ pub struct MessageEditor {
impl MessageEditor {
pub fn new(
fs: Arc<dyn Fs>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
thread_store: WeakModel<ThreadStore>,
thread: Model<Thread>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let context_store = cx.new_model(|_cx| ContextStore::new());
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_model(|cx| {
let mut editor = Editor::auto_height(10, window, cx);
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(10, cx);
editor.set_placeholder_text("Ask anything…", cx);
editor.set_show_indent_guides(false, cx);
editor
});
let inline_context_picker = cx.new_model(|cx| {
let inline_context_picker = cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
Some(thread_store.clone()),
context_store.downgrade(),
ConfirmBehavior::Close,
window,
cx,
)
});
let subscriptions = vec![
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(
&inline_context_picker,
window,
Self::handle_inline_context_picker_event,
),
];
@@ -80,22 +80,22 @@ impl MessageEditor {
thread,
editor: editor.clone(),
context_store: context_store.clone(),
context_strip: cx.new_model(|cx| {
context_strip: cx.new_view(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
Some(thread_store.clone()),
editor.focus_handle(cx),
context_picker_menu_handle.clone(),
window,
SuggestContextKind::File,
cx,
)
}),
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new_model(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), window, cx)
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
}),
model_selector_menu_handle,
use_tools: false,
@@ -103,33 +103,22 @@ impl MessageEditor {
}
}
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.model_selector_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_context_picker(
&mut self,
_: &ToggleContextPicker,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.context_picker_menu_handle.toggle(window, cx);
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut ModelContext<Self>) {
self.send_to_model(RequestKind::Chat, window, cx);
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx);
}
fn send_to_model(
&mut self,
request_kind: RequestKind,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Option<()> {
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
@@ -145,7 +134,7 @@ impl MessageEditor {
let user_message = self.editor.update(cx, |editor, cx| {
let text = editor.text(cx);
editor.clear(window, cx);
editor.clear(cx);
text
});
let context = self.context_store.update(cx, |this, _cx| this.drain());
@@ -175,10 +164,9 @@ impl MessageEditor {
fn handle_editor_event(
&mut self,
editor: &Model<Editor>,
editor: View<Editor>,
event: &EditorEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::SelectionsChanged { .. } => {
@@ -189,7 +177,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(window, cx);
self.inline_context_picker_menu_handle.show(cx);
}
}
});
@@ -200,26 +188,25 @@ impl MessageEditor {
fn handle_inline_context_picker_event(
&mut self,
_inline_context_picker: &Model<ContextPicker>,
_inline_context_picker: View<ContextPicker>,
_event: &DismissEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
window.focus(&editor_focus_handle);
cx.focus(&editor_focus_handle);
}
}
impl Focusable for MessageEditor {
impl FocusableView for MessageEditor {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl Render for MessageEditor {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
let line_height = font_size.to_pixels(cx.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;
@@ -261,7 +248,7 @@ impl Render for MessageEditor {
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |_window, _cx| Some(inline_context_picker.clone()))
.menu(move |_cx| Some(inline_context_picker.clone()))
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
@@ -277,7 +264,7 @@ impl Render for MessageEditor {
"use-tools",
Label::new("Tools"),
self.use_tools.into(),
cx.listener(|this, selection, _window, _cx| {
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => {
@@ -293,11 +280,11 @@ impl Render for MessageEditor {
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit"))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, window)
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, window, cx);
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
}),
),
),

View File

@@ -11,7 +11,10 @@ use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque};
use editor::{actions::SelectAll, MultiBuffer};
use fs::Fs;
use gpui::{AppContext, Context, Focusable, Global, Model, Subscription, UpdateGlobal, WeakModel};
use gpui::{
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
WeakView,
};
use language::Buffer;
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
@@ -65,11 +68,10 @@ impl TerminalInlineAssistant {
pub fn assist(
&mut self,
terminal_view: &Model<TerminalView>,
workspace: WeakModel<Workspace>,
terminal_view: &View<TerminalView>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc();
@@ -79,7 +81,7 @@ impl TerminalInlineAssistant {
let context_store = cx.new_model(|_cx| ContextStore::new());
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_model(|cx| {
let prompt_editor = cx.new_view(|cx| {
PromptEditor::new_terminal(
assist_id,
self.prompt_history.clone(),
@@ -89,7 +91,6 @@ impl TerminalInlineAssistant {
context_store.clone(),
workspace.clone(),
thread_store.clone(),
window,
cx,
)
});
@@ -99,7 +100,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, window, cx);
terminal_view.set_block_below_cursor(block, cx);
});
let terminal_assistant = TerminalInlineAssist::new(
@@ -108,27 +109,21 @@ impl TerminalInlineAssistant {
prompt_editor,
workspace.clone(),
context_store,
window,
cx,
);
self.assists.insert(assist_id, terminal_assistant);
self.focus_assist(assist_id, window, cx);
self.focus_assist(assist_id, cx);
}
fn focus_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut AppContext,
) {
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
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| {
window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, window, cx);
editor.focus(cx);
editor.select_all(&SelectAll, cx);
});
});
}
@@ -136,10 +131,9 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: Model<PromptEditor<TerminalCodegen>>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
event: &PromptEditorEvent,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
let assist_id = prompt_editor.read(cx).id();
match event {
@@ -150,21 +144,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx);
}
PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, window, cx);
self.finish_assist(assist_id, false, *execute, cx);
}
PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, window, cx);
self.finish_assist(assist_id, true, false, cx);
}
PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, cx);
}
PromptEditorEvent::Resized { height_in_lines } => {
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
}
}
}
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -202,7 +196,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
}
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut AppContext) {
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist
} else {
@@ -215,7 +209,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist(
&self,
assist_id: TerminalInlineAssistId,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?;
@@ -271,17 +265,16 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId,
undo: bool,
execute: bool,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
self.dismiss_assist(assist_id, window, cx);
self.dismiss_assist(assist_id, 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(window);
this.focus_handle(cx).focus(cx);
})
.log_err();
@@ -324,8 +317,7 @@ impl TerminalInlineAssistant {
fn dismiss_assist(
&mut self,
assist_id: TerminalInlineAssistId,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else {
return false;
@@ -338,7 +330,7 @@ impl TerminalInlineAssistant {
.terminal
.update(cx, |this, cx| {
this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(window);
this.focus_handle(cx).focus(cx);
})
.is_ok()
}
@@ -347,8 +339,7 @@ impl TerminalInlineAssistant {
&mut self,
assist_id: TerminalInlineAssistId,
height: u8,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) {
if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@@ -360,7 +351,7 @@ impl TerminalInlineAssistant {
height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
};
terminal.set_block_below_cursor(block, window, cx);
terminal.set_block_below_cursor(block, cx);
})
.log_err();
}
@@ -369,10 +360,10 @@ impl TerminalInlineAssistant {
}
struct TerminalInlineAssist {
terminal: WeakModel<TerminalView>,
prompt_editor: Option<Model<PromptEditor<TerminalCodegen>>>,
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
codegen: Model<TerminalCodegen>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
_subscriptions: Vec<Subscription>,
}
@@ -380,12 +371,11 @@ struct TerminalInlineAssist {
impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &Model<TerminalView>,
prompt_editor: Model<PromptEditor<TerminalCodegen>>,
workspace: WeakModel<Workspace>,
terminal: &View<TerminalView>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen().clone();
Self {
@@ -395,12 +385,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(),
context_store,
_subscriptions: vec![
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| {
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
this.handle_prompt_editor_event(prompt_editor, event, cx)
})
}),
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
cx.subscribe(&codegen, move |codegen, event, cx| {
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) {
@@ -429,7 +419,7 @@ impl TerminalInlineAssist {
}
if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, window, cx);
this.finish_assist(assist_id, false, false, cx);
}
}
})

View File

@@ -164,6 +164,27 @@ impl Thread {
id
}
/// Returns the representation of this [`Thread`] in a textual form.
///
/// This is the representation we use when attaching a thread as context to another thread.
pub fn text(&self) -> String {
let mut text = String::new();
for message in &self.messages {
text.push_str(match message.role {
language_model::Role::User => "User:",
language_model::Role::Assistant => "Assistant:",
language_model::Role::System => "System:",
});
text.push('\n');
text.push_str(&message.text);
text.push('\n');
}
text
}
pub fn to_completion_request(
&self,
_request_kind: RequestKind,

View File

@@ -1,5 +1,5 @@
use gpui::{
uniform_list, AppContext, FocusHandle, Focusable, Model, UniformListScrollHandle, WeakModel,
uniform_list, AppContext, FocusHandle, FocusableView, Model, UniformListScrollHandle, WeakView,
};
use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
@@ -10,17 +10,16 @@ use crate::AssistantPanel;
pub struct ThreadHistory {
focus_handle: FocusHandle,
assistant_panel: WeakModel<AssistantPanel>,
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
scroll_handle: UniformListScrollHandle,
}
impl ThreadHistory {
pub(crate) fn new(
assistant_panel: WeakModel<AssistantPanel>,
assistant_panel: WeakView<AssistantPanel>,
thread_store: Model<ThreadStore>,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
focus_handle: cx.focus_handle(),
@@ -31,14 +30,14 @@ impl ThreadHistory {
}
}
impl Focusable for ThreadHistory {
impl FocusableView for ThreadHistory {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ThreadHistory {
fn render(&mut self, _window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let threads = self.thread_store.update(cx, |this, cx| this.threads(cx));
v_flex()
@@ -60,10 +59,10 @@ impl Render for ThreadHistory {
} else {
history.child(
uniform_list(
cx.model().clone(),
cx.view().clone(),
"thread-history",
threads.len(),
move |history, range, _window, _cx| {
move |history, range, _cx| {
threads[range]
.iter()
.map(|thread| {
@@ -86,11 +85,11 @@ impl Render for ThreadHistory {
#[derive(IntoElement)]
pub struct PastThread {
thread: Model<Thread>,
assistant_panel: WeakModel<AssistantPanel>,
assistant_panel: WeakView<AssistantPanel>,
}
impl PastThread {
pub fn new(thread: Model<Thread>, assistant_panel: WeakModel<AssistantPanel>) -> Self {
pub fn new(thread: Model<Thread>, assistant_panel: WeakView<AssistantPanel>) -> Self {
Self {
thread,
assistant_panel,
@@ -99,7 +98,7 @@ impl PastThread {
}
impl RenderOnce for PastThread {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let (id, summary) = {
const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");
let thread = self.thread.read(cx);
@@ -140,11 +139,11 @@ impl RenderOnce for PastThread {
IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Delete Thread"))
.tooltip(|cx| Tooltip::text("Delete Thread", cx))
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = id.clone();
move |_event, _, cx| {
move |_event, cx| {
assistant_panel
.update(cx, |this, cx| {
this.delete_thread(&id, cx);
@@ -157,10 +156,10 @@ impl RenderOnce for PastThread {
.on_click({
let assistant_panel = self.assistant_panel.clone();
let id = id.clone();
move |_event, window, cx| {
move |_event, cx| {
assistant_panel
.update(cx, |this, cx| {
this.open_thread(&id, window, cx);
this.open_thread(&id, cx);
})
.ok();
}

View File

@@ -238,5 +238,46 @@ impl ThreadStore {
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);
}
```"#.unindent(), cx);
thread
}));
}
}

View File

@@ -8,7 +8,7 @@ use crate::context::{Context, ContextKind};
#[derive(IntoElement)]
pub struct ContextPill {
context: Context,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut AppContext)>>,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
}
impl ContextPill {
@@ -19,27 +19,24 @@ impl ContextPill {
}
}
pub fn on_remove(
mut self,
on_remove: Rc<dyn Fn(&ClickEvent, &mut Window, &mut AppContext)>,
) -> Self {
pub fn on_remove(mut self, on_remove: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
self.on_remove = Some(on_remove);
self
}
}
impl RenderOnce for ContextPill {
fn render(self, _window: &mut Window, cx: &mut AppContext) -> impl IntoElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let padding_right = if self.on_remove.is_some() {
px(2.)
} else {
px(4.)
};
let icon = match self.context.kind {
ContextKind::File => IconName::File,
ContextKind::File(_) => IconName::File,
ContextKind::Directory => IconName::Folder,
ContextKind::FetchedUrl => IconName::Globe,
ContextKind::Thread => IconName::MessageCircle,
ContextKind::Thread(_) => IconName::MessageCircle,
};
h_flex()
@@ -60,7 +57,7 @@ impl RenderOnce for ContextPill {
.icon_size(IconSize::XSmall)
.on_click({
let on_remove = on_remove.clone();
move |event, window, cx| on_remove(event, window, cx)
move |event, cx| on_remove(event, cx)
}),
)
})

View File

@@ -6,7 +6,7 @@ pub use crate::slash_command_registry::*;
use anyhow::Result;
use futures::stream::{self, BoxStream};
use futures::StreamExt;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakModel, Window};
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role;
use serde::{Deserialize, Serialize};
@@ -78,9 +78,8 @@ pub trait SlashCommand: 'static + Send + Sync {
self: Arc<Self>,
arguments: &[String],
cancel: Arc<AtomicBool>,
workspace: Option<WeakModel<Workspace>>,
window: &mut Window,
cx: &mut AppContext,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>>;
fn requires_argument(&self) -> bool;
fn accepts_arguments(&self) -> bool {
@@ -91,27 +90,21 @@ pub trait SlashCommand: 'static + Send + Sync {
arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakModel<Workspace>,
workspace: WeakView<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>>,
window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult>;
}
pub type RenderFoldPlaceholder = Arc<
dyn Send
+ Sync
+ Fn(
ElementId,
Arc<dyn Fn(&mut Window, &mut AppContext)>,
&mut Window,
&mut AppContext,
) -> AnyElement,
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
>;
#[derive(Debug, PartialEq)]

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, WeakModel, Window};
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -97,9 +97,8 @@ impl SlashCommand for ExtensionSlashCommand {
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakModel<Workspace>>,
_window: &mut Window,
cx: &mut AppContext,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let command = self.command.clone();
let arguments = arguments.to_owned();
@@ -128,10 +127,9 @@ impl SlashCommand for ExtensionSlashCommand {
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakModel<Workspace>,
_workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
_window: &mut Window,
cx: &mut AppContext,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let command = self.command.clone();
let arguments = arguments.to_owned();

View File

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

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Result};
use assistant_tool::Tool;
use chrono::{Local, Utc};
use gpui::{AppContext, Task, WeakModel, Window};
use gpui::{Task, WeakView, WindowContext};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -41,9 +41,8 @@ impl Tool for NowTool {
fn run(
self: Arc<Self>,
input: serde_json::Value,
_workspace: WeakModel<workspace::Workspace>,
_window: &mut Window,
_cx: &mut AppContext,
_workspace: WeakView<workspace::Workspace>,
_cx: &mut WindowContext,
) -> Task<Result<String>> {
let input: FileToolInput = match serde_json::from_value(input) {
Ok(input) => input,

View File

@@ -4,7 +4,7 @@ use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, Task, Window,
SemanticVersion, Task, WindowContext,
};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use paths::remote_servers_dir;
@@ -130,10 +130,10 @@ impl Global for GlobalAutoUpdate {}
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_models(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(|_, action: &Check, window, cx| check(action, window, cx));
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|_, action: &Check, cx| check(action, cx));
workspace.register_action(|_, action, _, cx| {
workspace.register_action(|_, action, cx| {
view_release_notes(action, cx);
});
})
@@ -172,25 +172,23 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
}
pub fn check(_: &Check, window: &mut Window, cx: &mut AppContext) {
pub fn check(_: &Check, cx: &mut WindowContext) {
if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") {
drop(window.prompt(
drop(cx.prompt(
gpui::PromptLevel::Info,
"Zed was installed via a package manager.",
Some(message),
&["Ok"],
cx,
));
return;
}
if let Ok(message) = env::var("ZED_UPDATE_EXPLANATION") {
drop(window.prompt(
drop(cx.prompt(
gpui::PromptLevel::Info,
"Zed was installed via a package manager.",
Some(&message),
&["Ok"],
cx,
));
return;
}
@@ -205,12 +203,11 @@ pub fn check(_: &Check, window: &mut Window, cx: &mut AppContext) {
if let Some(updater) = AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx));
} else {
drop(window.prompt(
drop(cx.prompt(
gpui::PromptLevel::Info,
"Could not check for updates",
Some("Auto-updates disabled for non-bundled app."),
&["Ok"],
cx,
));
}
}

View File

@@ -2,7 +2,7 @@ mod update_notification;
use auto_update::AutoUpdater;
use editor::{Editor, MultiBuffer};
use gpui::{actions, prelude::*, AppContext, Model, ModelContext, SharedString, Window};
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
use http_client::HttpClient;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use release_channel::{AppVersion, ReleaseChannel};
@@ -17,9 +17,9 @@ use crate::update_notification::UpdateNotification;
actions!(auto_update, [ViewReleaseNotesLocally]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_models(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, window, cx| {
view_release_notes_locally(workspace, window, cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
view_release_notes_locally(workspace, cx);
});
})
.detach();
@@ -31,11 +31,7 @@ struct ReleaseNotesBody {
release_notes: String,
}
fn view_release_notes_locally(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) {
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let url = match release_channel {
@@ -64,8 +60,8 @@ fn view_release_notes_locally(
.language_for_name("Markdown");
workspace
.with_local_workspace(window, cx, move |_, window, cx| {
cx.spawn_in(window, |workspace, mut cx| async move {
.with_local_workspace(cx, move |_, cx| {
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else {
@@ -80,7 +76,7 @@ fn view_release_notes_locally(
if let Ok(body) = body {
workspace
.update_in(&mut cx, |workspace, window, cx| {
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
@@ -93,24 +89,22 @@ fn view_release_notes_locally(
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_model(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, window, cx)
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx)
});
let workspace_handle = workspace.weak_handle();
let view: Model<MarkdownPreviewView> = MarkdownPreviewView::new(
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
language_registry,
Some(tab_description),
window,
cx,
);
workspace.add_item_to_active_pane(
Box::new(view.clone()),
None,
true,
window,
cx,
);
cx.notify();
@@ -123,15 +117,12 @@ fn view_release_notes_locally(
.detach();
}
pub fn notify_of_any_new_update(
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Option<()> {
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version();
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn_in(window, |workspace, mut cx| async move {
cx.spawn(|workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
@@ -139,7 +130,7 @@ pub fn notify_of_any_new_update(
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_model(|_| UpdateNotification::new(version, workspace_handle)),
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
);
updater.update(cx, |updater, cx| {
updater

View File

@@ -1,6 +1,6 @@
use gpui::{
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ModelContext, ParentElement,
Render, SemanticVersion, StatefulInteractiveElement, Styled, WeakModel, Window,
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render,
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, WeakView,
};
use menu::Cancel;
use release_channel::ReleaseChannel;
@@ -12,13 +12,13 @@ use workspace::{
pub struct UpdateNotification {
version: SemanticVersion,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
}
impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let app_name = ReleaseChannel::global(cx).display_name();
v_flex()
@@ -37,9 +37,7 @@ impl Render for UpdateNotification {
.id("cancel")
.child(Icon::new(IconName::Close))
.cursor_pointer()
.on_click(cx.listener(|this, _, window, cx| {
this.dismiss(&menu::Cancel, window, cx)
})),
.on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
),
)
.child(
@@ -47,24 +45,24 @@ impl Render for UpdateNotification {
.id("notes")
.child(Label::new("View the release notes"))
.cursor_pointer()
.on_click(cx.listener(|this, _, window, cx| {
.on_click(cx.listener(|this, _, cx| {
this.workspace
.update(cx, |workspace, cx| {
crate::view_release_notes_locally(workspace, window, cx);
crate::view_release_notes_locally(workspace, cx);
})
.log_err();
this.dismiss(&menu::Cancel, window, cx)
this.dismiss(&menu::Cancel, cx)
})),
)
}
}
impl UpdateNotification {
pub fn new(version: SemanticVersion, workspace: WeakModel<Workspace>) -> Self {
pub fn new(version: SemanticVersion, workspace: WeakView<Workspace>) -> Self {
Self { version, workspace }
}
pub fn dismiss(&mut self, _: &Cancel, _: &mut Window, cx: &mut ModelContext<Self>) {
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
}
}

View File

@@ -1,7 +1,7 @@
use editor::Editor;
use gpui::{
Element, EventEmitter, Focusable, IntoElement, ModelContext, ParentElement, Render, StyledText,
Subscription, Window,
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
Subscription, ViewContext,
};
use itertools::Itertools;
use std::cmp;
@@ -37,7 +37,7 @@ impl Breadcrumbs {
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex()
@@ -72,7 +72,7 @@ impl Render for Breadcrumbs {
}
let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = window.text_style();
let mut text_style = cx.text_style();
if let Some(font) = segment.font {
text_style.font_family = font.family;
text_style.font_features = font.features;
@@ -101,30 +101,28 @@ impl Render for Breadcrumbs {
.style(ButtonStyle::Transparent)
.on_click({
let editor = editor.clone();
move |_, window, cx| {
move |_, cx| {
if let Some((editor, callback)) = editor
.upgrade()
.zip(zed_actions::outline::TOGGLE_OUTLINE.get())
{
callback(editor.to_any(), window, cx);
callback(editor.to_any(), cx);
}
}
})
.tooltip(move |window, cx| {
.tooltip(move |cx| {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
&focus_handle,
window,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
)
}
@@ -142,8 +140,7 @@ impl ToolbarItemView for Breadcrumbs {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
cx.notify();
self.active_item = None;
@@ -152,11 +149,10 @@ impl ToolbarItemView for Breadcrumbs {
return ToolbarItemLocation::Hidden;
};
let this = cx.model().downgrade();
let this = cx.view().downgrade();
self.subscription = Some(item.subscribe_to_item_events(
window,
cx,
Box::new(move |event, _, cx| {
Box::new(move |event, cx| {
if let ItemEvent::UpdateBreadcrumbs = event {
this.update(cx, |this, cx| {
cx.notify();
@@ -174,12 +170,7 @@ impl ToolbarItemView for Breadcrumbs {
item.breadcrumb_location(cx)
}
fn pane_focus_update(
&mut self,
pane_focused: bool,
_window: &mut Window,
_: &mut ModelContext<Self>,
) {
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
self.pane_focused = pane_focused;
}
}

View File

@@ -81,7 +81,7 @@ impl ChannelBuffer {
collaborators: Default::default(),
acknowledge_task: None,
channel_id: channel.id,
subscription: Some(subscription.set_model(&cx.model(), &mut cx.to_async())),
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
user_store,
channel_store,
};

View File

@@ -134,7 +134,7 @@ impl ChannelChat {
last_acknowledged_id: None,
rng: StdRng::from_entropy(),
first_loaded_message_id: None,
_subscription: subscription.set_model(&cx.model(), &mut cx.to_async()),
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
}
})?;
Self::handle_loaded_messages(

View File

@@ -311,7 +311,7 @@ impl ChannelStore {
) -> Task<Result<Model<ChannelBuffer>>> {
let client = self.client.clone();
let user_store = self.user_store.clone();
let channel_store = cx.model();
let channel_store = cx.handle();
self.open_channel_resource(
channel_id,
|this| &mut this.opened_buffers,
@@ -441,7 +441,7 @@ impl ChannelStore {
) -> Task<Result<Model<ChannelChat>>> {
let client = self.client.clone();
let user_store = self.user_store.clone();
let this = cx.model();
let this = cx.handle();
self.open_channel_resource(
channel_id,
|this| &mut this.opened_chats,

View File

@@ -2,4 +2,10 @@ fn main() {
if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() {
println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#);
}
if cfg!(target_os = "macos") {
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
// Weakly link ScreenCaptureKit to ensure can be used on macOS 10.15+.
println!("cargo:rustc-link-arg=-Wl,-weak_framework,ScreenCaptureKit");
}
}

View File

@@ -79,7 +79,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
Ok(existing_path) => PathWithPosition::from_path(existing_path),
Err(_) => {
let path = PathWithPosition::parse_str(argument_str);
let curdir = env::current_dir().context("reteiving current directory")?;
let curdir = env::current_dir().context("retrieving current directory")?;
path.map_path(|path| match fs::canonicalize(&path) {
Ok(path) => Ok(path),
Err(e) => {
@@ -257,7 +257,6 @@ fn main() -> Result<()> {
if args.foreground {
app.run_foreground(url)?;
} else {
eprintln!("Logs are written to {:?}", paths::log_file());
app.launch(url)?;
sender.join().unwrap()?;
pipe_handle.join().unwrap()?;

View File

@@ -106,6 +106,22 @@ CREATE TABLE "worktree_repositories" (
CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
CREATE TABLE "worktree_repository_statuses" (
"project_id" INTEGER NOT NULL,
"worktree_id" INT8 NOT NULL,
"work_directory_id" INT8 NOT NULL,
"repo_path" VARCHAR NOT NULL,
"status" INT8 NOT NULL,
"scan_id" INT8 NOT NULL,
"is_deleted" BOOL NOT NULL,
PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
);
CREATE INDEX "index_wt_repos_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id_and_wd_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
CREATE TABLE "worktree_settings_files" (
"project_id" INTEGER NOT NULL,
"worktree_id" INTEGER NOT NULL,

View File

@@ -2,11 +2,7 @@ use collab::env::get_dotenv_vars;
fn main() -> anyhow::Result<()> {
for (key, value) in get_dotenv_vars(".")? {
if option_env!("POWERSHELL").is_some() {
println!("$Env:{} = \"{}\"", key, value);
} else {
println!("export {}=\"{}\"", key, value);
}
println!("export {}=\"{}\"", key, value);
}
Ok(())
}

View File

@@ -1,4 +1,5 @@
use anyhow::Context as _;
use util::ResultExt;
use super::*;
@@ -274,8 +275,8 @@ impl Database {
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
canonical_path: ActiveValue::set(entry.canonical_path.clone()),
is_ignored: ActiveValue::set(entry.is_ignored),
git_status: ActiveValue::set(None),
is_external: ActiveValue::set(entry.is_external),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
is_deleted: ActiveValue::set(false),
scan_id: ActiveValue::set(update.scan_id as i64),
is_fifo: ActiveValue::set(entry.is_fifo),
@@ -295,7 +296,6 @@ impl Database {
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::CanonicalPath,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
])
.to_owned(),
@@ -349,6 +349,79 @@ impl Database {
)
.exec(&*tx)
.await?;
let has_any_statuses = update
.updated_repositories
.iter()
.any(|repository| !repository.updated_statuses.is_empty());
if has_any_statuses {
worktree_repository_statuses::Entity::insert_many(
update.updated_repositories.iter().flat_map(
|repository: &proto::RepositoryEntry| {
repository.updated_statuses.iter().map(|status_entry| {
worktree_repository_statuses::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
work_directory_id: ActiveValue::set(
repository.work_directory_id as i64,
),
scan_id: ActiveValue::set(update.scan_id as i64),
is_deleted: ActiveValue::set(false),
repo_path: ActiveValue::set(status_entry.repo_path.clone()),
status: ActiveValue::set(status_entry.status as i64),
}
})
},
),
)
.on_conflict(
OnConflict::columns([
worktree_repository_statuses::Column::ProjectId,
worktree_repository_statuses::Column::WorktreeId,
worktree_repository_statuses::Column::WorkDirectoryId,
worktree_repository_statuses::Column::RepoPath,
])
.update_columns([
worktree_repository_statuses::Column::ScanId,
worktree_repository_statuses::Column::Status,
])
.to_owned(),
)
.exec(&*tx)
.await?;
}
let has_any_removed_statuses = update
.updated_repositories
.iter()
.any(|repository| !repository.removed_statuses.is_empty());
if has_any_removed_statuses {
worktree_repository_statuses::Entity::update_many()
.filter(
worktree_repository_statuses::Column::ProjectId
.eq(project_id)
.and(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree_id),
)
.and(
worktree_repository_statuses::Column::RepoPath.is_in(
update.updated_repositories.iter().flat_map(|repository| {
repository.removed_statuses.iter()
}),
),
),
)
.set(worktree_repository_statuses::ActiveModel {
is_deleted: ActiveValue::Set(true),
scan_id: ActiveValue::Set(update.scan_id as i64),
..Default::default()
})
.exec(&*tx)
.await?;
}
}
if !update.removed_repositories.is_empty() {
@@ -643,7 +716,6 @@ impl Database {
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go
@@ -657,23 +729,49 @@ impl Database {
// Populate repository entries.
{
let mut db_repository_entries = worktree_repository::Entity::find()
let db_repository_entries = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::IsDeleted.eq(false)),
)
.stream(tx)
.all(tx)
.await?;
while let Some(db_repository_entry) = db_repository_entries.next().await {
let db_repository_entry = db_repository_entry?;
for db_repository_entry in db_repository_entries {
if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
{
let mut repository_statuses = worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(worktree_repository_statuses::Column::ProjectId.eq(project.id))
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),
)
.add(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(db_repository_entry.work_directory_id),
)
.add(worktree_repository_statuses::Column::IsDeleted.eq(false)),
)
.stream(tx)
.await?;
let mut updated_statuses = Vec::new();
while let Some(status_entry) = repository_statuses.next().await {
let status_entry: worktree_repository_statuses::Model = status_entry?;
updated_statuses.push(proto::StatusEntry {
repo_path: status_entry.repo_path,
status: status_entry.status as i32,
});
}
worktree.repository_entries.insert(
db_repository_entry.work_directory_id as u64,
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
updated_statuses,
removed_statuses: Vec::new(),
},
);
}

View File

@@ -662,7 +662,6 @@ impl Database {
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go
@@ -682,26 +681,69 @@ impl Database {
worktree_repository::Column::IsDeleted.eq(false)
};
let mut db_repositories = worktree_repository::Entity::find()
let db_repositories = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::WorktreeId.eq(worktree.id))
.add(repository_entry_filter),
)
.stream(tx)
.all(tx)
.await?;
while let Some(db_repository) = db_repositories.next().await {
let db_repository = db_repository?;
for db_repository in db_repositories.into_iter() {
if db_repository.is_deleted {
worktree
.removed_repositories
.push(db_repository.work_directory_id as u64);
} else {
let status_entry_filter = if let Some(rejoined_worktree) = rejoined_worktree
{
worktree_repository_statuses::Column::ScanId
.gt(rejoined_worktree.scan_id)
} else {
worktree_repository_statuses::Column::IsDeleted.eq(false)
};
let mut db_statuses = worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(
worktree_repository_statuses::Column::ProjectId
.eq(project.id),
)
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),
)
.add(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(db_repository.work_directory_id),
)
.add(status_entry_filter),
)
.stream(tx)
.await?;
let mut removed_statuses = Vec::new();
let mut updated_statuses = Vec::new();
while let Some(db_status) = db_statuses.next().await {
let db_status: worktree_repository_statuses::Model = db_status?;
if db_status.is_deleted {
removed_statuses.push(db_status.repo_path);
} else {
updated_statuses.push(proto::StatusEntry {
repo_path: db_status.repo_path,
status: db_status.status as i32,
});
}
}
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
updated_statuses,
removed_statuses,
});
}
}

View File

@@ -6,7 +6,6 @@ use axum::{
routing::get,
Extension, Router,
};
use collab::api::billing::sync_llm_usage_with_stripe_periodically;
use collab::api::CloudflareIpCountryHeader;
use collab::llm::{db::LlmDatabase, log_usage_periodically};

View File

@@ -9,7 +9,7 @@ use collab_ui::channel_view::ChannelView;
use collections::HashMap;
use editor::{Anchor, Editor, ToOffset};
use futures::future;
use gpui::{BackgroundExecutor, Model, ModelContext, TestAppContext, Window};
use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
use serde_json::json;
use std::ops::Range;
@@ -161,43 +161,43 @@ async fn test_channel_notes_participant_indices(
// Clients A, B, and C open the channel notes
let channel_view_a = cx_a
.update(|window, cx| ChannelView::open(channel_id, None, workspace_a.clone(), window, cx))
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), cx))
.await
.unwrap();
let channel_view_b = cx_b
.update(|window, cx| ChannelView::open(channel_id, None, workspace_b.clone(), window, cx))
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx))
.await
.unwrap();
let channel_view_c = cx_c
.update(|window, cx| ChannelView::open(channel_id, None, workspace_c.clone(), window, cx))
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), cx))
.await
.unwrap();
// Clients A, B, and C all insert and select some text
channel_view_a.update_in(cx_a, |notes, window, cx| {
channel_view_a.update(cx_a, |notes, cx| {
notes.editor.update(cx, |editor, cx| {
editor.insert("a", window, cx);
editor.change_selections(None, window, cx, |selections| {
editor.insert("a", cx);
editor.change_selections(None, cx, |selections| {
selections.select_ranges(vec![0..1]);
});
});
});
executor.run_until_parked();
channel_view_b.update_in(cx_b, |notes, window, cx| {
channel_view_b.update(cx_b, |notes, cx| {
notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), window, cx);
editor.insert("b", window, cx);
editor.change_selections(None, window, cx, |selections| {
editor.move_down(&Default::default(), cx);
editor.insert("b", cx);
editor.change_selections(None, cx, |selections| {
selections.select_ranges(vec![1..2]);
});
});
});
executor.run_until_parked();
channel_view_c.update_in(cx_c, |notes, window, cx| {
channel_view_c.update(cx_c, |notes, cx| {
notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), window, cx);
editor.insert("c", window, cx);
editor.change_selections(None, window, cx, |selections| {
editor.move_down(&Default::default(), cx);
editor.insert("c", cx);
editor.change_selections(None, cx, |selections| {
selections.select_ranges(vec![2..3]);
});
});
@@ -206,9 +206,9 @@ async fn test_channel_notes_participant_indices(
// Client A sees clients B and C without assigned colors, because they aren't
// in a call together.
executor.run_until_parked();
channel_view_a.update_in(cx_a, |notes, window, cx| {
channel_view_a.update(cx_a, |notes, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], window, cx);
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
});
});
@@ -222,22 +222,20 @@ async fn test_channel_notes_participant_indices(
// Clients A and B see each other with two different assigned colors. Client C
// still doesn't have a color.
executor.run_until_parked();
channel_view_a.update_in(cx_a, |notes, window, cx| {
channel_view_a.update(cx_a, |notes, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(
editor,
&[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
window,
cx,
);
});
});
channel_view_b.update_in(cx_b, |notes, window, cx| {
channel_view_b.update(cx_b, |notes, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(
editor,
&[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
window,
cx,
);
});
@@ -254,8 +252,8 @@ async fn test_channel_notes_participant_indices(
// Clients A and B open the same file.
executor.start_waiting();
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
})
.await
.unwrap()
@@ -263,32 +261,32 @@ async fn test_channel_notes_participant_indices(
.unwrap();
executor.start_waiting();
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |selections| {
selections.select_ranges(vec![0..1]);
});
});
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |selections| {
selections.select_ranges(vec![2..3]);
});
});
executor.run_until_parked();
// Clients A and B see each other with the same colors as in the channel notes.
editor_a.update_in(cx_a, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], window, cx);
editor_a.update(cx_a, |editor, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
});
editor_b.update_in(cx_b, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], window, cx);
editor_b.update(cx_b, |editor, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
});
}
@@ -296,10 +294,9 @@ async fn test_channel_notes_participant_indices(
fn assert_remote_selections(
editor: &mut Editor,
expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
window: &mut Window,
cx: &mut ModelContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let snapshot = editor.snapshot(window, cx);
let snapshot = editor.snapshot(cx);
let range = Anchor::min()..Anchor::max();
let remote_selections = snapshot
.remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
@@ -644,9 +641,9 @@ async fn test_channel_buffer_changes(
});
// Closing the buffer should re-enable change tracking
cx_b.update(|window, cx| {
cx_b.update(|cx| {
workspace_b.update(cx, |workspace, cx| {
workspace.close_all_items_and_panes(&Default::default(), window, cx)
workspace.close_all_items_and_panes(&Default::default(), cx)
});
});
deterministic.run_until_parked();

View File

@@ -107,7 +107,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
});
assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
cx_b.update(|_, cx_b| {
cx_b.update(|cx_b| {
assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx)));
});
assert!(room_b
@@ -135,7 +135,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
// B sees themselves as muted, and can unmute.
cx_b.update(|_, cx_b| {
cx_b.update(|cx_b| {
assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx)));
});
room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));

View File

@@ -356,10 +356,10 @@ async fn test_channel_message_changes(
let project_b = client_b.build_empty_local_project(cx_b);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let chat_panel_b = workspace_b.update_in(cx_b, ChatPanel::new);
let chat_panel_b = workspace_b.update(cx_b, ChatPanel::new);
chat_panel_b
.update_in(cx_b, |chat_panel, window, cx| {
chat_panel.set_active(true, window, cx);
.update(cx_b, |chat_panel, cx| {
chat_panel.set_active(true, cx);
chat_panel.select_channel(channel_id, None, cx)
})
.await
@@ -367,7 +367,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|_, cx| {
let b_has_messages = cx_b.update(|cx| {
client_b
.channel_store()
.read(cx)
@@ -384,7 +384,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|_, cx| {
let b_has_messages = cx_b.update(|cx| {
client_b
.channel_store()
.read(cx)
@@ -394,8 +394,8 @@ async fn test_channel_message_changes(
assert!(!b_has_messages);
// Sending a message while the chat is closed should change the flag.
chat_panel_b.update_in(cx_b, |chat_panel, window, cx| {
chat_panel.set_active(false, window, cx);
chat_panel_b.update(cx_b, |chat_panel, cx| {
chat_panel.set_active(false, cx);
});
// Sending a message while the chat is open should not change the flag.
@@ -406,7 +406,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|_, cx| {
let b_has_messages = cx_b.update(|cx| {
client_b
.channel_store()
.read(cx)
@@ -416,7 +416,7 @@ async fn test_channel_message_changes(
assert!(b_has_messages);
// Closing the chat should re-enable change tracking
cx_b.update(|_, _| drop(chat_panel_b));
cx_b.update(|_| drop(chat_panel_b));
channel_chat_a
.update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
@@ -425,7 +425,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|_, cx| {
let b_has_messages = cx_b.update(|cx| {
client_b
.channel_store()
.read(cx)

View File

@@ -82,21 +82,14 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
let workspace_b = cx_b.add_window(|window, cx| {
Workspace::new(
None,
project_b.clone(),
client_b.app_state.clone(),
window,
cx,
)
});
let workspace_b = cx_b
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
let workspace_b_view = workspace_b.root_model(cx_b).unwrap();
let workspace_b_view = workspace_b.root_view(cx_b).unwrap();
let editor_b = workspace_b
.update(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
})
.unwrap()
.await
@@ -105,10 +98,10 @@ async fn test_host_disconnect(
.unwrap();
//TODO: focus
assert!(cx_b.update_window_model(&editor_b, |editor, window, _| editor.is_focused(window)));
editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
cx_b.update(|_, cx| {
cx_b.update(|cx| {
assert!(workspace_b_view.read(cx).is_edited());
});
@@ -128,7 +121,7 @@ async fn test_host_disconnect(
// Ensure client B's edited state is reset and that the whole window is blurred.
workspace_b
.update(cx_b, |workspace, _, cx| {
.update(cx_b, |workspace, cx| {
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
assert!(!workspace.is_edited());
})
@@ -136,8 +129,8 @@ async fn test_host_disconnect(
// Ensure client B is not prompted to save edits when closing window after disconnecting.
let can_close = workspace_b
.update(cx_b, |workspace, window, cx| {
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
.update(cx_b, |workspace, cx| {
workspace.prepare_to_close(CloseIntent::Quit, cx)
})
.unwrap()
.await
@@ -208,12 +201,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let editor_a = cx_a
.new_window_model(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let mut editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.window_handle(),
window: cx_a.handle(),
editor: editor_a,
assertion_cx: AssertionContextManager::new(),
};
@@ -224,12 +216,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let editor_b = cx_b
.new_window_model(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let mut editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.window_handle(),
window: cx_b.handle(),
editor: editor_b,
assertion_cx: AssertionContextManager::new(),
};
@@ -241,9 +232,8 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
editor_cx_b.set_selections_state(indoc! {"
Some textˇ
"});
editor_cx_a.update_editor(|editor, window, cx| {
editor.newline_above(&editor::actions::NewlineAbove, window, cx)
});
editor_cx_a
.update_editor(|editor, cx| editor.newline_above(&editor::actions::NewlineAbove, cx));
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
ˇ
@@ -263,9 +253,8 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
Some textˇ
"});
editor_cx_a.update_editor(|editor, window, cx| {
editor.newline_below(&editor::actions::NewlineBelow, window, cx)
});
editor_cx_a
.update_editor(|editor, cx| editor.newline_below(&editor::actions::NewlineBelow, cx));
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
@@ -328,9 +317,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
});
let editor_b =
cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx));
let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.background_executor.run_until_parked();
@@ -340,11 +328,11 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Type a completion trigger character as the guest.
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", window, cx);
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", cx);
});
cx_b.focus(&editor_b);
cx_b.focus_view(&editor_b);
// Receive a completion request as the host's language server.
// Return some completions from the host's language server.
@@ -406,9 +394,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Confirm a completion on the guest.
editor_b.update_in(cx_b, |editor, window, cx| {
editor_b.update(cx_b, |editor, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
});
@@ -453,10 +441,10 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
// Now we do a second completion, this time to ensure that documentation/snippets are
// resolved
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
editor.handle_input("; a", window, cx);
editor.handle_input(".", window, cx);
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([46..46]));
editor.handle_input("; a", cx);
editor.handle_input(".", cx);
});
buffer_b.read_with(cx_b, |buffer, _| {
@@ -520,18 +508,18 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
completion_response.next().await.unwrap();
editor_b.update_in(cx_b, |editor, window, cx| {
editor_b.update(cx_b, |editor, cx| {
assert!(editor.context_menu_visible());
editor.context_menu_first(&ContextMenuFirst {}, window, cx);
editor.context_menu_first(&ContextMenuFirst {}, cx);
});
resolve_completion_response.next().await.unwrap();
cx_b.executor().run_until_parked();
// When accepting the completion, the snippet is insert.
editor_b.update_in(cx_b, |editor, window, cx| {
editor_b.update(cx_b, |editor, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
assert_eq!(
editor.text(cx),
"use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
@@ -581,8 +569,8 @@ async fn test_collaborating_with_code_actions(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
})
.await
.unwrap()
@@ -605,12 +593,12 @@ async fn test_collaborating_with_code_actions(
requests.next().await;
// Move cursor to a location that contains code actions.
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
});
});
cx_b.focus(&editor_b);
cx_b.focus_view(&editor_b);
let mut requests = fake_language_server
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
@@ -670,12 +658,11 @@ async fn test_collaborating_with_code_actions(
requests.next().await;
// Toggle code actions and wait for them to display.
editor_b.update_in(cx_b, |editor, window, cx| {
editor_b.update(cx_b, |editor, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
},
window,
cx,
);
});
@@ -687,8 +674,8 @@ async fn test_collaborating_with_code_actions(
// Confirming the code action will trigger a resolve request.
let confirm_action = editor_b
.update_in(cx_b, |editor, window, cx| {
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
.update(cx_b, |editor, cx| {
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, cx)
})
.unwrap();
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
@@ -739,14 +726,14 @@ async fn test_collaborating_with_code_actions(
.downcast::<Editor>()
.unwrap()
});
code_action_editor.update_in(cx_b, |editor, window, cx| {
code_action_editor.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(
editor.text(cx),
"mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
);
editor.redo(&Redo, window, cx);
editor.redo(&Redo, cx);
assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
});
}
@@ -798,8 +785,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
})
.await
.unwrap()
@@ -808,9 +795,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
let fake_language_server = fake_language_servers.next().await.unwrap();
// Move cursor to a location that can be renamed.
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
editor.rename(&Rename, window, cx).unwrap()
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
editor.rename(&Rename, cx).unwrap()
});
fake_language_server
@@ -848,12 +835,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
// Cancel the rename, and repeat the same, but use selections instead of cursor movement
editor_b.update_in(cx_b, |editor, window, cx| {
editor.cancel(&editor::actions::Cancel, window, cx);
editor_b.update(cx_b, |editor, cx| {
editor.cancel(&editor::actions::Cancel, cx);
});
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, window, cx).unwrap()
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, cx).unwrap()
});
fake_language_server
@@ -889,8 +876,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
});
let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
let confirm_rename = editor_b.update(cx_b, |editor, cx| {
Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
});
fake_language_server
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
@@ -948,17 +935,17 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
workspace.active_item_as::<Editor>(cx).unwrap()
});
rename_editor.update_in(cx_b, |editor, window, cx| {
rename_editor.update(cx_b, |editor, cx| {
assert_eq!(
editor.text(cx),
"const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
);
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(
editor.text(cx),
"const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
);
editor.redo(&Redo, window, cx);
editor.redo(&Redo, cx);
assert_eq!(
editor.text(cx),
"const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
@@ -966,12 +953,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
// Ensure temporary rename edits cannot be undone/redone.
editor_b.update_in(cx_b, |editor, window, cx| {
editor.undo(&Undo, window, cx);
editor_b.update(cx_b, |editor, cx| {
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "const ONE: usize = 1;");
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "const ONE: usize = 1;");
editor.redo(&Redo, window, cx);
editor.redo(&Redo, cx);
assert_eq!(editor.text(cx), "const THREE: usize = 1;");
})
}
@@ -1206,8 +1193,7 @@ async fn test_share_project(
.await
.unwrap();
let editor_b =
cx_b.new_window_model(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx));
// Client A sees client B's selection
executor.run_until_parked();
@@ -1221,9 +1207,7 @@ async fn test_share_project(
});
// Edit the buffer as client B and see that edit as client A.
editor_b.update_in(cx_b, |editor, window, cx| {
editor.handle_input("ok, ", window, cx)
});
editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@@ -1250,7 +1234,7 @@ async fn test_share_project(
let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
// Client B closes the editor, and client A sees client B's selections removed.
cx_b.update(move |_, _| drop(editor_b));
cx_b.update(move |_| drop(editor_b));
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@@ -1314,9 +1298,7 @@ async fn test_on_input_format_from_host_to_guest(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
});
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx));
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
@@ -1348,10 +1330,10 @@ async fn test_on_input_format_from_host_to_guest(
.unwrap();
// Type a on type formatting trigger character as the guest.
cx_a.focus(&editor_a);
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", window, cx);
cx_a.focus_view(&editor_a);
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", cx);
});
executor.run_until_parked();
@@ -1361,9 +1343,9 @@ async fn test_on_input_format_from_host_to_guest(
});
// Undo should remove LSP edits first
editor_a.update_in(cx_a, |editor, window, cx| {
editor_a.update(cx_a, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a>~< }");
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a> }");
});
executor.run_until_parked();
@@ -1372,9 +1354,9 @@ async fn test_on_input_format_from_host_to_guest(
assert_eq!(buffer.text(), "fn main() { a> }")
});
editor_a.update_in(cx_a, |editor, window, cx| {
editor_a.update(cx_a, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a> }");
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
executor.run_until_parked();
@@ -1436,18 +1418,16 @@ async fn test_on_input_format_from_guest_to_host(
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
});
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx));
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
// Type a on type formatting trigger character as the guest.
cx_b.focus(&editor_b);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", window, cx);
cx_b.focus_view(&editor_b);
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", cx);
});
// Receive an OnTypeFormatting request as the host's language server.
@@ -1486,9 +1466,9 @@ async fn test_on_input_format_from_guest_to_host(
});
// Undo should remove LSP edits first
editor_b.update_in(cx_b, |editor, window, cx| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a:~: }");
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a: }");
});
executor.run_until_parked();
@@ -1497,9 +1477,9 @@ async fn test_on_input_format_from_guest_to_host(
assert_eq!(buffer.text(), "fn main() { a: }")
});
editor_b.update_in(cx_b, |editor, window, cx| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a: }");
editor.undo(&Undo, window, cx);
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
executor.run_until_parked();
@@ -1610,8 +1590,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await
.unwrap();
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
})
.await
.unwrap()
@@ -1666,8 +1646,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
})
.await
.unwrap()
@@ -1690,11 +1670,11 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", window, cx);
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", cx);
});
cx_b.focus(&editor_b);
cx_b.focus_view(&editor_b);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
@@ -1715,11 +1695,11 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", window, cx);
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", cx);
});
cx_a.focus(&editor_a);
cx_a.focus_view(&editor_a);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
@@ -1868,8 +1848,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
cx_a.background_executor.start_waiting();
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
})
.await
.unwrap()
@@ -1877,8 +1857,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.unwrap();
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
})
.await
.unwrap()
@@ -2062,8 +2042,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// Create editor_a
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
})
.await
.unwrap()
@@ -2074,8 +2054,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
})
.await
.unwrap()
@@ -2083,9 +2063,9 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.unwrap();
// client_b now requests git blame for the open buffer
editor_b.update_in(cx_b, |editor_b, window, cx| {
editor_b.update(cx_b, |editor_b, cx| {
assert!(editor_b.blame().is_none());
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, window, cx);
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, cx);
});
cx_a.executor().run_until_parked();
@@ -2123,7 +2103,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// editor_b updates the file, which gets sent to client_a, which updates git blame,
// which gets back to client_b.
editor_b.update_in(cx_b, |editor_b, _, cx| {
editor_b.update(cx_b, |editor_b, cx| {
editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
});
@@ -2150,7 +2130,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
});
// Now editor_a also updates the file
editor_a.update_in(cx_a, |editor_a, _, cx| {
editor_a.update(cx_a, |editor_a, cx| {
editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
});
@@ -2228,21 +2208,19 @@ async fn test_collaborating_with_editorconfig(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let main_editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
});
let other_editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
});
let main_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
let other_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
let mut main_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.window_handle(),
window: cx_a.handle(),
editor: main_editor_a,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.window_handle(),
window: cx_a.handle(),
editor: other_editor_a,
assertion_cx: AssertionContextManager::new(),
};
@@ -2262,21 +2240,19 @@ async fn test_collaborating_with_editorconfig(
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let main_editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
});
let other_editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
});
let main_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
let other_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
let mut main_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.window_handle(),
window: cx_b.handle(),
editor: main_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.window_handle(),
window: cx_b.handle(),
editor: other_editor_b,
assertion_cx: AssertionContextManager::new(),
};
@@ -2440,12 +2416,12 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_initial);
if a_tabs {
cx_a.update_editor(|editor, window, cx| {
editor.tab(&editor::actions::Tab, window, cx);
cx_a.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
} else {
cx_b.update_editor(|editor, window, cx| {
editor.tab(&editor::actions::Tab, window, cx);
cx_b.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
}
@@ -2456,12 +2432,12 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_tabbed);
if a_tabs {
cx_a.update_editor(|editor, window, cx| {
editor.undo(&editor::actions::Undo, window, cx);
cx_a.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
} else {
cx_b.update_editor(|editor, window, cx| {
editor.undo(&editor::actions::Undo, window, cx);
cx_b.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
}
cx_a.run_until_parked();

View File

@@ -8,8 +8,8 @@ use collab_ui::{
};
use editor::{Editor, ExcerptRange, MultiBuffer};
use gpui::{
point, BackgroundExecutor, BorrowAppContext, Context, Entity, Model, SharedString,
TestAppContext, VisualTestContext,
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
View, VisualContext, VisualTestContext,
};
use language::Capability;
use project::WorktreeSettings;
@@ -77,23 +77,23 @@ async fn test_basic_following(
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
cx_b.update(|window, _| {
assert!(window.is_window_active());
cx_b.update(|cx| {
assert!(cx.is_window_active());
});
// Client A opens some editors.
let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
let editor_a1 = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let editor_a2 = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
})
.await
.unwrap()
@@ -102,8 +102,8 @@ async fn test_basic_following(
// Client B opens an editor.
let editor_b1 = workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap()
@@ -116,24 +116,22 @@ async fn test_basic_following(
let peer_id_d = client_d.peer_id().unwrap();
// Client A updates their selections in those editors
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.handle_input("a", window, cx);
editor.handle_input("b", window, cx);
editor.handle_input("c", window, cx);
editor.select_left(&Default::default(), window, cx);
editor_a1.update(cx_a, |editor, cx| {
editor.handle_input("a", cx);
editor.handle_input("b", cx);
editor.handle_input("c", cx);
editor.select_left(&Default::default(), cx);
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
});
editor_a2.update_in(cx_a, |editor, window, cx| {
editor.handle_input("d", window, cx);
editor.handle_input("e", window, cx);
editor.select_left(&Default::default(), window, cx);
editor_a2.update(cx_a, |editor, cx| {
editor.handle_input("d", cx);
editor.handle_input("e", cx);
editor.select_left(&Default::default(), cx);
assert_eq!(editor.selections.ranges(cx), vec![2..1]);
});
// When client B starts following client A, only the active view state is replicated to client B.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
cx_c.executor().run_until_parked();
let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
@@ -167,9 +165,7 @@ async fn test_basic_following(
drop(project_c);
// Client C also follows client A.
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
workspace_c.update(cx_c, |workspace, cx| workspace.follow(peer_id_a, cx));
cx_d.executor().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
@@ -192,8 +188,8 @@ async fn test_basic_following(
}
// Client C unfollows client A.
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.unfollow(peer_id_a, window, cx).unwrap();
workspace_c.update(cx_c, |workspace, cx| {
workspace.unfollow(peer_id_a, cx).unwrap();
});
// All clients see that clients B is following client A.
@@ -207,9 +203,7 @@ async fn test_basic_following(
}
// Client C re-follows client A.
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
workspace_c.update(cx_c, |workspace, cx| workspace.follow(peer_id_a, cx));
// All clients see that clients B and C are following client A.
cx_c.executor().run_until_parked();
@@ -222,13 +216,9 @@ async fn test_basic_following(
}
// Client D follows client B, then switches to following client C.
workspace_d.update_in(cx_d, |workspace, window, cx| {
workspace.follow(peer_id_b, window, cx)
});
workspace_d.update(cx_d, |workspace, cx| workspace.follow(peer_id_b, cx));
cx_a.executor().run_until_parked();
workspace_d.update_in(cx_d, |workspace, window, cx| {
workspace.follow(peer_id_c, window, cx)
});
workspace_d.update(cx_d, |workspace, cx| workspace.follow(peer_id_c, cx));
// All clients see that D is following C
cx_a.executor().run_until_parked();
@@ -245,8 +235,8 @@ async fn test_basic_following(
// Client C closes the project.
let weak_workspace_c = workspace_c.downgrade();
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.close_window(&Default::default(), window, cx);
workspace_c.update(cx_c, |workspace, cx| {
workspace.close_window(&Default::default(), cx);
});
executor.run_until_parked();
// are you sure you want to leave the call?
@@ -270,8 +260,8 @@ async fn test_basic_following(
}
// When client A activates a different editor, client B does so as well.
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_item(&editor_a1, true, true, window, cx)
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a1, true, true, cx)
});
executor.run_until_parked();
workspace_b.update(cx_b, |workspace, cx| {
@@ -312,11 +302,11 @@ async fn test_basic_following(
);
result
});
let multibuffer_editor_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
let editor = cx.new_model(|cx| {
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, window, cx)
let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, cx)
});
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
editor
});
executor.run_until_parked();
@@ -334,8 +324,8 @@ async fn test_basic_following(
// When client A navigates back and forth, client B does so as well.
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.go_back(workspace.active_pane().downgrade(), window, cx)
.update(cx_a, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
@@ -348,8 +338,8 @@ async fn test_basic_following(
});
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.go_back(workspace.active_pane().downgrade(), window, cx)
.update(cx_a, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
@@ -362,8 +352,8 @@ async fn test_basic_following(
});
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.go_forward(workspace.active_pane().downgrade(), window, cx)
.update(cx_a, |workspace, cx| {
workspace.go_forward(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
@@ -376,8 +366,8 @@ async fn test_basic_following(
});
// Changes to client A's editor are reflected on client B.
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2]));
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@@ -387,15 +377,13 @@ async fn test_basic_following(
assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
});
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.set_text("TWO", window, cx)
});
editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), window, cx);
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@@ -404,11 +392,11 @@ async fn test_basic_following(
});
// After unfollowing, client B stops receiving updates from client A.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.unfollow(peer_id_a, window, cx).unwrap()
workspace_b.update(cx_b, |workspace, cx| {
workspace.unfollow(peer_id_a, cx).unwrap()
});
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_item(&editor_a2, true, true, window, cx)
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a2, true, true, cx)
});
executor.run_until_parked();
assert_eq!(
@@ -420,16 +408,14 @@ async fn test_basic_following(
);
// Client A starts following client B.
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(peer_id_b, window, cx)
});
workspace_a.update(cx_a, |workspace, cx| workspace.follow(peer_id_b, cx));
executor.run_until_parked();
assert_eq!(
workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
Some(peer_id_b)
);
assert_eq!(
workspace_a.update_in(cx_a, |workspace, _, cx| workspace
workspace_a.update(cx_a, |workspace, cx| workspace
.active_item(cx)
.unwrap()
.item_id()),
@@ -607,19 +593,19 @@ async fn test_following_tab_order(
//Open 1, 3 in that order on client A
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap();
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
})
.await
.unwrap();
let pane_paths = |pane: &Model<workspace::Pane>, cx: &mut VisualTestContext| {
let pane_paths = |pane: &View<workspace::Pane>, cx: &mut VisualTestContext| {
pane.update(cx, |pane, cx| {
pane.items()
.map(|item| {
@@ -638,15 +624,13 @@ async fn test_following_tab_order(
assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
//Follow client B as client A
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b_id, window, cx)
});
workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
executor.run_until_parked();
//Open just 2 on client B
workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
})
.await
.unwrap();
@@ -657,8 +641,8 @@ async fn test_following_tab_order(
//Open just 1 on client B
workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap();
@@ -717,8 +701,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client A opens a file.
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap()
@@ -728,8 +712,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B opens a different file.
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
})
.await
.unwrap()
@@ -737,38 +721,24 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
.unwrap();
// Clients A and B follow each other in split panes
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
window,
cx,
);
workspace_a.update(cx_a, |workspace, cx| {
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
});
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b.peer_id().unwrap(), window, cx)
workspace_a.update(cx_a, |workspace, cx| {
workspace.follow(client_b.peer_id().unwrap(), cx)
});
executor.run_until_parked();
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
window,
cx,
);
workspace_b.update(cx_b, |workspace, cx| {
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
});
executor.run_until_parked();
// Clients A and B return focus to the original files they had open
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
executor.run_until_parked();
// Both clients see the other client's focused file in their right pane.
@@ -805,15 +775,15 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Clients A and B each open a new file.
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
})
.await
.unwrap();
workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "4.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "4.txt"), None, true, cx)
})
.await
.unwrap();
@@ -861,9 +831,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
);
// Client A focuses their right pane, in which they're following client B.
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
executor.run_until_parked();
// Client B sees that client A is now looking at the same file as them.
@@ -909,9 +877,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B focuses their right pane, in which they're following client A,
// who is following them.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
executor.run_until_parked();
// Client A sees that client B is now looking at the same file as them.
@@ -957,9 +923,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B focuses a file that they previously followed A to, breaking
// the follow.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace_b.update(cx_b, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, window, cx);
pane.activate_prev_item(true, cx);
});
});
executor.run_until_parked();
@@ -1008,9 +974,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B closes tabs, some of which were originally opened by client A,
// and some of which were originally opened by client B.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace_b.update(cx_b, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.close_inactive_items(&Default::default(), window, cx)
pane.close_inactive_items(&Default::default(), cx)
.unwrap()
.detach();
});
@@ -1056,14 +1022,14 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
);
// Client B follows client A again.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
});
executor.run_until_parked();
// Client A cycles through some tabs.
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace_a.update(cx_a, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, window, cx);
pane.activate_prev_item(true, cx);
});
});
executor.run_until_parked();
@@ -1105,9 +1071,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
]
);
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace_a.update(cx_a, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, window, cx);
pane.activate_prev_item(true, cx);
});
});
executor.run_until_parked();
@@ -1152,9 +1118,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
]
);
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace_a.update(cx_a, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, window, cx);
pane.activate_prev_item(true, cx);
});
});
executor.run_until_parked();
@@ -1249,8 +1215,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let _editor_a1 = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap()
@@ -1262,9 +1228,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
let leader_id = project_b.update(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1279,17 +1243,15 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
});
// When client B moves, it automatically stops following client A.
editor_b2.update_in(cx_b, |editor, window, cx| {
editor.move_right(&editor::actions::MoveRight, window, cx)
editor_b2.update(cx_b, |editor, cx| {
editor.move_right(&editor::actions::MoveRight, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1297,15 +1259,13 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B edits, it automatically stops following client A.
editor_b2.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
assert_eq!(
workspace_b.update_in(cx_b, |workspace, _, _| workspace.leader_for_pane(&pane_b)),
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1313,17 +1273,15 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B scrolls, it automatically stops following client A.
editor_b2.update_in(cx_b, |editor, window, cx| {
editor.set_scroll_position(point(0., 3.), window, cx)
editor_b2.update(cx_b, |editor, cx| {
editor.set_scroll_position(point(0., 3.), cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@@ -1331,17 +1289,15 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B activates a different pane, it continues following client A in the original pane.
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, window, cx)
workspace_b.update(cx_b, |workspace, cx| {
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
@@ -1349,8 +1305,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
// When client B activates a different item in the original pane, it automatically stops following client A.
workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
})
.await
.unwrap();
@@ -1396,12 +1352,8 @@ async fn test_peers_simultaneously_following_each_other(
project.collaborators().values().next().unwrap().peer_id
});
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b_id, window, cx)
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a_id, window, cx)
});
workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
workspace_b.update(cx_b, |workspace, cx| workspace.follow(client_a_id, cx));
executor.run_until_parked();
workspace_a.update(cx_a, |workspace, _| {
@@ -1482,8 +1434,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
.unwrap();
workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "w.rs"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
})
.await
.unwrap();
@@ -1491,8 +1443,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_b).len(), 1);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
});
executor.run_until_parked();
@@ -1538,8 +1490,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
// b moves to x.rs in a's project, and a follows
workspace_b_project_a
.update_in(&mut cx_b2, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "x.rs"), None, true, window, cx)
.update(&mut cx_b2, |workspace, cx| {
workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
})
.await
.unwrap();
@@ -1553,8 +1505,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
);
});
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b.peer_id().unwrap(), window, cx)
workspace_a.update(cx_a, |workspace, cx| {
workspace.follow(client_b.peer_id().unwrap(), cx)
});
executor.run_until_parked();
@@ -1570,8 +1522,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
// b moves to y.rs in b's project, a is still following but can't yet see
workspace_b
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_b, "y.rs"), None, true, window, cx)
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
})
.await
.unwrap();
@@ -1592,7 +1544,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_a).len(), 1);
cx_a.update(|_, cx| {
cx_a.update(|cx| {
workspace::join_in_room_project(
project_b_id,
client_b.user_id().unwrap(),
@@ -1655,8 +1607,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
});
// b should follow a to position 1
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]))
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@@ -1666,7 +1618,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
});
// a unshares the project
cx_a.update(|_, cx| {
cx_a.update(|cx| {
let project = workspace_a.read(cx).project().clone();
ActiveCall::global(cx).update(cx, |call, cx| {
call.unshare_project(project, cx).unwrap();
@@ -1675,8 +1627,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
cx_a.run_until_parked();
// b should not follow a to position 2
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]))
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@@ -1684,7 +1636,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
});
cx_b.update(|_, cx| {
cx_b.update(|cx| {
let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx);
let participant = room.remote_participants().get(&client_a.id()).unwrap();
assert_eq!(participant.location, ParticipantLocation::UnsharedProject)
@@ -1751,16 +1703,16 @@ async fn test_following_into_excluded_file(
// Client A opens editors for a regular file and an excluded file.
let editor_for_regular = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let editor_for_excluded_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
})
.await
.unwrap()
@@ -1768,24 +1720,22 @@ async fn test_following_into_excluded_file(
.unwrap();
// Client A updates their selections in those editors
editor_for_regular.update_in(cx_a, |editor, window, cx| {
editor.handle_input("a", window, cx);
editor.handle_input("b", window, cx);
editor.handle_input("c", window, cx);
editor.select_left(&Default::default(), window, cx);
editor_for_regular.update(cx_a, |editor, cx| {
editor.handle_input("a", cx);
editor.handle_input("b", cx);
editor.handle_input("c", cx);
editor.select_left(&Default::default(), cx);
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
});
editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
editor.select_all(&Default::default(), window, cx);
editor.handle_input("new commit message", window, cx);
editor.select_left(&Default::default(), window, cx);
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_all(&Default::default(), cx);
editor.handle_input("new commit message", cx);
editor.select_left(&Default::default(), cx);
assert_eq!(editor.selections.ranges(cx), vec![18..17]);
});
// When client B starts following client A, currently visible file is replicated
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@@ -1805,15 +1755,15 @@ async fn test_following_into_excluded_file(
vec![18..17]
);
editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
editor.select_right(&Default::default(), window, cx);
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_right(&Default::default(), cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
// Changes from B to the excluded file are replicated in A's editor
editor_for_excluded_b.update_in(cx_b, |editor, window, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", window, cx);
editor_for_excluded_b.update(cx_b, |editor, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
});
executor.run_until_parked();
editor_for_excluded_a.update(cx_a, |editor, cx| {
@@ -1824,11 +1774,13 @@ async fn test_following_into_excluded_file(
});
}
fn visible_push_notifications(cx: &mut TestAppContext) -> Vec<Model<ProjectSharedNotification>> {
fn visible_push_notifications(
cx: &mut TestAppContext,
) -> Vec<gpui::View<ProjectSharedNotification>> {
let mut ret = Vec::new();
for window in cx.windows() {
window
.update(cx, |window, _, _| {
.update(cx, |window, _| {
if let Ok(handle) = window.downcast::<ProjectSharedNotification>() {
ret.push(handle)
}
@@ -1869,7 +1821,7 @@ fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec
})
}
fn pane_summaries(workspace: &Model<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
workspace.update(cx, |workspace, cx| {
let active_pane = workspace.active_pane();
workspace
@@ -1972,14 +1924,14 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens the notes for channel 1.
let channel_notes_1_a = cx_a
.update(|window, cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), window, cx))
.update(|cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), cx))
.await
.unwrap();
channel_notes_1_a.update_in(cx_a, |notes, window, cx| {
channel_notes_1_a.update(cx_a, |notes, cx| {
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
notes.editor.update(cx, |editor, cx| {
editor.insert("Hello from A.", window, cx);
editor.change_selections(None, window, cx, |selections| {
editor.insert("Hello from A.", cx);
editor.change_selections(None, cx, |selections| {
selections.select_ranges(vec![3..4]);
});
});
@@ -1987,9 +1939,9 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client B follows client A.
workspace_b
.update_in(cx_b, |workspace, window, cx| {
.update(cx_b, |workspace, cx| {
workspace
.start_following(client_a.peer_id().unwrap(), window, cx)
.start_following(client_a.peer_id().unwrap(), cx)
.unwrap()
})
.await
@@ -2019,7 +1971,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens the notes for channel 2.
let channel_notes_2_a = cx_a
.update(|window, cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), window, cx))
.update(|cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), cx))
.await
.unwrap();
channel_notes_2_a.update(cx_a, |notes, cx| {
@@ -2045,8 +1997,8 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens a local buffer in their unshared project.
let _unshared_editor_a1 = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
})
.await
.unwrap()
@@ -2075,7 +2027,7 @@ pub(crate) async fn join_channel(
}
async fn share_workspace(
workspace: &Model<Workspace>,
workspace: &View<Workspace>,
cx: &mut VisualTestContext,
) -> anyhow::Result<u64> {
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
@@ -2117,9 +2069,9 @@ async fn test_following_to_channel_notes_other_workspace(
// a opens a second workspace and the channel notes
let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
cx_a2.update(|window, _| window.activate_window());
cx_a2.update(|cx| cx.activate_window());
cx_a2
.update(|window, cx| ChannelView::open(channel, None, workspace_a2, window, cx))
.update(|cx| ChannelView::open(channel, None, workspace_a2, cx))
.await
.unwrap();
cx_a2.run_until_parked();
@@ -2131,7 +2083,7 @@ async fn test_following_to_channel_notes_other_workspace(
});
// a returns to the shared project
cx_a.update(|window, _| window.activate_window());
cx_a.update(|cx| cx.activate_window());
cx_a.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {
@@ -2189,7 +2141,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
// a opens a file in a new window
let (_, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
cx_a2.update(|window, _| window.activate_window());
cx_a2.update(|cx| cx.activate_window());
cx_a2.simulate_keystrokes("cmd-p");
cx_a2.run_until_parked();
cx_a2.simulate_keystrokes("3 enter");
@@ -2200,7 +2152,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
cx_a.run_until_parked();
// a returns to the shared project
cx_a.update(|window, _| window.activate_window());
cx_a.update(|cx| cx.activate_window());
cx_a.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {

View File

@@ -2925,8 +2925,6 @@ async fn test_git_status_sync(
assert_eq!(snapshot.status_for_file(file), status);
}
// Smoke test status reading
project_local.read_with(cx_a, |project, cx| {
assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx);
assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx);
@@ -4067,7 +4065,7 @@ async fn test_collaborating_with_diagnostics(
DiagnosticEntry {
range: Point::new(0, 4)..Point::new(0, 7),
diagnostic: Diagnostic {
group_id: 2,
group_id: 3,
message: "message 1".to_string(),
severity: lsp::DiagnosticSeverity::ERROR,
is_primary: true,
@@ -4077,7 +4075,7 @@ async fn test_collaborating_with_diagnostics(
DiagnosticEntry {
range: Point::new(0, 10)..Point::new(0, 13),
diagnostic: Diagnostic {
group_id: 3,
group_id: 4,
severity: lsp::DiagnosticSeverity::WARNING,
message: "message 2".to_string(),
is_primary: true,
@@ -6174,7 +6172,7 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
cx.simulate_resize(size(px(300.), px(300.)));
cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
cx.update(|window, _cx| window.refresh());
cx.update(|cx| cx.refresh());
let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
@@ -6273,8 +6271,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Opening item 3 as a "permanent" tab
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path(path_3.clone(), None, false, window, cx)
.update(cx, |workspace, cx| {
workspace.open_path(path_3.clone(), None, false, cx)
})
.await
.unwrap();
@@ -6290,8 +6288,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 1 as preview
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, window, cx)
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, cx)
})
.await
.unwrap();
@@ -6311,8 +6309,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
})
.await
.unwrap();
@@ -6332,9 +6330,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Going back should show item 1 as preview
workspace
.update_in(cx, |workspace, window, cx| {
workspace.go_back(pane.downgrade(), window, cx)
})
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.await
.unwrap();
@@ -6352,11 +6348,10 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Closing item 1
pane.update_in(cx, |pane, window, cx| {
pane.update(cx, |pane, cx| {
pane.close_item_by_id(
pane.active_item().unwrap().item_id(),
workspace::SaveIntent::Skip,
window,
cx,
)
})
@@ -6374,9 +6369,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Going back should show item 1 as preview
workspace
.update_in(cx, |workspace, window, cx| {
workspace.go_back(pane.downgrade(), window, cx)
})
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.await
.unwrap();
@@ -6394,9 +6387,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Close permanent tab
pane.update_in(cx, |pane, window, cx| {
pane.update(cx, |pane, cx| {
let id = pane.items().next().unwrap().item_id();
pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
pane.close_item_by_id(id, workspace::SaveIntent::Skip, cx)
})
.await
.unwrap();
@@ -6443,8 +6436,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in right pane
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
})
.await
.unwrap();
@@ -6475,14 +6468,14 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Focus left pane
workspace.update_in(cx, |workspace, window, cx| {
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
workspace.update(cx, |workspace, cx| {
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx)
});
// Open item 2 as preview in left pane
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
})
.await
.unwrap();
@@ -6674,6 +6667,10 @@ async fn test_remote_git_branches(
client_a
.fs()
.insert_branches(Path::new("/project/.git"), &branches);
let branches_set = branches
.into_iter()
.map(ToString::to_string)
.collect::<HashSet<_>>();
let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await;
let project_id = active_call_a
@@ -6695,10 +6692,10 @@ async fn test_remote_git_branches(
let branches_b = branches_b
.into_iter()
.map(|branch| branch.name)
.collect::<Vec<_>>();
.map(|branch| branch.name.to_string())
.collect::<HashSet<_>>();
assert_eq!(&branches_b, &branches);
assert_eq!(branches_b, branches_set);
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {

View File

@@ -21,14 +21,14 @@ async fn test_notifications(
let notification_events_b = Arc::new(Mutex::new(Vec::new()));
client_a.notification_store().update(cx_a, |_, cx| {
let events = notification_events_a.clone();
cx.subscribe(&cx.model(), move |_, _, event, _| {
cx.subscribe(&cx.handle(), move |_, _, event, _| {
events.lock().push(event.clone());
})
.detach()
});
client_b.notification_store().update(cx_b, |_, cx| {
let events = notification_events_b.clone();
cx.subscribe(&cx.model(), move |_, _, event, _| {
cx.subscribe(&cx.handle(), move |_, _, event, _| {
events.lock().push(event.clone());
})
.detach()

View File

@@ -229,6 +229,10 @@ async fn test_ssh_collaboration_git_branches(
.await;
let branches = ["main", "dev", "feature-1"];
let branches_set = branches
.iter()
.map(ToString::to_string)
.collect::<HashSet<_>>();
remote_fs.insert_branches(Path::new("/project/.git"), &branches);
// User A connects to the remote project via SSH.
@@ -281,10 +285,10 @@ async fn test_ssh_collaboration_git_branches(
let branches_b = branches_b
.into_iter()
.map(|branch| branch.name)
.collect::<Vec<_>>();
.map(|branch| branch.name.to_string())
.collect::<HashSet<_>>();
assert_eq!(&branches_b, &branches);
assert_eq!(&branches_b, &branches_set);
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {

View File

@@ -17,7 +17,7 @@ use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use git::GitHostingProviderRegistry;
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, VisualTestContext};
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
use http_client::FakeHttpClient;
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
@@ -752,17 +752,17 @@ impl TestClient {
pub async fn host_workspace(
&self,
workspace: &Model<Workspace>,
workspace: &View<Workspace>,
channel_id: ChannelId,
cx: &mut VisualTestContext,
) {
cx.update(|_, cx| {
cx.update(|cx| {
let active_call = ActiveCall::global(cx);
active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
})
.await
.unwrap();
cx.update(|_, cx| {
cx.update(|cx| {
let active_call = ActiveCall::global(cx);
let project = workspace.read(cx).project().clone();
active_call.update(cx, |call, cx| call.share_project(project, cx))
@@ -776,7 +776,7 @@ impl TestClient {
&'a self,
channel_id: ChannelId,
cx: &'a mut TestAppContext,
) -> (Model<Workspace>, &'a mut VisualTestContext) {
) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
.await
.unwrap();
@@ -822,31 +822,31 @@ impl TestClient {
&'a self,
project: &Model<Project>,
cx: &'a mut TestAppContext,
) -> (Model<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|window, cx| {
window.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|cx| {
cx.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
})
}
pub async fn build_test_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (Model<Workspace>, &'a mut VisualTestContext) {
) -> (View<Workspace>, &'a mut VisualTestContext) {
let project = self.build_test_project(cx).await;
cx.add_window_view(|window, cx| {
window.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
cx.add_window_view(|cx| {
cx.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
})
}
pub fn active_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (Model<Workspace>, &'a mut VisualTestContext) {
) -> (View<Workspace>, &'a mut VisualTestContext) {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_model(cx).unwrap();
let view = window.root_view(cx).unwrap();
let cx = VisualTestContext::from_window(*window.deref(), cx).as_mut();
// it might be nice to try and cleanup these at the end of each test.
(view, cx)
@@ -856,11 +856,11 @@ impl TestClient {
pub fn open_channel_notes(
channel_id: ChannelId,
cx: &mut VisualTestContext,
) -> Task<anyhow::Result<Model<ChannelView>>> {
let window = cx.update(|_, cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_model(cx).unwrap();
) -> Task<anyhow::Result<View<ChannelView>>> {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_view(cx).unwrap();
cx.update(|window, cx| ChannelView::open(channel_id, None, view.clone(), window, cx))
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
}
impl Drop for TestClient {

View File

@@ -11,8 +11,9 @@ use editor::{
EditorEvent,
};
use gpui::{
actions, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter, Focusable, Model,
ModelContext, Pixels, Point, Render, Subscription, Task, VisualContext as _, WeakModel, Window,
actions, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter, FocusableView, Model,
Pixels, Point, Render, Subscription, Task, View, ViewContext, VisualContext as _, WeakView,
WindowContext,
};
use project::Project;
use rpc::proto::ChannelVisibility;
@@ -37,8 +38,8 @@ pub fn init(cx: &mut AppContext) {
}
pub struct ChannelView {
pub editor: Model<Editor>,
workspace: WeakModel<Workspace>,
pub editor: View<Editor>,
workspace: WeakView<Workspace>,
project: Model<Project>,
channel_store: Model<ChannelStore>,
channel_buffer: Model<ChannelBuffer>,
@@ -51,22 +52,20 @@ impl ChannelView {
pub fn open(
channel_id: ChannelId,
link_position: Option<String>,
workspace: Model<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let pane = workspace.read(cx).active_pane().clone();
let channel_view = Self::open_in_pane(
channel_id,
link_position,
pane.clone(),
workspace.clone(),
window,
cx,
);
window.spawn(cx, |mut cx| async move {
cx.spawn(|mut cx| async move {
let channel_view = channel_view.await?;
pane.update_in(&mut cx, |pane, window, cx| {
pane.update(&mut cx, |pane, cx| {
telemetry::event!(
"Channel Notes Opened",
channel_id,
@@ -75,7 +74,7 @@ impl ChannelView {
.room()
.map(|r| r.read(cx).id())
);
pane.add_item(Box::new(channel_view.clone()), true, true, None, window, cx);
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
})?;
anyhow::Ok(channel_view)
})
@@ -84,16 +83,15 @@ impl ChannelView {
pub fn open_in_pane(
channel_id: ChannelId,
link_position: Option<String>,
pane: Model<Pane>,
workspace: Model<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
let channel_view = Self::load(channel_id, workspace, window, cx);
window.spawn(cx, |mut cx| async move {
pane: View<Pane>,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let channel_view = Self::load(channel_id, workspace, cx);
cx.spawn(|mut cx| async move {
let channel_view = channel_view.await?;
pane.update_in(&mut cx, |pane, window, cx| {
pane.update(&mut cx, |pane, cx| {
let buffer_id = channel_view.read(cx).channel_buffer.read(cx).remote_id(cx);
let existing_view = pane
@@ -106,12 +104,7 @@ impl ChannelView {
{
if let Some(link_position) = link_position {
existing_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(
link_position,
true,
window,
cx,
)
channel_view.focus_position_from_link(link_position, true, cx)
});
}
return existing_view;
@@ -122,27 +115,15 @@ impl ChannelView {
// replace that.
if let Some(existing_item) = existing_view {
if let Some(ix) = pane.index_for_item(&existing_item) {
pane.close_item_by_id(
existing_item.entity_id(),
SaveIntent::Skip,
window,
cx,
)
.detach();
pane.add_item(
Box::new(channel_view.clone()),
true,
true,
Some(ix),
window,
cx,
);
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
.detach();
pane.add_item(Box::new(channel_view.clone()), true, true, Some(ix), cx);
}
}
if let Some(link_position) = link_position {
channel_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, window, cx)
channel_view.focus_position_from_link(link_position, true, cx)
});
}
@@ -153,10 +134,9 @@ impl ChannelView {
pub fn load(
channel_id: ChannelId,
workspace: Model<Workspace>,
window: &mut Window,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let weak_workspace = workspace.downgrade();
let workspace = workspace.read(cx);
let project = workspace.project().to_owned();
@@ -166,7 +146,7 @@ impl ChannelView {
let channel_buffer =
channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
window.spawn(cx, |mut cx| async move {
cx.spawn(|mut cx| async move {
let channel_buffer = channel_buffer.await?;
let markdown = markdown.await.log_err();
@@ -180,15 +160,9 @@ impl ChannelView {
})
})?;
cx.new_window_model(|window, cx| {
let mut this = Self::new(
project,
weak_workspace,
channel_store,
channel_buffer,
window,
cx,
);
cx.new_view(|cx| {
let mut this =
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
this.acknowledge_buffer_version(cx);
this
})
@@ -197,27 +171,24 @@ impl ChannelView {
pub fn new(
project: Model<Project>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
channel_store: Model<ChannelStore>,
channel_buffer: Model<ChannelBuffer>,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = channel_buffer.read(cx).buffer();
let this = cx.model().downgrade();
let editor = cx.new_model(|cx| {
let mut editor = Editor::for_buffer(buffer, None, window, cx);
let this = cx.view().downgrade();
let editor = cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx);
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
channel_buffer.clone(),
)));
editor.set_custom_context_menu(move |_, position, window, cx| {
editor.set_custom_context_menu(move |_, position, cx| {
let this = this.clone();
Some(ui::ContextMenu::build(window, cx, move |menu, _, _| {
menu.entry("Copy link to section", None, move |window, cx| {
this.update(cx, |this, cx| {
this.copy_link_for_position(position, window, cx)
})
.ok();
Some(ui::ContextMenu::build(cx, move |menu, _| {
menu.entry("Copy link to section", None, move |cx| {
this.update(cx, |this, cx| this.copy_link_for_position(position, cx))
.ok();
})
}))
});
@@ -226,7 +197,7 @@ impl ChannelView {
let _editor_event_subscription =
cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
cx.subscribe_in(&channel_buffer, window, Self::handle_channel_buffer_event)
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
.detach();
Self {
@@ -245,13 +216,10 @@ impl ChannelView {
&mut self,
position: String,
first_attempt: bool,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
let position = Channel::slug(&position).to_lowercase();
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
if let Some(item) = outline
@@ -260,7 +228,7 @@ impl ChannelView {
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
{
self.editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
})
});
@@ -271,13 +239,12 @@ impl ChannelView {
if !first_attempt {
return;
}
self._reparse_subscription = Some(cx.subscribe_in(
self._reparse_subscription = Some(cx.subscribe(
&self.editor,
window,
move |this, _, e: &EditorEvent, window, cx| {
move |this, _, e: &EditorEvent, cx| {
match e {
EditorEvent::Reparsed(_) => {
this.focus_position_from_link(position.clone(), false, window, cx);
this.focus_position_from_link(position.clone(), false, cx);
this._reparse_subscription.take();
}
EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {
@@ -289,22 +256,15 @@ impl ChannelView {
));
}
fn copy_link(&mut self, _: &CopyLink, window: &mut Window, cx: &mut ModelContext<Self>) {
fn copy_link(&mut self, _: &CopyLink, cx: &mut ViewContext<Self>) {
let position = self
.editor
.update(cx, |editor, cx| editor.selections.newest_display(cx).start);
self.copy_link_for_position(position, window, cx)
self.copy_link_for_position(position, cx)
}
fn copy_link_for_position(
&self,
position: DisplayPoint,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
fn copy_link_for_position(&self, position: DisplayPoint, cx: &mut ViewContext<Self>) {
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
let mut closest_heading = None;
@@ -344,10 +304,9 @@ impl ChannelView {
fn handle_channel_buffer_event(
&mut self,
_: &Model<ChannelBuffer>,
_: Model<ChannelBuffer>,
event: &ChannelBufferEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
@@ -361,7 +320,7 @@ impl ChannelView {
});
}
ChannelBufferEvent::BufferEdited => {
if self.editor.read(cx).is_focused(window) {
if self.editor.read(cx).is_focused(cx) {
self.acknowledge_buffer_version(cx);
} else {
self.channel_store.update(cx, |store, cx| {
@@ -379,7 +338,7 @@ impl ChannelView {
}
}
fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<ChannelView>) {
fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<ChannelView>) {
self.channel_store.update(cx, |store, cx| {
let channel_buffer = self.channel_buffer.read(cx);
store.acknowledge_notes_version(
@@ -398,7 +357,7 @@ impl ChannelView {
impl EventEmitter<EditorEvent> for ChannelView {}
impl Render for ChannelView {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.size_full()
.on_action(cx.listener(Self::copy_link))
@@ -406,7 +365,7 @@ impl Render for ChannelView {
}
}
impl Focusable for ChannelView {
impl FocusableView for ChannelView {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.read(cx).focus_handle(cx)
}
@@ -418,7 +377,7 @@ impl Item for ChannelView {
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a Model<Self>,
self_handle: &'a View<Self>,
_: &'a AppContext,
) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
@@ -430,7 +389,7 @@ impl Item for ChannelView {
}
}
fn tab_icon(&self, _: &Window, cx: &AppContext) -> Option<Icon> {
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
let channel = self.channel(cx)?;
let icon = match channel.visibility {
ChannelVisibility::Public => IconName::Public,
@@ -440,12 +399,7 @@ impl Item for ChannelView {
Some(Icon::new(icon))
}
fn tab_content(
&self,
params: TabContentParams,
_: &Window,
cx: &AppContext,
) -> gpui::AnyElement {
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> gpui::AnyElement {
let (channel_name, status) = if let Some(channel) = self.channel(cx) {
let status = match (
self.channel_buffer.read(cx).buffer().read(cx).read_only(),
@@ -485,16 +439,14 @@ impl Item for ChannelView {
fn clone_on_split(
&self,
_: Option<WorkspaceId>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<Model<Self>> {
Some(cx.new_model(|cx| {
cx: &mut ViewContext<Self>,
) -> Option<View<Self>> {
Some(cx.new_view(|cx| {
Self::new(
self.project.clone(),
self.workspace.clone(),
self.channel_store.clone(),
self.channel_buffer.clone(),
window,
cx,
)
}))
@@ -504,33 +456,21 @@ impl Item for ChannelView {
false
}
fn navigate(
&mut self,
data: Box<dyn Any>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> bool {
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, window, cx))
.update(cx, |editor, cx| editor.navigate(data, cx))
}
fn deactivated(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) {
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, Item::deactivated)
}
fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |item, cx| item.deactivated(window, cx))
.update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
}
fn set_nav_history(
&mut self,
history: ItemNavHistory,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
Item::set_nav_history(editor, history, window, cx)
})
}
fn as_searchable(&self, _: &Model<Self>) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
@@ -552,7 +492,7 @@ impl FollowableItem for ChannelView {
self.remote_id
}
fn to_state_proto(&self, window: &Window, cx: &AppContext) -> Option<proto::view::Variant> {
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
let channel_buffer = self.channel_buffer.read(cx);
if !channel_buffer.is_connected() {
return None;
@@ -562,7 +502,7 @@ impl FollowableItem for ChannelView {
proto::view::ChannelView {
channel_id: channel_buffer.channel_id.0,
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(window, cx)
self.editor.read(cx).to_state_proto(cx)
{
Some(proto)
} else {
@@ -573,12 +513,11 @@ impl FollowableItem for ChannelView {
}
fn from_state_proto(
workspace: Model<workspace::Workspace>,
workspace: View<workspace::Workspace>,
remote_id: workspace::ViewId,
state: &mut Option<proto::view::Variant>,
window: &mut Window,
cx: &mut AppContext,
) -> Option<gpui::Task<anyhow::Result<Model<Self>>>> {
cx: &mut WindowContext,
) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
let Some(proto::view::Variant::ChannelView(_)) = state else {
return None;
};
@@ -586,12 +525,12 @@ impl FollowableItem for ChannelView {
unreachable!()
};
let open = ChannelView::load(ChannelId(state.channel_id), workspace, window, cx);
let open = ChannelView::load(ChannelId(state.channel_id), workspace, cx);
Some(window.spawn(cx, |mut cx| async move {
Some(cx.spawn(|mut cx| async move {
let this = open.await?;
let task = this.update_in(&mut cx, |this, window, cx| {
let task = this.update(&mut cx, |this, cx| {
this.remote_id = Some(remote_id);
if let Some(state) = state.editor {
@@ -606,7 +545,6 @@ impl FollowableItem for ChannelView {
scroll_y: state.scroll_y,
..Default::default()
}),
window,
cx,
)
}))
@@ -627,38 +565,31 @@ impl FollowableItem for ChannelView {
&self,
event: &EditorEvent,
update: &mut Option<proto::update_view::Variant>,
window: &Window,
cx: &AppContext,
cx: &WindowContext,
) -> bool {
self.editor
.read(cx)
.add_event_to_update_proto(event, update, window, cx)
.add_event_to_update_proto(event, update, cx)
}
fn apply_update_proto(
&mut self,
project: &Model<Project>,
message: proto::update_view::Variant,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> gpui::Task<anyhow::Result<()>> {
self.editor.update(cx, |editor, cx| {
editor.apply_update_proto(project, message, window, cx)
editor.apply_update_proto(project, message, cx)
})
}
fn set_leader_peer_id(
&mut self,
leader_peer_id: Option<PeerId>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, cx| {
editor.set_leader_peer_id(leader_peer_id, window, cx)
editor.set_leader_peer_id(leader_peer_id, cx)
})
}
fn is_project_item(&self, _window: &Window, _cx: &AppContext) -> bool {
fn is_project_item(&self, _cx: &WindowContext) -> bool {
false
}
@@ -666,7 +597,7 @@ impl FollowableItem for ChannelView {
Editor::to_follow_event(event)
}
fn dedup(&self, existing: &Self, _: &Window, cx: &AppContext) -> Option<Dedup> {
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
let existing = existing.channel_buffer.read(cx);
if self.channel_buffer.read(cx).channel_id == existing.channel_id {
if existing.is_connected() {

View File

@@ -8,9 +8,9 @@ use db::kvp::KEY_VALUE_STORE;
use editor::{actions, Editor};
use gpui::{
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, ClipboardItem,
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, Focusable, FontWeight,
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, ModelContext, Render, Stateful,
Subscription, Task, WeakModel, Window,
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight,
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, Stateful, Subscription,
Task, View, ViewContext, VisualContext, WeakView,
};
use language::LanguageRegistry;
use menu::Confirm;
@@ -37,9 +37,9 @@ const MESSAGE_LOADING_THRESHOLD: usize = 50;
const CHAT_PANEL_KEY: &str = "ChatPanel";
pub fn init(cx: &mut AppContext) {
cx.observe_new_models(|workspace: &mut Workspace, _, _| {
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<ChatPanel>(window, cx);
cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<ChatPanel>(cx);
});
})
.detach();
@@ -51,7 +51,7 @@ pub struct ChatPanel {
languages: Arc<LanguageRegistry>,
message_list: ListState,
active_chat: Option<(Model<ChannelChat>, Subscription)>,
message_editor: Model<MessageEditor>,
message_editor: View<MessageEditor>,
local_timezone: UtcOffset,
fs: Arc<dyn Fs>,
width: Option<Pixels>,
@@ -74,46 +74,37 @@ struct SerializedChatPanel {
actions!(chat_panel, [ToggleFocus]);
impl ChatPanel {
pub fn new(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Model<Self> {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let fs = workspace.app_state().fs.clone();
let client = workspace.app_state().client.clone();
let channel_store = ChannelStore::global(cx);
let user_store = workspace.app_state().user_store.clone();
let languages = workspace.app_state().languages.clone();
let input_editor = cx.new_model(|cx| {
let input_editor = cx.new_view(|cx| {
MessageEditor::new(
languages.clone(),
user_store.clone(),
None,
cx.new_model(|cx| Editor::auto_height(4, window, cx)),
window,
cx.new_view(|cx| Editor::auto_height(4, cx)),
cx,
)
});
cx.new_model(|cx| {
let view = cx.model().downgrade();
let message_list = ListState::new(
0,
gpui::ListAlignment::Bottom,
px(1000.),
move |ix, window, cx| {
cx.new_view(|cx: &mut ViewContext<Self>| {
let view = cx.view().downgrade();
let message_list =
ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
if let Some(view) = view.upgrade() {
view.update(cx, |view: &mut Self, cx| {
view.render_message(ix, window, cx).into_any_element()
view.update(cx, |view, cx| {
view.render_message(ix, cx).into_any_element()
})
} else {
div().into_any()
}
},
);
});
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, _, cx| {
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
this.load_more_messages(cx);
}
@@ -196,9 +187,9 @@ impl ChatPanel {
}
pub fn load(
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<Model<Self>>> {
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let serialized_panel = if let Some(panel) = cx
.background_executor()
@@ -212,8 +203,8 @@ impl ChatPanel {
None
};
workspace.update_in(&mut cx, |workspace, window, cx| {
let panel = Self::new(workspace, window, cx);
workspace.update(&mut cx, |workspace, cx| {
let panel = Self::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|r| r.round());
@@ -225,7 +216,7 @@ impl ChatPanel {
})
}
fn serialize(&mut self, cx: &mut ModelContext<Self>) {
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
async move {
@@ -241,7 +232,7 @@ impl ChatPanel {
);
}
fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ModelContext<Self>) {
fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
self.markdown_data.clear();
self.message_list.reset(chat.read(cx).message_count());
@@ -260,7 +251,7 @@ impl ChatPanel {
&mut self,
_: Model<ChannelChat>,
event: &ChannelChatEvent,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
ChannelChatEvent::MessagesUpdated {
@@ -293,7 +284,7 @@ impl ChatPanel {
cx.notify();
}
fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) {
fn acknowledge_last_message(&mut self, cx: &mut ViewContext<Self>) {
if self.active && self.is_scrolled_to_bottom {
if let Some((chat, _)) = &self.active_chat {
if let Some(channel_id) = self.channel_id(cx) {
@@ -314,7 +305,7 @@ impl ChatPanel {
&mut self,
message_id: Option<ChannelMessageId>,
reply_to_message: &Option<ChannelMessage>,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let reply_to_message = match reply_to_message {
None => {
@@ -378,8 +369,8 @@ impl ChatPanel {
),
)
.cursor(CursorStyle::PointingHand)
.tooltip(Tooltip::text("Go to message"))
.on_click(cx.listener(move |chat_panel, _, _, cx| {
.tooltip(|cx| Tooltip::text("Go to message", cx))
.on_click(cx.listener(move |chat_panel, _, cx| {
if let Some(channel_id) = current_channel_id {
chat_panel
.select_channel(channel_id, reply_to_message_id.into(), cx)
@@ -389,12 +380,7 @@ impl ChatPanel {
)
}
fn render_message(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> impl IntoElement {
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_chat = &self.active_chat.as_ref().unwrap().0;
let (message, is_continuation_from_previous, is_admin) =
active_chat.update(cx, |active_chat, cx| {
@@ -544,7 +530,7 @@ impl ChatPanel {
.w_full()
.text_ui_sm(cx)
.id(element_id)
.child(text.element("body".into(), window, cx)),
.child(text.element("body".into(), cx)),
)
.when(self.has_open_menu(message_id), |el| {
el.bg(cx.theme().colors().element_selected)
@@ -574,7 +560,7 @@ impl ChatPanel {
},
)
.child(
self.render_popover_buttons(message_id, can_delete_message, can_edit_message, cx)
self.render_popover_buttons(cx, message_id, can_delete_message, can_edit_message)
.mt_neg_2p5(),
)
}
@@ -586,7 +572,7 @@ impl ChatPanel {
}
}
fn render_popover_button(&self, cx: &mut ModelContext<Self>, child: Stateful<Div>) -> Div {
fn render_popover_button(&self, cx: &ViewContext<Self>, child: Stateful<Div>) -> Div {
div()
.w_6()
.bg(cx.theme().colors().element_background)
@@ -596,10 +582,10 @@ impl ChatPanel {
fn render_popover_buttons(
&self,
cx: &ViewContext<Self>,
message_id: Option<u64>,
can_delete_message: bool,
can_edit_message: bool,
cx: &mut ModelContext<Self>,
) -> Div {
h_flex()
.absolute()
@@ -620,16 +606,16 @@ impl ChatPanel {
.id("reply")
.child(
IconButton::new(("reply", message_id), IconName::ReplyArrowRight)
.on_click(cx.listener(move |this, _, window, cx| {
.on_click(cx.listener(move |this, _, cx| {
this.cancel_edit_message(cx);
this.message_editor.update(cx, |editor, cx| {
editor.set_reply_to_message_id(message_id);
window.focus(&editor.focus_handle(cx));
editor.focus_handle(cx).focus(cx);
})
})),
)
.tooltip(Tooltip::text("Reply")),
.tooltip(|cx| Tooltip::text("Reply", cx)),
),
)
})
@@ -642,7 +628,7 @@ impl ChatPanel {
.id("edit")
.child(
IconButton::new(("edit", message_id), IconName::Pencil)
.on_click(cx.listener(move |this, _, window, cx| {
.on_click(cx.listener(move |this, _, cx| {
this.message_editor.update(cx, |editor, cx| {
editor.clear_reply_to_message_id();
@@ -669,18 +655,18 @@ impl ChatPanel {
});
editor.set_edit_message_id(message_id);
editor.focus_handle(cx).focus(window);
editor.focus_handle(cx).focus(cx);
}
})
})),
)
.tooltip(Tooltip::text("Edit")),
.tooltip(|cx| Tooltip::text("Edit", cx)),
),
)
})
})
.when_some(message_id, |el, message_id| {
let this = cx.model().clone();
let this = cx.view().clone();
el.child(
self.render_popover_button(
@@ -692,36 +678,34 @@ impl ChatPanel {
("trigger", message_id),
IconName::Ellipsis,
))
.menu(move |window, cx| {
.menu(move |cx| {
Some(Self::render_message_menu(
&this,
message_id,
can_delete_message,
window,
cx,
))
}),
)
.id("more")
.tooltip(Tooltip::text("More")),
.tooltip(|cx| Tooltip::text("More", cx)),
),
)
})
}
fn render_message_menu(
this: &Model<Self>,
this: &View<Self>,
message_id: u64,
can_delete_message: bool,
window: &mut Window,
cx: &mut AppContext,
) -> Model<ContextMenu> {
cx: &mut WindowContext,
) -> View<ContextMenu> {
let menu = {
ContextMenu::build(window, cx, move |menu, window, _| {
ContextMenu::build(cx, move |menu, cx| {
menu.entry(
"Copy message text",
None,
window.handler_for(this, move |this, _, cx| {
cx.handler_for(this, move |this, cx| {
if let Some(message) = this.active_chat().and_then(|active_chat| {
active_chat.read(cx).find_loaded_message(message_id)
}) {
@@ -734,21 +718,15 @@ impl ChatPanel {
menu.entry(
"Delete message",
None,
window.handler_for(this, move |this, _, cx| {
this.remove_message(message_id, cx)
}),
cx.handler_for(this, move |this, cx| this.remove_message(message_id, cx)),
)
})
})
};
this.update(cx, |this, cx| {
let subscription = cx.subscribe_in(
&menu,
window,
|this: &mut Self, _, _: &DismissEvent, _, _| {
this.open_context_menu = None;
},
);
let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| {
this.open_context_menu = None;
});
this.open_context_menu = Some((message_id, subscription));
});
menu
@@ -799,19 +777,19 @@ impl ChatPanel {
);
rich_text.custom_ranges.push(range);
rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, _, cx| {
Some(Tooltip::simple(edit_timestamp_text.clone(), cx))
rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, cx| {
Some(Tooltip::text(edit_timestamp_text.clone(), cx))
})
}
}
rich_text
}
fn send(&mut self, _: &Confirm, window: &mut Window, cx: &mut ModelContext<Self>) {
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
let message = self
.message_editor
.update(cx, |editor, cx| editor.take_message(window, cx));
.update(cx, |editor, cx| editor.take_message(cx));
if let Some(id) = self.message_editor.read(cx).edit_message_id() {
self.message_editor.update(cx, |editor, _| {
@@ -833,13 +811,13 @@ impl ChatPanel {
}
}
fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) {
fn remove_message(&mut self, id: u64, cx: &mut ViewContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
chat.update(cx, |chat, cx| chat.remove_message(id, cx).detach())
}
}
fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) {
fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
chat.update(cx, |channel, cx| {
if let Some(task) = channel.load_more_messages(cx) {
@@ -853,7 +831,7 @@ impl ChatPanel {
&mut self,
selected_channel_id: ChannelId,
scroll_to_message_id: Option<u64>,
cx: &mut ModelContext<ChatPanel>,
cx: &mut ViewContext<ChatPanel>,
) -> Task<Result<()>> {
let open_chat = self
.active_chat
@@ -879,18 +857,20 @@ impl ChatPanel {
if let Some(message_id) = scroll_to_message_id {
if let Some(item_ix) =
ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone())
ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
.await
{
this.update(&mut cx, |this, cx| {
if let Some(highlight_message_id) = highlight_message_id {
let task = cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(Duration::from_secs(2)).await;
this.update(&mut cx, |this, cx| {
this.highlighted_message.take();
cx.notify();
})
.ok();
let task = cx.spawn({
|this, mut cx| async move {
cx.background_executor().timer(Duration::from_secs(2)).await;
this.update(&mut cx, |this, cx| {
this.highlighted_message.take();
cx.notify();
})
.ok();
}
});
this.highlighted_message = Some((highlight_message_id, task));
@@ -911,12 +891,12 @@ impl ChatPanel {
})
}
fn close_reply_preview(&mut self, cx: &mut ModelContext<Self>) {
fn close_reply_preview(&mut self, cx: &mut ViewContext<Self>) {
self.message_editor
.update(cx, |editor, _| editor.clear_reply_to_message_id());
}
fn cancel_edit_message(&mut self, cx: &mut ModelContext<Self>) {
fn cancel_edit_message(&mut self, cx: &mut ViewContext<Self>) {
self.message_editor.update(cx, |editor, cx| {
// only clear the editor input if we were editing a message
if editor.edit_message_id().is_none() {
@@ -939,7 +919,7 @@ impl ChatPanel {
}
impl Render for ChatPanel {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let channel_id = self
.active_chat
.as_ref()
@@ -991,12 +971,11 @@ impl Render for ChatPanel {
.full_width()
.key_binding(KeyBinding::for_action(
&collab_panel::ToggleFocus,
window,
cx,
))
.on_click(|_, window, cx| {
window.dispatch_action(
.on_click(|_, cx| {
cx.dispatch_action(
collab_panel::ToggleFocus.boxed_clone(),
cx,
)
}),
),
@@ -1020,8 +999,8 @@ impl Render for ChatPanel {
.child(
IconButton::new("cancel-edit-message", IconName::Close)
.shape(ui::IconButtonShape::Square)
.tooltip(Tooltip::text("Cancel edit message"))
.on_click(cx.listener(move |this, _, _, cx| {
.tooltip(|cx| Tooltip::text("Cancel edit message", cx))
.on_click(cx.listener(move |this, _, cx| {
this.cancel_edit_message(cx);
})),
),
@@ -1066,7 +1045,7 @@ impl Render for ChatPanel {
)
.when_some(channel_id, |this, channel_id| {
this.cursor_pointer().on_click(cx.listener(
move |chat_panel, _, _, cx| {
move |chat_panel, _, cx| {
chat_panel
.select_channel(
channel_id,
@@ -1082,8 +1061,8 @@ impl Render for ChatPanel {
.child(
IconButton::new("close-reply-preview", IconName::Close)
.shape(ui::IconButtonShape::Square)
.tooltip(Tooltip::text("Close reply"))
.on_click(cx.listener(move |this, _, _, cx| {
.tooltip(|cx| Tooltip::text("Close reply", cx))
.on_click(cx.listener(move |this, _, cx| {
this.close_reply_preview(cx);
})),
),
@@ -1094,7 +1073,7 @@ impl Render for ChatPanel {
Some(
h_flex()
.p_2()
.on_action(cx.listener(|this, _: &actions::Cancel, _, cx| {
.on_action(cx.listener(|this, _: &actions::Cancel, cx| {
this.cancel_edit_message(cx);
this.close_reply_preview(cx);
}))
@@ -1106,7 +1085,7 @@ impl Render for ChatPanel {
}
}
impl Focusable for ChatPanel {
impl FocusableView for ChatPanel {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
if self.active_chat.is_some() {
self.message_editor.read(cx).focus_handle(cx)
@@ -1117,7 +1096,7 @@ impl Focusable for ChatPanel {
}
impl Panel for ChatPanel {
fn position(&self, _: &Window, cx: &AppContext) -> DockPosition {
fn position(&self, cx: &WindowContext) -> DockPosition {
ChatPanelSettings::get_global(cx).dock
}
@@ -1125,12 +1104,7 @@ impl Panel for ChatPanel {
matches!(position, DockPosition::Left | DockPosition::Right)
}
fn set_position(
&mut self,
position: DockPosition,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
settings::update_settings_file::<ChatPanelSettings>(
self.fs.clone(),
cx,
@@ -1138,18 +1112,18 @@ impl Panel for ChatPanel {
);
}
fn size(&self, _: &Window, cx: &AppContext) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
}
fn set_size(&mut self, size: Option<Pixels>, _: &mut Window, cx: &mut ModelContext<Self>) {
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut ModelContext<Self>) {
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
self.active = active;
if active {
self.acknowledge_last_message(cx);
@@ -1160,19 +1134,24 @@ impl Panel for ChatPanel {
"ChatPanel"
}
fn icon(&self, _: &Window, cx: &AppContext) -> Option<ui::IconName> {
match ChatPanelSettings::get_global(cx).button {
ChatPanelButton::Never => None,
ChatPanelButton::Always => Some(ui::IconName::MessageBubbles),
ChatPanelButton::WhenInCall => ActiveCall::global(cx)
.read(cx)
.room()
.filter(|room| room.read(cx).contains_guests())
.map(|_| ui::IconName::MessageBubbles),
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
let show_icon = match ChatPanelSettings::get_global(cx).button {
ChatPanelButton::Never => false,
ChatPanelButton::Always => true,
ChatPanelButton::WhenInCall => {
let is_in_call = ActiveCall::global(cx)
.read(cx)
.room()
.map_or(false, |room| room.read(cx).contains_guests());
self.active || is_in_call
}
};
show_icon.then(|| ui::IconName::MessageBubbles)
}
fn icon_tooltip(&self, _: &Window, _: &AppContext) -> Option<&'static str> {
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
Some("Chat Panel")
}
@@ -1180,7 +1159,7 @@ impl Panel for ChatPanel {
Box::new(ToggleFocus)
}
fn starts_open(&self, _: &Window, cx: &AppContext) -> bool {
fn starts_open(&self, cx: &WindowContext) -> bool {
ActiveCall::global(cx)
.read(cx)
.room()

View File

@@ -5,8 +5,8 @@ use collections::HashSet;
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AsyncWindowContext, Focusable, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
ModelContext, Render, Task, TextStyle, WeakModel, Window,
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
Render, Task, TextStyle, View, ViewContext, WeakView,
};
use language::{
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
@@ -42,7 +42,7 @@ static MENTIONS_SEARCH: LazyLock<SearchQuery> = LazyLock::new(|| {
});
pub struct MessageEditor {
pub editor: Model<Editor>,
pub editor: View<Editor>,
user_store: Model<UserStore>,
channel_chat: Option<Model<ChannelChat>>,
mentions: Vec<UserId>,
@@ -51,7 +51,7 @@ pub struct MessageEditor {
edit_message_id: Option<u64>,
}
struct MessageEditorCompletionProvider(WeakModel<MessageEditor>);
struct MessageEditorCompletionProvider(WeakView<MessageEditor>);
impl CompletionProvider for MessageEditorCompletionProvider {
fn completions(
@@ -59,14 +59,13 @@ impl CompletionProvider for MessageEditorCompletionProvider {
buffer: &Model<Buffer>,
buffer_position: language::Anchor,
_: editor::CompletionContext,
window: &mut Window,
cx: &mut ModelContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> Task<anyhow::Result<Vec<Completion>>> {
let Some(handle) = self.0.upgrade() else {
return Task::ready(Ok(Vec::new()));
};
handle.update(cx, |message_editor, cx| {
message_editor.completions(buffer, buffer_position, window, cx)
message_editor.completions(buffer, buffer_position, cx)
})
}
@@ -75,8 +74,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
_buffer: Model<Buffer>,
_completion_indices: Vec<usize>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_window: &mut Window,
_cx: &mut ModelContext<Editor>,
_cx: &mut ViewContext<Editor>,
) -> Task<anyhow::Result<bool>> {
Task::ready(Ok(false))
}
@@ -87,8 +85,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
_position: language::Anchor,
text: &str,
_trigger_in_words: bool,
_window: &mut Window,
_cx: &mut ModelContext<Editor>,
_cx: &mut ViewContext<Editor>,
) -> bool {
text == "@"
}
@@ -99,11 +96,10 @@ impl MessageEditor {
language_registry: Arc<LanguageRegistry>,
user_store: Model<UserStore>,
channel_chat: Option<Model<ChannelChat>>,
editor: Model<Editor>,
window: &mut Window,
cx: &mut ModelContext<Self>,
editor: View<Editor>,
cx: &mut ViewContext<Self>,
) -> Self {
let this = cx.model().downgrade();
let this = cx.view().downgrade();
editor.update(cx, |editor, cx| {
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_use_autoclose(false);
@@ -125,8 +121,7 @@ impl MessageEditor {
.as_singleton()
.expect("message editor must be singleton");
cx.subscribe_in(&buffer, window, Self::on_buffer_event)
.detach();
cx.subscribe(&buffer, Self::on_buffer_event).detach();
cx.observe_global::<settings::SettingsStore>(|view, cx| {
view.editor.update(cx, |editor, cx| {
editor.set_auto_replace_emoji_shortcode(
@@ -139,7 +134,7 @@ impl MessageEditor {
.detach();
let markdown = language_registry.language_for_name("Markdown");
cx.spawn_in(window, |_, mut cx| async move {
cx.spawn(|_, mut cx| async move {
let markdown = markdown.await.context("failed to load Markdown language")?;
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(Some(markdown), cx)
@@ -182,7 +177,7 @@ impl MessageEditor {
self.edit_message_id = None;
}
pub fn set_channel_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ModelContext<Self>) {
pub fn set_channel_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
let channel_id = chat.read(cx).channel_id;
self.channel_chat = Some(chat);
let channel_name = ChannelStore::global(cx)
@@ -198,11 +193,7 @@ impl MessageEditor {
});
}
pub fn take_message(
&mut self,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> MessageParams {
pub fn take_message(&mut self, cx: &mut ViewContext<Self>) -> MessageParams {
self.editor.update(cx, |editor, cx| {
let highlights = editor.text_highlights::<Self>(cx);
let text = editor.text(cx);
@@ -217,7 +208,7 @@ impl MessageEditor {
Vec::new()
};
editor.clear(window, cx);
editor.clear(cx);
self.mentions.clear();
let reply_to_message_id = std::mem::take(&mut self.reply_to_message_id);
@@ -231,14 +222,13 @@ impl MessageEditor {
fn on_buffer_event(
&mut self,
buffer: &Model<Buffer>,
buffer: Model<Buffer>,
event: &language::BufferEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
let buffer = buffer.read(cx).snapshot();
self.mentions_task = Some(cx.spawn_in(window, |this, cx| async move {
self.mentions_task = Some(cx.spawn(|this, cx| async move {
cx.background_executor()
.timer(MENTIONS_DEBOUNCE_INTERVAL)
.await;
@@ -251,14 +241,13 @@ impl MessageEditor {
&mut self,
buffer: &Model<Buffer>,
end_anchor: Anchor,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Task<Result<Vec<Completion>>> {
if let Some((start_anchor, query, candidates)) =
self.collect_mention_candidates(buffer, end_anchor, cx)
{
if !candidates.is_empty() {
return cx.spawn_in(window, |_, cx| async move {
return cx.spawn(|_, cx| async move {
Ok(Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
@@ -275,7 +264,7 @@ impl MessageEditor {
self.collect_emoji_candidates(buffer, end_anchor, cx)
{
if !candidates.is_empty() {
return cx.spawn_in(window, |_, cx| async move {
return cx.spawn(|_, cx| async move {
Ok(Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
@@ -349,7 +338,7 @@ impl MessageEditor {
&mut self,
buffer: &Model<Buffer>,
end_anchor: Anchor,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
let end_offset = end_anchor.to_offset(buffer.read(cx));
@@ -398,7 +387,7 @@ impl MessageEditor {
&mut self,
buffer: &Model<Buffer>,
end_anchor: Anchor,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Option<(Anchor, String, &'static [StringMatchCandidate])> {
static EMOJI_FUZZY_MATCH_CANDIDATES: LazyLock<Vec<StringMatchCandidate>> =
LazyLock::new(|| {
@@ -456,7 +445,7 @@ impl MessageEditor {
}
async fn find_mentions(
this: WeakModel<MessageEditor>,
this: WeakView<MessageEditor>,
buffer: BufferSnapshot,
mut cx: AsyncWindowContext,
) {
@@ -516,7 +505,7 @@ impl MessageEditor {
}
impl Render for MessageEditor {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ use client::{
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, anchored, deferred, div, AppContext, ClipboardItem, DismissEvent, EventEmitter,
Focusable, Model, ModelContext, ParentElement, Render, Styled, Subscription, Task, WeakModel,
Window,
FocusableView, Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
@@ -26,7 +26,7 @@ actions!(
);
pub struct ChannelModal {
picker: Model<Picker<ChannelModalDelegate>>,
picker: View<Picker<ChannelModalDelegate>>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
}
@@ -37,12 +37,11 @@ impl ChannelModal {
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
mode: Mode,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
let channel_modal = cx.model().downgrade();
let picker = cx.new_model(|cx| {
let channel_modal = cx.view().downgrade();
let picker = cx.new_view(|cx| {
Picker::uniform_list(
ChannelModalDelegate {
channel_modal,
@@ -58,7 +57,6 @@ impl ChannelModal {
has_all_members: false,
mode,
},
window,
cx,
)
.modal(false)
@@ -71,32 +69,27 @@ impl ChannelModal {
}
}
fn toggle_mode(&mut self, _: &ToggleMode, window: &mut Window, cx: &mut ModelContext<Self>) {
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
let mode = match self.picker.read(cx).delegate.mode {
Mode::ManageMembers => Mode::InviteMembers,
Mode::InviteMembers => Mode::ManageMembers,
};
self.set_mode(mode, window, cx);
self.set_mode(mode, cx);
}
fn set_mode(&mut self, mode: Mode, window: &mut Window, cx: &mut ModelContext<Self>) {
fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
let delegate = &mut picker.delegate;
delegate.mode = mode;
delegate.selected_index = 0;
picker.set_query("", window, cx);
picker.update_matches(picker.query(cx), window, cx);
picker.set_query("", cx);
picker.update_matches(picker.query(cx), cx);
cx.notify()
});
cx.notify()
}
fn set_channel_visibility(
&mut self,
selection: &ToggleState,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn set_channel_visibility(&mut self, selection: &ToggleState, cx: &mut ViewContext<Self>) {
self.channel_store.update(cx, |channel_store, cx| {
channel_store
.set_channel_visibility(
@@ -112,7 +105,7 @@ impl ChannelModal {
});
}
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut ModelContext<Self>) {
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
}
}
@@ -120,14 +113,14 @@ impl ChannelModal {
impl EventEmitter<DismissEvent> for ChannelModal {}
impl ModalView for ChannelModal {}
impl Focusable for ChannelModal {
impl FocusableView for ChannelModal {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ChannelModal {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let channel_store = self.channel_store.read(cx);
let Some(channel) = channel_store.channel_for_id(self.channel_id) else {
return div();
@@ -176,7 +169,7 @@ impl Render for ChannelModal {
Some(
Button::new("copy-link", "Copy Link")
.label_size(LabelSize::Small)
.on_click(cx.listener(move |this, _, _, cx| {
.on_click(cx.listener(move |this, _, cx| {
if let Some(channel) = this
.channel_store
.read(cx)
@@ -204,8 +197,8 @@ impl Render for ChannelModal {
this.border_color(cx.theme().colors().border)
})
.child(Label::new("Manage Members"))
.on_click(cx.listener(|this, _, window, cx| {
this.set_mode(Mode::ManageMembers, window, cx);
.on_click(cx.listener(|this, _, cx| {
this.set_mode(Mode::ManageMembers, cx);
})),
)
.child(
@@ -219,8 +212,8 @@ impl Render for ChannelModal {
this.border_color(cx.theme().colors().border)
})
.child(Label::new("Invite Members"))
.on_click(cx.listener(|this, _, window, cx| {
this.set_mode(Mode::InviteMembers, window, cx);
.on_click(cx.listener(|this, _, cx| {
this.set_mode(Mode::InviteMembers, cx);
})),
),
),
@@ -236,7 +229,7 @@ pub enum Mode {
}
pub struct ChannelModalDelegate {
channel_modal: WeakModel<ChannelModal>,
channel_modal: WeakView<ChannelModal>,
matching_users: Vec<Arc<User>>,
matching_member_indices: Vec<usize>,
user_store: Model<UserStore>,
@@ -247,13 +240,13 @@ pub struct ChannelModalDelegate {
match_candidates: Vec<StringMatchCandidate>,
members: Vec<ChannelMembership>,
has_all_members: bool,
context_menu: Option<(Model<ContextMenu>, Subscription)>,
context_menu: Option<(View<ContextMenu>, Subscription)>,
}
impl PickerDelegate for ChannelModalDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search collaborator by username...".into()
}
@@ -268,21 +261,11 @@ impl PickerDelegate for ChannelModalDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
match self.mode {
Mode::ManageMembers => {
if self.has_all_members {
@@ -301,7 +284,7 @@ impl PickerDelegate for ChannelModalDelegate {
cx.background_executor().clone(),
));
cx.spawn_in(window, |picker, mut cx| async move {
cx.spawn(|picker, mut cx| async move {
picker
.update(&mut cx, |picker, cx| {
let delegate = &mut picker.delegate;
@@ -317,7 +300,7 @@ impl PickerDelegate for ChannelModalDelegate {
let search_members = self.channel_store.update(cx, |store, cx| {
store.fuzzy_search_members(self.channel_id, query.clone(), 100, cx)
});
cx.spawn_in(window, |picker, mut cx| async move {
cx.spawn(|picker, mut cx| async move {
async {
let members = search_members.await?;
picker.update(&mut cx, |picker, cx| {
@@ -339,7 +322,7 @@ impl PickerDelegate for ChannelModalDelegate {
let search_users = self
.user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
cx.spawn_in(window, |picker, mut cx| async move {
cx.spawn(|picker, mut cx| async move {
async {
let users = search_users.await?;
picker.update(&mut cx, |picker, cx| {
@@ -355,26 +338,26 @@ impl PickerDelegate for ChannelModalDelegate {
}
}
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(selected_user) = self.user_at_index(self.selected_index) {
if Some(selected_user.id) == self.user_store.read(cx).current_user().map(|user| user.id)
{
return;
}
match self.mode {
Mode::ManageMembers => self.show_context_menu(self.selected_index, window, cx),
Mode::ManageMembers => self.show_context_menu(self.selected_index, cx),
Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
Some(proto::channel_member::Kind::Invitee) => {
self.remove_member(selected_user.id, window, cx);
self.remove_member(selected_user.id, cx);
}
Some(proto::channel_member::Kind::Member) => {}
None => self.invite_member(selected_user, window, cx),
None => self.invite_member(selected_user, cx),
},
}
}
}
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if self.context_menu.is_none() {
self.channel_modal
.update(cx, |_, cx| {
@@ -388,8 +371,7 @@ impl PickerDelegate for ChannelModalDelegate {
&self,
ix: usize,
selected: bool,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let user = self.user_at_index(ix)?;
let membership = self.member_at_index(ix);
@@ -488,39 +470,33 @@ impl ChannelModalDelegate {
&mut self,
user_id: UserId,
new_role: ChannelRole,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<()> {
let update = self.channel_store.update(cx, |store, cx| {
store.set_member_role(self.channel_id, user_id, new_role, cx)
});
cx.spawn_in(window, |picker, mut cx| async move {
cx.spawn(|picker, mut cx| async move {
update.await?;
picker.update_in(&mut cx, |picker, window, cx| {
picker.update(&mut cx, |picker, cx| {
let this = &mut picker.delegate;
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user_id) {
member.role = new_role;
}
cx.focus_self(window);
cx.focus_self();
cx.notify();
})
})
.detach_and_prompt_err("Failed to update role", window, cx, |_, _, _| None);
.detach_and_prompt_err("Failed to update role", cx, |_, _| None);
Some(())
}
fn remove_member(
&mut self,
user_id: UserId,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Option<()> {
fn remove_member(&mut self, user_id: UserId, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
let update = self.channel_store.update(cx, |store, cx| {
store.remove_member(self.channel_id, user_id, cx)
});
cx.spawn_in(window, |picker, mut cx| async move {
cx.spawn(|picker, mut cx| async move {
update.await?;
picker.update_in(&mut cx, |picker, window, cx| {
picker.update(&mut cx, |picker, cx| {
let this = &mut picker.delegate;
if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
this.members.remove(ix);
@@ -538,25 +514,20 @@ impl ChannelModalDelegate {
.selected_index
.min(this.matching_member_indices.len().saturating_sub(1));
picker.focus(window, cx);
picker.focus(cx);
cx.notify();
})
})
.detach_and_prompt_err("Failed to remove member", window, cx, |_, _, _| None);
.detach_and_prompt_err("Failed to remove member", cx, |_, _| None);
Some(())
}
fn invite_member(
&mut self,
user: Arc<User>,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
let invite_member = self.channel_store.update(cx, |store, cx| {
store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx)
});
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
invite_member.await?;
this.update(&mut cx, |this, cx| {
@@ -573,30 +544,25 @@ impl ChannelModalDelegate {
cx.notify();
})
})
.detach_and_prompt_err("Failed to invite member", window, cx, |_, _, _| None);
.detach_and_prompt_err("Failed to invite member", cx, |_, _| None);
}
fn show_context_menu(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) {
fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
let Some(membership) = self.member_at_index(ix) else {
return;
};
let user_id = membership.user.id;
let picker = cx.model().clone();
let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
let picker = cx.view().clone();
let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
let role = membership.role;
if role == ChannelRole::Admin || role == ChannelRole::Member {
let picker = picker.clone();
menu = menu.entry("Demote to Guest", None, move |window, cx| {
menu = menu.entry("Demote to Guest", None, move |cx| {
picker.update(cx, |picker, cx| {
picker
.delegate
.set_user_role(user_id, ChannelRole::Guest, window, cx);
.set_user_role(user_id, ChannelRole::Guest, cx);
})
});
}
@@ -609,22 +575,22 @@ impl ChannelModalDelegate {
"Demote to Member"
};
menu = menu.entry(label, None, move |window, cx| {
menu = menu.entry(label, None, move |cx| {
picker.update(cx, |picker, cx| {
picker
.delegate
.set_user_role(user_id, ChannelRole::Member, window, cx);
.set_user_role(user_id, ChannelRole::Member, cx);
})
});
}
if role == ChannelRole::Member || role == ChannelRole::Guest {
let picker = picker.clone();
menu = menu.entry("Promote to Admin", None, move |window, cx| {
menu = menu.entry("Promote to Admin", None, move |cx| {
picker.update(cx, |picker, cx| {
picker
.delegate
.set_user_role(user_id, ChannelRole::Admin, window, cx);
.set_user_role(user_id, ChannelRole::Admin, cx);
})
});
};
@@ -632,24 +598,20 @@ impl ChannelModalDelegate {
menu = menu.separator();
menu = menu.entry("Remove from Channel", None, {
let picker = picker.clone();
move |window, cx| {
move |cx| {
picker.update(cx, |picker, cx| {
picker.delegate.remove_member(user_id, window, cx);
picker.delegate.remove_member(user_id, cx);
})
}
});
menu
});
window.focus(&context_menu.focus_handle(cx));
let subscription = cx.subscribe_in(
&context_menu,
window,
|picker, _, _: &DismissEvent, window, cx| {
picker.delegate.context_menu = None;
picker.focus(window, cx);
cx.notify();
},
);
cx.focus_view(&context_menu);
let subscription = cx.subscribe(&context_menu, |picker, _, _: &DismissEvent, cx| {
picker.delegate.context_menu = None;
picker.focus(cx);
cx.notify();
});
self.context_menu = Some((context_menu, subscription));
}
}

View File

@@ -1,7 +1,7 @@
use client::{ContactRequestStatus, User, UserStore};
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, Focusable, Model, ModelContext,
ParentElement as _, Render, Styled, Task, WeakModel, Window,
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ParentElement as _,
Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
@@ -10,35 +10,31 @@ use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView;
pub struct ContactFinder {
picker: Model<Picker<ContactFinderDelegate>>,
picker: View<Picker<ContactFinderDelegate>>,
}
impl ContactFinder {
pub fn new(
user_store: Model<UserStore>,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Self {
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let delegate = ContactFinderDelegate {
parent: cx.model().downgrade(),
parent: cx.view().downgrade(),
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
};
let picker = cx.new_model(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx).modal(false));
Self { picker }
}
pub fn set_query(&mut self, query: String, window: &mut Window, cx: &mut ModelContext<Self>) {
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.set_query(query, window, cx);
picker.set_query(query, cx);
});
}
}
impl Render for ContactFinder {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.elevation_3(cx)
.child(
@@ -57,7 +53,7 @@ impl Render for ContactFinder {
}
pub struct ContactFinderDelegate {
parent: WeakModel<ContactFinder>,
parent: WeakView<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: Model<UserStore>,
selected_index: usize,
@@ -66,7 +62,7 @@ pub struct ContactFinderDelegate {
impl EventEmitter<DismissEvent> for ContactFinder {}
impl ModalView for ContactFinder {}
impl Focusable for ContactFinder {
impl FocusableView for ContactFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
@@ -83,30 +79,20 @@ impl PickerDelegate for ContactFinderDelegate {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut ModelContext<Picker<Self>>,
) {
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut AppContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search collaborator by username...".into()
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
) -> Task<()> {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search_users = self
.user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
cx.spawn_in(window, |picker, mut cx| async move {
cx.spawn(|picker, mut cx| async move {
async {
let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| {
@@ -120,7 +106,7 @@ impl PickerDelegate for ContactFinderDelegate {
})
}
fn confirm(&mut self, _: bool, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(user) = self.potential_contacts.get(self.selected_index) {
let user_store = self.user_store.read(cx);
match user_store.contact_request_status(user) {
@@ -139,7 +125,7 @@ impl PickerDelegate for ContactFinderDelegate {
}
}
fn dismissed(&mut self, _: &mut Window, cx: &mut ModelContext<Picker<Self>>) {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.parent
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
@@ -149,8 +135,7 @@ impl PickerDelegate for ContactFinderDelegate {
&self,
ix: usize,
selected: bool,
_: &mut Window,
cx: &mut ModelContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user);

View File

@@ -7,9 +7,10 @@ use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt;
use gpui::{
actions, div, img, list, px, AnyElement, AppContext, AsyncWindowContext, CursorStyle,
DismissEvent, Element, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement,
ListAlignment, ListScrollEvent, ListState, Model, ModelContext, ParentElement, Render,
StatefulInteractiveElement, Styled, Task, WeakModel, Window,
DismissEvent, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
IntoElement, ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render,
StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext, WeakView,
WindowContext,
};
use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
use project::Fs;
@@ -44,7 +45,7 @@ pub struct NotificationPanel {
notification_list: ListState,
pending_serialization: Task<Option<()>>,
subscriptions: Vec<gpui::Subscription>,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
current_notification_toast: Option<(u64, Task<()>)>,
local_timezone: UtcOffset,
focus_handle: FocusHandle,
@@ -75,31 +76,27 @@ pub struct NotificationPresenter {
actions!(notification_panel, [ToggleFocus]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_models(|workspace: &mut Workspace, _, _| {
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
workspace.toggle_panel_focus::<NotificationPanel>(window, cx);
cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<NotificationPanel>(cx);
});
})
.detach();
}
impl NotificationPanel {
pub fn new(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut ModelContext<Workspace>,
) -> Model<Self> {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let fs = workspace.app_state().fs.clone();
let client = workspace.app_state().client.clone();
let user_store = workspace.app_state().user_store.clone();
let workspace_handle = workspace.weak_handle();
cx.new_model(|cx| {
cx.new_view(|cx: &mut ViewContext<Self>| {
let mut status = client.status();
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
while (status.next().await).is_some() {
if this
.update(&mut cx, |_: &mut Self, cx| {
.update(&mut cx, |_, cx| {
cx.notify();
})
.is_err()
@@ -110,17 +107,17 @@ impl NotificationPanel {
})
.detach();
let view = cx.model().downgrade();
let view = cx.view().downgrade();
let notification_list =
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, window, cx| {
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
view.upgrade()
.and_then(|view| {
view.update(cx, |this, cx| this.render_notification(ix, window, cx))
view.update(cx, |this, cx| this.render_notification(ix, cx))
})
.unwrap_or_else(|| div().into_any())
});
notification_list.set_scroll_handler(cx.listener(
|this, event: &ListScrollEvent, _, cx| {
|this, event: &ListScrollEvent, cx| {
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
if let Some(task) = this
.notification_store
@@ -152,34 +149,27 @@ impl NotificationPanel {
unseen_notifications: Vec::new(),
};
let mut old_dock_position = this.position(window, cx);
let mut old_dock_position = this.position(cx);
this.subscriptions.extend([
cx.observe(&this.notification_store, |_, _, cx| cx.notify()),
cx.subscribe_in(
&this.notification_store,
window,
Self::on_notification_event,
),
cx.observe_global_in::<SettingsStore>(
window,
move |this: &mut Self, window, cx| {
let new_dock_position = this.position(window, cx);
if new_dock_position != old_dock_position {
old_dock_position = new_dock_position;
cx.emit(Event::DockPositionChanged);
}
cx.notify();
},
),
cx.subscribe(&this.notification_store, Self::on_notification_event),
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
let new_dock_position = this.position(cx);
if new_dock_position != old_dock_position {
old_dock_position = new_dock_position;
cx.emit(Event::DockPositionChanged);
}
cx.notify();
}),
]);
this
})
}
pub fn load(
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<Model<Self>>> {
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let serialized_panel = if let Some(panel) = cx
.background_executor()
@@ -193,8 +183,8 @@ impl NotificationPanel {
None
};
workspace.update_in(&mut cx, |workspace, window, cx| {
let panel = Self::new(workspace, window, cx);
workspace.update(&mut cx, |workspace, cx| {
let panel = Self::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|w| w.round());
@@ -206,7 +196,7 @@ impl NotificationPanel {
})
}
fn serialize(&mut self, cx: &mut ModelContext<Self>) {
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
async move {
@@ -222,12 +212,7 @@ impl NotificationPanel {
);
}
fn render_notification(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut ModelContext<Self>,
) -> Option<AnyElement> {
fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let entry = self.notification_store.read(cx).notification_at(ix)?;
let notification_id = entry.id;
let now = OffsetDateTime::now_utc();
@@ -244,7 +229,7 @@ impl NotificationPanel {
let notification = entry.notification.clone();
if self.active && !entry.is_read {
self.did_render_notification(notification_id, &notification, window, cx);
self.did_render_notification(notification_id, &notification, cx);
}
let relative_timestamp = time_format::format_localized_timestamp(
@@ -274,8 +259,8 @@ impl NotificationPanel {
.when(can_navigate, |el| {
el.cursor(CursorStyle::PointingHand).on_click({
let notification = notification.clone();
cx.listener(move |this, _, window, cx| {
this.did_click_notification(&notification, window, cx)
cx.listener(move |this, _, cx| {
this.did_click_notification(&notification, cx)
})
})
})
@@ -303,8 +288,8 @@ impl NotificationPanel {
.rounded_md()
})
.child(Label::new(relative_timestamp).color(Color::Muted))
.tooltip(move |_, cx| {
Tooltip::simple(absolute_timestamp.clone(), cx)
.tooltip(move |cx| {
Tooltip::text(absolute_timestamp.clone(), cx)
}),
)
.children(if let Some(is_accepted) = response {
@@ -322,8 +307,8 @@ impl NotificationPanel {
.justify_end()
.child(Button::new("decline", "Decline").on_click({
let notification = notification.clone();
let view = cx.model().clone();
move |_, _, cx| {
let view = cx.view().clone();
move |_, cx| {
view.update(cx, |this, cx| {
this.respond_to_notification(
notification.clone(),
@@ -335,8 +320,8 @@ impl NotificationPanel {
}))
.child(Button::new("accept", "Accept").on_click({
let notification = notification.clone();
let view = cx.model().clone();
move |_, _, cx| {
let view = cx.view().clone();
move |_, cx| {
view.update(cx, |this, cx| {
this.respond_to_notification(
notification.clone(),
@@ -430,8 +415,7 @@ impl NotificationPanel {
&mut self,
notification_id: u64,
notification: &Notification,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
let should_mark_as_read = match notification {
Notification::ContactRequestAccepted { .. } => true,
@@ -445,7 +429,7 @@ impl NotificationPanel {
.entry(notification_id)
.or_insert_with(|| {
let client = self.client.clone();
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(MARK_AS_READ_DELAY).await;
client
.request(proto::MarkNotificationRead { notification_id })
@@ -459,12 +443,7 @@ impl NotificationPanel {
}
}
fn did_click_notification(
&mut self,
notification: &Notification,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn did_click_notification(&mut self, notification: &Notification, cx: &mut ViewContext<Self>) {
if let Notification::ChannelMessageMention {
message_id,
channel_id,
@@ -472,9 +451,9 @@ impl NotificationPanel {
} = notification.clone()
{
if let Some(workspace) = self.workspace.upgrade() {
window.defer(cx, move |window, cx| {
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(window, cx) {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.select_channel(ChannelId(channel_id), Some(message_id), cx)
@@ -487,11 +466,7 @@ impl NotificationPanel {
}
}
fn is_showing_notification(
&self,
notification: &Notification,
cx: &mut ModelContext<Self>,
) -> bool {
fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
if !self.active {
return false;
}
@@ -515,17 +490,16 @@ impl NotificationPanel {
fn on_notification_event(
&mut self,
_: &Model<NotificationStore>,
_: Model<NotificationStore>,
event: &NotificationEvent,
window: &mut Window,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
match event {
NotificationEvent::NewNotification { entry } => {
if !self.is_showing_notification(&entry.notification, cx) {
self.unseen_notifications.push(entry.clone());
}
self.add_toast(entry, window, cx);
self.add_toast(entry, cx);
}
NotificationEvent::NotificationRemoved { entry }
| NotificationEvent::NotificationRead { entry } => {
@@ -542,12 +516,7 @@ impl NotificationPanel {
}
}
fn add_toast(
&mut self,
entry: &NotificationEntry,
window: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
if self.is_showing_notification(&entry.notification, cx) {
return;
}
@@ -560,7 +529,7 @@ impl NotificationPanel {
let notification_id = entry.id;
self.current_notification_toast = Some((
notification_id,
cx.spawn_in(window, |this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(TOAST_DURATION).await;
this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
.ok();
@@ -573,8 +542,8 @@ impl NotificationPanel {
workspace.dismiss_notification(&id, cx);
workspace.show_notification(id, cx, |cx| {
let workspace = cx.model().downgrade();
cx.new_model(|_| NotificationToast {
let workspace = cx.view().downgrade();
cx.new_view(|_| NotificationToast {
notification_id,
actor,
text,
@@ -585,7 +554,7 @@ impl NotificationPanel {
.ok();
}
fn remove_toast(&mut self, notification_id: u64, cx: &mut ModelContext<Self>) {
fn remove_toast(&mut self, notification_id: u64, cx: &mut ViewContext<Self>) {
if let Some((current_id, _)) = &self.current_notification_toast {
if *current_id == notification_id {
self.current_notification_toast.take();
@@ -603,8 +572,7 @@ impl NotificationPanel {
&mut self,
notification: Notification,
response: bool,
cx: &mut ModelContext<Self>,
cx: &mut ViewContext<Self>,
) {
self.notification_store.update(cx, |store, cx| {
store.respond_to_notification(notification, response, cx);
@@ -613,7 +581,7 @@ impl NotificationPanel {
}
impl Render for NotificationPanel {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.size_full()
.child(
@@ -643,16 +611,15 @@ impl Render for NotificationPanel {
.full_width()
.on_click({
let client = self.client.clone();
move |_, window, cx| {
move |_, cx| {
let client = client.clone();
window
.spawn(cx, move |cx| async move {
client
.authenticate_and_connect(true, &cx)
.log_err()
.await;
})
.detach()
cx.spawn(move |cx| async move {
client
.authenticate_and_connect(true, &cx)
.log_err()
.await;
})
.detach()
}
}),
)
@@ -681,7 +648,7 @@ impl Render for NotificationPanel {
}
}
impl Focusable for NotificationPanel {
impl FocusableView for NotificationPanel {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
@@ -695,7 +662,7 @@ impl Panel for NotificationPanel {
"NotificationPanel"
}
fn position(&self, _: &Window, cx: &AppContext) -> DockPosition {
fn position(&self, cx: &WindowContext) -> DockPosition {
NotificationPanelSettings::get_global(cx).dock
}
@@ -703,12 +670,7 @@ impl Panel for NotificationPanel {
matches!(position, DockPosition::Left | DockPosition::Right)
}
fn set_position(
&mut self,
position: DockPosition,
_: &mut Window,
cx: &mut ModelContext<Self>,
) {
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
settings::update_settings_file::<NotificationPanelSettings>(
self.fs.clone(),
cx,
@@ -716,18 +678,18 @@ impl Panel for NotificationPanel {
);
}
fn size(&self, _: &Window, cx: &AppContext) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
}
fn set_size(&mut self, size: Option<Pixels>, _: &mut Window, cx: &mut ModelContext<Self>) {
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut ModelContext<Self>) {
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
self.active = active;
if self.active {
@@ -740,7 +702,7 @@ impl Panel for NotificationPanel {
}
}
fn icon(&self, _: &Window, cx: &AppContext) -> Option<IconName> {
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
let show_button = NotificationPanelSettings::get_global(cx).button;
if !show_button {
return None;
@@ -753,11 +715,11 @@ impl Panel for NotificationPanel {
Some(IconName::BellDot)
}
fn icon_tooltip(&self, _window: &Window, _cx: &AppContext) -> Option<&'static str> {
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
Some("Notification Panel")
}
fn icon_label(&self, _window: &Window, cx: &AppContext) -> Option<String> {
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
let count = self.notification_store.read(cx).unread_notification_count();
if count == 0 {
None
@@ -779,25 +741,21 @@ pub struct NotificationToast {
notification_id: u64,
actor: Option<Arc<User>>,
text: String,
workspace: WeakModel<Workspace>,
workspace: WeakView<Workspace>,
}
impl NotificationToast {
fn focus_notification_panel(&self, window: &mut Window, cx: &mut ModelContext<Self>) {
fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
let workspace = self.workspace.clone();
let notification_id = self.notification_id;
window.defer(cx, move |window, cx| {
cx.window_context().defer(move |cx| {
workspace
.update(cx, |workspace, cx| {
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(window, cx) {
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
panel.update(cx, |panel, cx| {
let store = panel.notification_store.read(cx);
if let Some(entry) = store.notification_for_id(notification_id) {
panel.did_click_notification(
&entry.clone().notification,
window,
cx,
);
panel.did_click_notification(&entry.clone().notification, cx);
}
});
}
@@ -808,7 +766,7 @@ impl NotificationToast {
}
impl Render for NotificationToast {
fn render(&mut self, _: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let user = self.actor.clone();
h_flex()
@@ -820,10 +778,10 @@ impl Render for NotificationToast {
.child(Label::new(self.text.clone()))
.child(
IconButton::new("close", IconName::Close)
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
)
.on_click(cx.listener(|this, _, window, cx| {
this.focus_notification_panel(window, cx);
.on_click(cx.listener(|this, _, cx| {
this.focus_notification_panel(cx);
cx.emit(DismissEvent);
}))
}

View File

@@ -32,7 +32,7 @@ impl ParentElement for CollabNotification {
}
impl RenderOnce for CollabNotification {
fn render(self, _: &mut Window, cx: &mut AppContext) -> impl IntoElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
h_flex()
.text_ui(cx)
.justify_between()

View File

@@ -17,8 +17,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
while let Some(incoming_call) = incoming_call.next().await {
for window in notification_windows.drain(..) {
window
.update(&mut cx, |_, window, _| {
window.remove_window();
.update(&mut cx, |_, cx| {
cx.remove_window();
})
.log_err();
}
@@ -36,8 +36,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
.log_err()
{
let window = cx
.open_window(options, |_, cx| {
cx.new_model(|_| {
.open_window(options, |cx| {
cx.new_view(|_| {
IncomingCallNotification::new(
incoming_call.clone(),
app_state.clone(),
@@ -111,19 +111,19 @@ impl IncomingCallNotification {
}
impl Render for IncomingCallNotification {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
div().size_full().font(ui_font).child(
CollabNotification::new(
self.state.call.calling_user.avatar_uri.clone(),
Button::new("accept", "Accept").on_click({
let state = self.state.clone();
move |_, _, cx| state.respond(true, cx)
move |_, cx| state.respond(true, cx)
}),
Button::new("decline", "Decline").on_click({
let state = self.state.clone();
move |_, _, cx| state.respond(false, cx)
move |_, cx| state.respond(false, cx)
}),
)
.child(v_flex().overflow_hidden().child(Label::new(format!(

View File

@@ -28,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.displays() {
let options = notification_window_options(screen, window_size, cx);
let Some(window) = cx
.open_window(options, |_, cx| {
cx.new_model(|_| {
.open_window(options, |cx| {
cx.new_view(|_| {
ProjectSharedNotification::new(
owner.clone(),
*project_id,
@@ -55,8 +55,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
if let Some(windows) = notification_windows.remove(project_id) {
for window in windows {
window
.update(cx, |_, window, _| {
window.remove_window();
.update(cx, |_, cx| {
cx.remove_window();
})
.ok();
}
@@ -67,8 +67,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for (_, windows) in notification_windows.drain() {
for window in windows {
window
.update(cx, |_, window, _| {
window.remove_window();
.update(cx, |_, cx| {
cx.remove_window();
})
.ok();
}
@@ -101,14 +101,14 @@ impl ProjectSharedNotification {
}
}
fn join(&mut self, cx: &mut ModelContext<Self>) {
fn join(&mut self, cx: &mut ViewContext<Self>) {
if let Some(app_state) = self.app_state.upgrade() {
workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
.detach_and_log_err(cx);
}
}
fn dismiss(&mut self, cx: &mut ModelContext<Self>) {
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_room) =
ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
{
@@ -122,20 +122,18 @@ impl ProjectSharedNotification {
}
impl Render for ProjectSharedNotification {
fn render(&mut self, window: &mut Window, cx: &mut ModelContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(window, cx);
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
div().size_full().font(ui_font).child(
CollabNotification::new(
self.owner.avatar_uri.clone(),
Button::new("open", "Open").on_click(cx.listener(move |this, _event, _, cx| {
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
this.join(cx);
})),
Button::new("dismiss", "Dismiss").on_click(cx.listener(
move |this, _event, _, cx| {
this.dismiss(cx);
},
)),
Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
this.dismiss(cx);
})),
)
.child(Label::new(self.owner.github_login.clone()))
.child(Label::new(format!(

View File

@@ -7,7 +7,7 @@ use crate::notifications::collab_notification::CollabNotification;
pub struct CollabNotificationStory;
impl Render for CollabNotificationStory {
fn render(&mut self, _window: &mut Window, _cx: &mut ModelContext<Self>) -> impl IntoElement {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let window_container = |width, height| div().w(px(width)).h(px(height));
Story::container()

View File

@@ -16,4 +16,5 @@ doctest = false
test-support = []
[dependencies]
rustc-hash = "1.1"
indexmap.workspace = true
rustc-hash.workspace = true

View File

@@ -4,12 +4,24 @@ pub type HashMap<K, V> = FxHashMap<K, V>;
#[cfg(feature = "test-support")]
pub type HashSet<T> = FxHashSet<T>;
#[cfg(feature = "test-support")]
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, rustc_hash::FxBuildHasher>;
#[cfg(feature = "test-support")]
pub type IndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>;
#[cfg(not(feature = "test-support"))]
pub type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "test-support"))]
pub type HashSet<T> = std::collections::HashSet<T>;
#[cfg(not(feature = "test-support"))]
pub type IndexMap<K, V> = indexmap::IndexMap<K, V>;
#[cfg(not(feature = "test-support"))]
pub type IndexSet<T> = indexmap::IndexSet<T>;
pub use rustc_hash::FxHasher;
pub use rustc_hash::{FxHashMap, FxHashSet};
pub use std::collections::*;

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