Compare commits

..

1447 Commits

Author SHA1 Message Date
Antonio Scandurra
676f6f490e WIP 2023-12-18 19:58:27 +01:00
Antonio Scandurra
be3c0ff419 WIP 2023-12-18 18:43:22 +01:00
Antonio Scandurra
2330256741 WIP 2023-12-18 14:55:59 +01:00
Antonio Scandurra
4e544545d1 Introduce an Arena to allocate and deallocate frame-related structs more cheaply (#3689)
Picture worth a thousand words:


![before-after](https://github.com/zed-industries/zed/assets/482957/0aa92baf-f1cd-485e-a234-6d8f8b63a79a)

Note how having an area makes a substantial amount of frames between
0.5ms and 1ms faster (15-20% faster frames).

Release Notes:

- N/A
2023-12-18 11:16:07 +01:00
Antonio Scandurra
5a4e2e6b90 Use a closure to allocate structs onto the Arena
This is a trick borrowed from Bumpalo that helps LLVM understand
it should instantiate the object directly on the heap, as opposed to
doing so on the stack and then moving it.
2023-12-18 10:56:37 +01:00
Max Brunsfeld
29c6061a14 Fix following bugs (#3688)
* Follow command didn't work, because follow task was dropped
* An extra div prevented titlebar facepiles from rendering correctly
2023-12-15 16:34:08 -08:00
Max Brunsfeld
b5ae2f060b Remove div around all avatars in titlebar facepile 2023-12-15 16:26:56 -08:00
Max Brunsfeld
a4cdf365be Fix accidental drop of following task. Add back FollowNextCollaborator 2023-12-15 16:15:31 -08:00
Marshall Bowers
54eb452144 Only apply scroll correction in uniform_list when already scrolled (#3687)
This PR fixes an issue where the scroll correction added to the uniform
list in a35fdf45fc was being applied even
if the list wasn't scrolled at all.

This manifested in the project panel starting with an incorrect scroll
offset that would disappear once scrolled.

It seems like we should only need to apply this scroll correction when
the list is already scrolled.

Release Notes:

- N/A
2023-12-15 17:53:10 -05:00
Marshall Bowers
5447c6d5ee Fix channel filter input (#3686)
This PR fixes the channel filter in the collab panel not showing up.

Release Notes:

- N/A
2023-12-15 17:03:59 -05:00
Max Brunsfeld
c8e918690f Don't scroll List on scroll wheel events outside of its bounds (#3685) 2023-12-15 13:47:40 -08:00
Max Brunsfeld
149a718d65 Don't scroll List on scroll wheel events outside of its bounds 2023-12-15 13:38:49 -08:00
Marshall Bowers
ee77728663 Add --release flag to script/zed-local (#3684)
This PR adds support for the `--release` flag to `script/zed-local`.

This allows you to run a local build of Zed in release mode, which can
be useful when needing to profile things or do other performance work.

Release Notes:

- N/A
2023-12-15 16:38:40 -05:00
Max Brunsfeld
8efc38d43a Pane rendering fixes (#3683)
* Fix a bug where a pane's leader info was not rendered if the pane was
part of a split
* Fix a crash when zooming a pane, due to duplicate render of that view.
2023-12-15 13:34:34 -08:00
Marshall Bowers
170e332417 Add support for --release flag to zed-local script 2023-12-15 16:28:23 -05:00
Marshall Bowers
0ea59d6466 Format zed-local script 2023-12-15 16:27:58 -05:00
Max Brunsfeld
a5e037befa Avoid duplicate render of a pane when it is zoomed 2023-12-15 13:26:48 -08:00
Max Brunsfeld
e3e988c150 Always render panes via pane_group::Member::render
Remove distinct code path for rendering panes in a PaneAxis, as this dropped
the logic for rendering the leader border and info box.
2023-12-15 13:26:36 -08:00
Max Brunsfeld
4eb609a954 Enable zoom (#3668)
* [x] zoom
* [x] pane tests
2023-12-15 12:58:03 -08:00
Nate Butler
7af0edf5b6 Editor UI Cleanup (#3676)
[[PR Description]]

TODO: 
- [x] Style fold marker
- [x] Editor - Code action icon too large, should use muted color,
should have tooltip
- [ ] Style inline assistant
    - Prompt text gets cut off veritcally
- [x] Line height on inline assist editor isn't tall enough
- Nate: This is my fault due to the line height (relative: 1) we are
setting on non-buffer editors. I'll look into this.
- [ ] Style diagnostic hover (right now it's an opaque box on some
themes)
    - Nate: This needs status tints, we are working on it
- [ ] Hovering over a symbol shows a dialogue that appears underneath
the scroll-bar

Release Notes:

- N/A
2023-12-15 15:49:55 -05:00
Nate Butler
e71f2b49e8 Style fold control 2023-12-15 15:41:53 -05:00
Conrad Irwin
b8cdcf4e93 fix theme selector gaps (#3682)
- Don't scroll beyond end of uniform list
- Restore position of uniform_list padding

[[PR Description]]

Release Notes:

- N/A
2023-12-15 13:27:17 -07:00
Nathan Sobo
3781626379 Merge branch 'main' into arena 2023-12-15 13:22:19 -07:00
Conrad Irwin
3c3e5ed7d8 Fix bug where vim commands were copied on each update_matches (#3680)
In zed1, the array of commands was recalculated on every update_matches,
In zed2 it's cached before we change the focus.

Release Notes:

- N/A
2023-12-15 13:20:10 -07:00
Conrad Irwin
2fe9a1df21 Restore position of uniform_list padding
It should be inside the scrollable area.
2023-12-15 13:18:51 -07:00
Max Brunsfeld
4c3ec0a8e5 Enable pane tests 2023-12-15 12:00:58 -08:00
Conrad Irwin
a35fdf45fc Don't scroll beyond end of uniform list 2023-12-15 12:54:12 -07:00
Max Brunsfeld
9a8225c855 When a dock is zoomed, don't render it in its normal place 2023-12-15 11:48:25 -08:00
Max Brunsfeld
a4c123bb11 Fix logic for activating panel when re-docking it 2023-12-15 11:47:01 -08:00
Marshall Bowers
b1a61ca21e Fix toolbar width (#3681)
This PR fixes an issue with the toolbar width introduced in #3666.

The lack of a flex container was making the toolbar contents not take up
the full width, and thus not positions items correctly along its main
axis.

Release Notes:

- N/A
2023-12-15 14:44:29 -05:00
Max Brunsfeld
793381f455 Render zoomed item in the workspace 2023-12-15 11:19:59 -08:00
Marshall Bowers
487edcb12b Rework collab panel rendering to use gpui::list (#3678)
This PR reworks the rendering of the collab panel to use `gpui::list`,
so that we don't render any items that are not visible on the screen.

In the process we also fixed some bugs in the channel list:

- Fixed the context menu for channels not deploying when activated via
keyboard
- Fixed drag and drop for channels
- Made it so when navigating the collab panel via keyboard the list only
scrolls enough to reveal the next item when navigating to an item that
is currently off-screen

Release Notes:

- N/A
2023-12-15 14:11:24 -05:00
Mikayla Maki
dc7f4f4786 Fix editor mouse event dispatch stealing clicks (#3679)
[[PR Description]]

Release Notes:

-
2023-12-15 11:06:31 -08:00
Marshall Bowers
5c060ceb1c Format code 2023-12-15 14:03:49 -05:00
Marshall Bowers
5417127902 Fix channel drag and drop
Also add the ability to unnest a channel by dragging it to the "Channels"
header. This is currently not working due to a collab server issue.

Co-authored-by: Max <max@zed.dev>
2023-12-15 14:01:24 -05:00
Conrad Irwin
d0dbf8e1e2 Fix bug where vim commands were copied on each update_matches
In zed1, the array of commands was recalculated on every update_matches,
In zed2 it's cached before we change the focus.
2023-12-15 11:59:06 -07:00
Marshall Bowers
a40f04b71f Only scroll enough to reveal the next channel if it isn't visible
Co-authored-by: Max <max@zed.dev>
2023-12-15 13:50:51 -05:00
Mikayla
b4135dd2f1 Fix editor mouse event dispatch 2023-12-15 10:26:41 -08:00
Marshall Bowers
47fc03ab98 Fix positioning of the inline context menu
Added a new `bounds_for_item` for `ListState`.

Co-authored-by: Max <max@zed.dev>
2023-12-15 13:24:30 -05:00
Mikayla Maki
a65688abac Fix center pane resizing (#3677)
Release Notes:

- N/A
2023-12-15 10:15:58 -08:00
Mikayla
a2852e36ce Fix pane resizing 2023-12-15 10:07:10 -08:00
Antonio Scandurra
0a57171066 Use a safe API for Arena 2023-12-15 19:03:37 +01:00
Nate Butler
4cb0f60779 Update size of code action indicator and show when active 2023-12-15 12:29:33 -05:00
Nate Butler
83525bf142 Revert change to single line editor height 2023-12-15 12:22:33 -05:00
Marshall Bowers
b478a4c4d5 Perform scroll interactions through the ListState 2023-12-15 12:10:40 -05:00
Nate Butler
d13c1486da Update fold_indicator render 2023-12-15 12:09:06 -05:00
Marshall Bowers
6c10ff8548 Render the collab panel using a gpui::list 2023-12-15 11:56:03 -05:00
Nate Butler
3993b0acde Titlebar cleanup (#3675)
[[PR Description]]

Clean up titlebar design nits

- Remove red borders
- Update menu button styles
- Adjust padding
- Improve tooltips with keybindings

Release Notes:

- N/A
2023-12-15 11:45:52 -05:00
Nate Butler
6345e6d4d2 Add some right side padding in titlebar. 2023-12-15 11:25:24 -05:00
Nate Butler
d099d35948 Reduce intensity of project panel icons 2023-12-15 11:20:56 -05:00
Nate Butler
47eaf1abd8 Remove red borders, improve left side padding 2023-12-15 11:07:40 -05:00
Nate Butler
50a44dd8ba Improve tooltip with keybinding styling 2023-12-15 11:07:17 -05:00
Nate Butler
2b4f779918 Story/Storybook Enhancements (#3674)
[[PR Description]]

- Adds some Story components to reduce the amount of code needed to lay
out stories
- Added the ability to open a story in Zed using a link (see text story)
- Added sections with descriptions and usage.

Release Notes:

- N/A
2023-12-15 10:48:13 -05:00
Nate Butler
f459fc5e27 Fix import 2023-12-15 10:33:27 -05:00
Nate Butler
62ea58618c Merge branch 'main' into story-enhancements 2023-12-15 10:26:59 -05:00
Nate Butler
3d9e051b07 Update storybook2.rs 2023-12-15 10:25:07 -05:00
Antonio Scandurra
be73dd852d Move Arena to a thread-local and use it to allocate AnyElement 2023-12-15 16:18:05 +01:00
Antonio Scandurra
e1ca8e81bb Use an Arena to reuse allocations for listeners 2023-12-15 15:13:32 +01:00
Piotr Osiewicz
ff3f4f3027 search: Reintroduce whole word switch (#3672)
It seems to have been lost in the recent styling pass.

Release Notes:

- N/A
2023-12-15 12:20:54 +01:00
Piotr Osiewicz
2b3d9deabe Dismiss Recent Projects & VCS modals on ESC (#3671)
Release Notes:

- N/A
2023-12-15 12:07:25 +01:00
Kirill Bulatov
83b55de73e zed2: Restore buffer search deploy & focus behavior (#3670)
In zed1, cmd-f either opened the search buffer, or focused into it if it
was open already. Search query got updated, if any selection was made on
the moment of cmd-f call.

The PR restores that behavior and also

* fixes a bug when opened diagnostics error always got pasted
* comments out the panic on multibuffer separator click 
* removes extra stdout & stderr debug logging

Release Notes:

- N/A
2023-12-15 12:33:45 +02:00
Kirill Bulatov
4bfe46f53a Restore zed1 behavior for buffer search deploy 2023-12-15 12:23:03 +02:00
Kirill Bulatov
31ff7d40ed Adjust copy/paste buffer only on the copy error action trigger 2023-12-15 11:34:00 +02:00
Kirill Bulatov
a6403aad1a Remove extra nits, do not panic on clicking the buffer separator 2023-12-15 11:28:48 +02:00
Julia
62d655183b Fire focus handlers on draw to avoid timing with newly created item (#3640)
See title

Any focus changes which fire from within a focus handler should be
handled in the next frame

Release Notes:

- N/A
2023-12-14 23:57:24 -05:00
Julia
de523c2d80 Give correct focus handle when project search has no matches 2023-12-14 23:49:44 -05:00
Julia
4be84f3db0 Merge branch 'main' into focus-handlers-on-draw 2023-12-14 23:08:23 -05:00
Conrad Irwin
70a14bb22f Implement user menu (#3636)
Release Notes:

- N/A
2023-12-14 20:30:28 -07:00
Julia
d4e09230cc Give result-less project search a focus handle 2023-12-14 22:29:33 -05:00
Conrad Irwin
7e1d61d116 Merge branch 'main' into user-menu 2023-12-14 20:18:20 -07:00
Mikayla Maki
46bd6088e8 Fix some todos, add others (#3669)
As it says on the tin

Release Notes:

-
2023-12-14 19:00:58 -08:00
Julia
ff4da878f9 Render panel in these tests 2023-12-14 21:33:11 -05:00
Julia
fbcaf96ab5 Track focus on TestItem 2023-12-14 20:58:59 -05:00
Mikayla
6973b1b592 Add several red outlines 2023-12-14 17:48:49 -08:00
Mikayla
ead5206741 Fix todos in auto-update 2023-12-14 17:00:07 -08:00
Max Brunsfeld
b52db696d3 Fix overlay rendering when dragging onto the center of a pane (#3667) 2023-12-14 16:59:19 -08:00
Max Brunsfeld
52b9fc303b Fix overlay rendering when dragging onto the center of a pane 2023-12-14 16:52:03 -08:00
Mikayla Maki
c27dd57a48 Make more performance improvements to GPUI 2 (#3664)
In the 3 charts below, "window draw" has 3 major subroutines. Request
layout, where we walk over the tree and have everything talk to the
layout engine initially. Compute layout, where we have the layout engine
actually do the layout, and then paint, where we use the computed bounds
to populate the scene.


![image_720](https://github.com/zed-industries/zed/assets/1789/d2225389-865f-4c8a-9452-9f611da64dcf)

Things are moving quickly so before/after comparisons are tough. In the
graph above, green bars are from a commit actually pre-dates a merge of
master which increased the complexity of layout. The red bars represent
the state of the world after this PR. Note how we improve the
performance of `paint`.

Improvements:

- Not moving `self` in `Element::paint`. This was moving from the heap
to the stack and imposing a big cost. This is the biggest win in this
PR.
- We got some minor wins by making the stacking order a bigger smallvec
of u8 instead of u32.
- A big win that doesn't show up in this chart is avoiding a double
render of the editor when autoscrolling by never pushing notification
effects or marking the window dirty when notifying during a window draw.

Release Notes:

- N/A
2023-12-14 16:45:58 -08:00
Max Brunsfeld
b95cc7221c Fix centering of empty pane text (#3666)
The formatting of the empty pane text regressed in #3665, in which I
also made it possible to drag items onto an empty pane.
2023-12-14 16:44:30 -08:00
Mikayla Maki
05cb68eff1 Merge branch 'main' into perf-2 2023-12-14 16:39:10 -08:00
Max Brunsfeld
842f15c65b Fix centering of empty pane text 2023-12-14 16:35:58 -08:00
Max Brunsfeld
82c8b49dac Fix stickiness of main pane drag target (#3665)
Reimplement the pane drag targets using on_drag_move
2023-12-14 16:30:02 -08:00
Mikayla Maki
8418f25d0a Merge branch 'main' into perf-2 2023-12-14 16:25:40 -08:00
Max Brunsfeld
d6383ab0c6 Fix stickiness of main pane drag target
Reimplement the pane drag targets using on_drag_move
2023-12-14 16:23:15 -08:00
Mikayla Maki
0b4b81fa0c Fix dock resizing (#3663)
This fixes some bugs in the display and tracking of dock resizes

Release Notes:

-
2023-12-14 16:20:29 -08:00
Nathan Sobo
ad8165ae79 Rename draw2 -> draw_and_update_state 2023-12-14 17:20:27 -07:00
Nathan Sobo
f4a954db4f Merge remote-tracking branch 'origin/main' into perf-2 2023-12-14 17:15:44 -07:00
Mikayla
8b4cf38379 Fix dock resize handles
co-authored-by: conrad <conrad@zed.dev>
2023-12-14 16:05:10 -08:00
Nathan Sobo
02606d1fb9 Merge remote-tracking branch 'origin/main' into perf-2 2023-12-14 16:56:36 -07:00
Marshall Bowers
bc3e6649f8 Fix warning in release mode (#3662)
This PR fixes a warning that was present in release mode, which was
preventing the nightly builds from running:

```
error: variable does not need to be mutable
   --> crates/gpui2/src/elements/div.rs:547:9
    |
547 |     let mut div = Div {
    |         ----^^^
    |         |
    |         help: remove this `mut`
    |
    = note: `-D unused-mut` implied by `-D warnings`
```

Release Notes:

- N/A
2023-12-14 18:52:14 -05:00
Mikayla
4c63c74f92 Fix bug in drag move dispatch
co-authored-by: conrad <conrad@zed.dev>
2023-12-14 15:23:24 -08:00
Nate Butler
e4f9bddbab Remove unused imports 2023-12-14 17:56:42 -05:00
Marshall Bowers
139fe7c1f1 Use separate editor foreground color (#3661)
This PR populates the `editor_foreground` color in the various themes
and updates the editor to use this as the color for text.

The `text` field in the theme should now be used for UI elements, while
`editor_foreground` should be used for buffers.

This improves the contrast in some themes, notably Ayu Dark.

Release Notes:

- N/A
2023-12-14 17:54:44 -05:00
Nate Butler
936c78be94 WIP IconButton story 2023-12-14 17:51:08 -05:00
Max Brunsfeld
fcbd58fed4 Enable all warnings in workspace, fix all warnings (#3660) 2023-12-14 14:38:28 -08:00
Nate Butler
3cf003763e Use updated story container in Text story 2023-12-14 17:38:22 -05:00
Marshall Bowers
2b278e69f7 Use editor_foreground color in editor 2023-12-14 17:34:07 -05:00
Marshall Bowers
292fac37bb Use editor.foreground from VS Code for the editor foreground 2023-12-14 17:30:37 -05:00
Marshall Bowers
8bac4e199d Emit editor_foreground color from theme converter 2023-12-14 17:28:45 -05:00
Max Brunsfeld
6170895932 Fix bug in Workspace::activate_pane_in_direction
Co-authored-by: Conrad <conrad@zed.dev>
2023-12-14 14:27:44 -08:00
Joseph T. Lyons
4e7005b4f7 Use bitflags to hold error states 2023-12-14 17:22:13 -05:00
Max Brunsfeld
b5e9e277db Re-enable cmd-<number> key bindings for activating panes (#3659) 2023-12-14 14:16:43 -08:00
Max Brunsfeld
23d5f3f3e8 Enable all warnings in workspace, fix all warnings
Bring back some workspace tests

Co-authored-by: Conrad <conrad@zed.dev>
2023-12-14 14:15:56 -08:00
Nathan Sobo
d13a21c238 Don't move in paint 2023-12-14 15:15:18 -07:00
Julia
bbbdb9ff3e Render panel in this test to make sure it can receive focus 2023-12-14 17:11:00 -05:00
Nate Butler
63c3edfb83 Continue styling new story components 2023-12-14 16:52:05 -05:00
Max Brunsfeld
8d994ce8c5 Enable dragging from project panel to panes (#3658)
Rework gpui2 drag API so that receivers need not specify the dragged
view type.
2023-12-14 13:32:40 -08:00
Max Brunsfeld
6b06bb4ffe Re-enable cmd-<number> key bindings for activating panes
co-authored-by: Conrad <conrad@zed.dev>
2023-12-14 13:30:11 -08:00
Mikayla
8791f7cefc Enable dragging from project panel to panes
Rework gpui2 drag API so that receivers need not specify the dragged view type.

co-authored-by: Max <max@zed.dev>
co-authored-by: Conrad <conrad@zed.dev>
2023-12-14 13:20:48 -08:00
Nathan Sobo
fb3382bcc5 Merge remote-tracking branch 'origin/main' into perf-2 2023-12-14 14:16:59 -07:00
Marshall Bowers
35838ae554 Style "Replace in project" input in project search (#3657)
This PR styles the "Replace in project" input in the project search.

Release Notes:

- N/A
2023-12-14 16:10:22 -05:00
Nathan Sobo
0dd6c50a20 Use FxHashMap for element state 2023-12-14 14:06:19 -07:00
Marshall Bowers
c166311c06 Style "Replace in project" input 2023-12-14 15:58:17 -05:00
Marshall Bowers
2484a6969a Fix toolbar flex sizing 2023-12-14 15:57:06 -05:00
Nate Butler
c041799c6a Extend Story components, allow linking to story file
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-14 15:55:17 -05:00
Conrad Irwin
2efd7cedbc Ignore invisible layers for mouse events (#3651)
Fixes clicking in the top/bottom 100px of the editor


Release Notes:

- N/A
2023-12-14 12:01:45 -07:00
Marshall Bowers
ed098c834e Style inputs in project search (#3655)
This PR styles the inputs in the project search.

<img width="772" alt="Screenshot 2023-12-14 at 1 53 28 PM"
src="https://github.com/zed-industries/zed/assets/1486634/fe2362be-79db-4551-a473-2acf7a8a1bcb">

Release Notes:

- N/A
2023-12-14 13:58:11 -05:00
Nathan Sobo
3d1dae9a06 Make z_indices bigger in StackingOrder 2023-12-14 11:37:48 -07:00
Nathan Sobo
0d30b698a4 Don't allocate interactive bounds 2023-12-14 11:28:52 -07:00
Joseph T. Lyons
8a361c93e2 Prep feedback code for testing 2023-12-14 13:22:41 -05:00
Marshall Bowers
c6e44683e6 Hide the toolbar if it has no visible items (#3654)
This PR makes the toolbar hide itself if it has no visible items.

This removes the double border beneath the tab bar when there are no
visible tools in the toolbar.

Release Notes:

- N/A
2023-12-14 13:02:27 -05:00
Conrad Irwin
4e1b4c4390 Refactor editor to be more clear about stacking 2023-12-14 10:48:15 -07:00
Conrad Irwin
d8cb0e8a2a Fix z-indexes in editor element
* Ensure that scroll events from blocks scroll the editor
* Ensure that scroll bars show behind hover things
2023-12-14 10:36:16 -07:00
Nathan Sobo
1ae25f52a1 WIP 2023-12-14 10:31:45 -07:00
Marshall Bowers
fd133df896 Improve storybook story selection (#3653)
This PR builds on top of #3652 by adding a selection prompt to the
storybook to allow you to choose from the available list of stories if
you don't provide one explicitly:

<img width="1387" alt="Screenshot 2023-12-14 at 12 00 26 PM"
src="https://github.com/zed-industries/zed/assets/1486634/640d62a3-1340-45f1-9746-69b513faff62">

This way we don't have to keep generating the `script/storybook` script
whenever stories are added/removed.

#### Usage (through `cargo`):

```sh
# Select from the available stories
cargo run -p storybook2

# Run a specific story
cargo run -p storybook2 -- components/list_item
```

#### Usage (through `script/storybook`):

```sh
# Select from the available stories
./script/storybook

# Run a specific story
./script/storybook list_item
```

Release Notes:

- N/A
2023-12-14 12:13:02 -05:00
Conrad Irwin
c2c7eead8a Robustify checks for visibility 2023-12-14 10:02:54 -07:00
Nate Butler
4ace342cf0 Fix typo 2023-12-14 11:59:24 -05:00
Nate Butler
08418618ab Generate script/storybook (#3652)
[[PR Description]]

This PR adds the ability to run stories with `script/storybook`.

Running it directly will give you a selector like this:

```zsh
➜  zed git:(add-storybook-script) script/storybook
1) auto_height_editor	 9) icon		17) scroll
2) avatar		10) icon_button		18) tab
3) button		11) keybinding		19) tab_bar
4) checkbox		12) label		20) text
5) context_menu		13) list		21) viewport_units
6) cursor		14) list_header		22) z_index
7) disclosure		15) list_item		23) picker
8) focus		16) overflow_scroll
```

You can also provide a value like:

`script/storybook {STORY_NAME}` - Example: `script/storybook text`

OR 

`script/storybook components/{STORY_NAME}` - Example: `script/storybook
components/text`

I just wanted an easier way to interface with stories quickly to make
using them a bit easier, and enable discovery of what exists easier with
the selector.

This was a really quick hack, in the future we can extend this to a
proper CLI.

Release Notes:

- N/A
2023-12-14 11:52:06 -05:00
Conrad Irwin
17a80ca09b Ignore invisible layers for mouse events 2023-12-14 09:44:04 -07:00
Nate Butler
ac3622cc6a Generate script/storybook 2023-12-14 11:42:08 -05:00
Nathan Sobo
6f17cf7337 WIP 2023-12-14 09:25:14 -07:00
Piotr Osiewicz
da960fffe7 calls: Fix off by one in prompts for leaving a call. (#3649)
We've noticed how leaving a call with multiple windows open would still
prompt with the popup along the lines of "Do you want to leave the
current call?". In Zed1, that popup only showed up when you've had just
one window open.

The code for prompting did not change at all between zed1 and zed2, but
the way we calculate the window count did. Calling AppContext::windows
to get all window handles from WindowContext::update essentially
excluded the window we were updating (that is the window being closed)
from the returned Vec of window handles. I've decided to keep the code
as close to original as possible (as the alternative would be to change
the \# of workspaces needed for a prompt to pop up). We now query the
window handles via a deref to AsyncAppContext, which does not exclude
"our" window handle from the returned results.

Release Notes:

- N/A
2023-12-14 15:35:31 +01:00
Piotr Osiewicz
9bce30687e Vcs menu2 (#3648)
Header and footer are gonna be added in a separate PR as they require
changes to Picker trait that I feel are separate from the contents of
this PR.

Release Notes:

- N/A
2023-12-14 12:36:53 +01:00
Kirill Bulatov
cfe7336519 Return include warnings toggle to the diagnostics panel 2023-12-14 10:00:31 +02:00
Marshall Bowers
3ad967778a Change project search bar flex axis to column (#3647)
This PR changes project search bar's flex axis to `column`.

This fixes an issue where toggling the filters would cause the search
controls to center themselves vertically instead of staying next to the
search input.

Release Notes:

- N/A
2023-12-13 23:02:49 -05:00
Julia
cd08d349a5 Quick and dirty attempt to immediately apply focus change in tests
Doesn't quite work yet
2023-12-13 21:48:05 -05:00
Joseph T. Lyons
837b4c3462 Avoid unnecessary match 2023-12-13 21:43:21 -05:00
Mikayla Maki
4f196f66fc Fix bugs in resizing the docks + Add debug styling (#3645)
This PR:
- Fixes several bugs with dock resizing
- Adds a new drag move API for resizes 
- Adds special debug styling for elements, including auto-opening Zed at
the element's location in source
- Changes the `cx.paint_quad()` API to take a type and adds several
helpers to create this type.
- Adds `()` as the empty element, and uses it to implement a derive
macro for the `Render` trait.

Release Notes:

- N/A
2023-12-13 18:40:28 -08:00
Joseph T. Lyons
d9adfdeac2 Highlight email address field as red when invalid 2023-12-13 21:39:31 -05:00
Joseph T. Lyons
435256ab0f Remove completed TODOs 2023-12-13 21:35:30 -05:00
Joseph T. Lyons
0db62f0ceb Feedback modal improvements (#3646)
- Cleans up modal state logic by using an enum over multiple booleans
- Simulates sending feedback in dev mode, so UI can be easily tested

Release Notes:

- N/A
2023-12-13 21:33:45 -05:00
Mikayla
53b1948c44 Remove inconsistency with how GPUI used to handle these events 2023-12-13 18:32:42 -08:00
Mikayla
d88fc27b79 Merge branch 'main' into fix-splits-bugs 2023-12-13 18:29:48 -08:00
Joseph T. Lyons
900a2bfb87 Use an enum to represent modal states and simplify logic 2023-12-13 21:20:09 -05:00
Joseph T. Lyons
be2ad2ff86 Add Duration dependency 2023-12-13 21:19:36 -05:00
Mikayla
0dd84924e4 add cmd-click-to-jump-to-element 2023-12-13 18:19:25 -08:00
Joseph T. Lyons
052e05fc74 Use existing convention to silence warnings 2023-12-13 21:19:12 -05:00
Joseph T. Lyons
7ba7b925ff Simulate sending feedback in dev 2023-12-13 21:18:50 -05:00
Marshall Bowers
ceede28fab Ensure the outer ListItem element has a unique ID (#3644)
This PR fixes an issue where the outer `ListItem` element was using a
static ID instead of the one provided to the component.

Now that active states are fixed, this meant that any time there were
sibling list items they would share active states if one of them was
clicked.

Release Notes:

- N/A
2023-12-13 21:14:21 -05:00
Marshall Bowers
474f09ca3f Remove unneeded left-click filtering in ListItem (#3643)
This PR removes the left-click filtering from the `on_click` handler for
`ListItem`s.

It's no longer needed after #3584.

Release Notes:

- N/A
2023-12-13 21:03:50 -05:00
Nathan Sobo
c863227dc2 Log frame timings 2023-12-13 18:44:21 -07:00
Marshall Bowers
057b235c56 Implement VisibleOnHover for IconButton (#3642)
This PR implements the `VisibleOnHover` trait for `IconButton`s.

I noticed that in a lot of places we were wrapping an `IconButton` in an
extra `div` just so we could call `visible_on_hover` on it. By
implementing the trait on `IconButton` directly it allows us to avoid
the interstitial `div` entirely.

Release Notes:

- N/A
2023-12-13 20:42:27 -05:00
Max Brunsfeld
15f62a49f7 Allowing dragging tabs onto panes and pane edges (#3641) 2023-12-13 17:23:43 -08:00
Max Brunsfeld
4f32f66271 Clone item when dragging to split 2023-12-13 17:16:00 -08:00
Max Brunsfeld
9059d70153 Ensure only top layer is styled with drag over style 2023-12-13 17:07:23 -08:00
Max Brunsfeld
93029376d9 Start work on allowing dragging tabs onto panes and pane edges 2023-12-13 17:06:42 -08:00
Mikayla
a4024b297e Add interactive debug info 2023-12-13 17:03:56 -08:00
Mikayla
04389939d3 Add debug and debug below methods for observing the bounds of divs 2023-12-13 16:47:55 -08:00
Mikayla
416bb45531 Tidy up workspace resizing 2023-12-13 16:35:25 -08:00
Marshall Bowers
137e4e9251 Add .visible_on_hover helper method (#3639)
This PR adds a `.visible_on_hover` helper method that can be used to
make an element only visible on hover.

I noticed we were repeating this similar stanza in a bunch of different
spots:

```rs
some_element
    .invisible()
    .group_hover("", |style| style.visible())
``` 

so it seemed like a nice thing to factor out into a reusable utility.

Release Notes:

- N/A
2023-12-13 19:12:20 -05:00
Marshall Bowers
d59de96921 Style collab panel (#3638)
This PR styles the collab panel.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-13 18:20:04 -05:00
Mikayla
e2bfd46455 Fix dock split resizin 2023-12-13 15:14:51 -08:00
Nathan Sobo
1e4a7e6ef1 Don't notify when drawing 2023-12-13 16:05:34 -07:00
Conrad Irwin
7899833367 Don't hang the app when signing in offline 2023-12-13 15:52:43 -07:00
Joseph T. Lyons
1ad1cc1148 Fix variable name 2023-12-13 17:31:51 -05:00
Marshall Bowers
aa55e55c7a Add config files for running Postgres inside Docker Compose (#3637)
This PR adds config files for running the Postgres instance for local
Zed development in a Docker Compose instance.

For those of us who don't like to have a Postgres install always present
on the host system 😄

Usage:

```
docker compose up -d
```

Release Notes:

- N/A
2023-12-13 17:25:07 -05:00
Joseph T. Lyons
985d4c7429 Remove TODO
Thanks @ConradIrwin
2023-12-13 17:09:26 -05:00
Mikayla
a807e798ec Add new drag API 2023-12-13 13:40:19 -08:00
Max Brunsfeld
26a31b41b9 frame time 2023-12-13 14:22:34 -07:00
Mikayla
bfbbec0b01 Add fluent quad API 2023-12-13 13:21:48 -08:00
Conrad Irwin
3094cb749e Implement user menu 2023-12-13 14:14:03 -07:00
Conrad Irwin
426d298173 Port project_symbols (#3634)
Release Notes:

- N/A
2023-12-13 14:13:25 -07:00
Antonio Scandurra
943acc5819 Optimize inserting lots of primitives with the same StackingOrder (#3631)
Release Notes:

- N/A
2023-12-13 22:12:31 +01:00
Marshall Bowers
ee509e043d Rework ListItem and ListHeader to use slot-based APIs (#3635)
This PR reworks the `ListItem` and `ListHeader` components to use
slot-based APIs, making them less opinionated about their contents.

Splitting this out of the collab UI styling PR so we can land it to
avoid conflicts.

Co-authored-by: Nate <nate@zed.dev>

Release Notes:

- N/A
2023-12-13 16:08:31 -05:00
Antonio Scandurra
06b9055e27 Clear last_order when building Scene 2023-12-13 22:02:30 +01:00
Piotr Osiewicz
72eef116c9 fixup! collab_ui: Wire up project picker 2023-12-13 12:53:41 -08:00
Piotr Osiewicz
a91a42763f collab_ui: Wire up project picker
Co-authored-by: Conrad <conrad@zed.dev>
2023-12-13 12:53:41 -08:00
Joseph T. Lyons
48faa171b5 v0.118.x dev 2023-12-13 12:53:41 -08:00
Marshall Bowers
ab8d0abbc1 Wire up tooltips on tab bar actions (#3629)
This PR wires up the tooltips on the actions in the tab bar.

Release Notes:

- N/A
2023-12-13 12:53:41 -08:00
Marshall Bowers
a874a96e76 Fix tab bar drop target sizing (#3627)
This PR fixes an issue where the tab bar drop target was not receiving
any size.

The styling isn't 100% correct yet, as the updated background color has
a gap around it.

Release Notes:

- N/A
2023-12-13 12:53:41 -08:00
Nate Butler
70c6660ae4 Add note 2023-12-13 12:53:41 -08:00
Nate Butler
f9e7c79672 Add deploy note 2023-12-13 12:53:41 -08:00
Nate Butler
5b3b15e95c Futher outline 2023-12-13 12:53:41 -08:00
Nate Butler
85a1a8f777 WIP 2023-12-13 12:53:41 -08:00
Piotr Osiewicz
ce1489f5dc Add inclusion of ignored files 2023-12-13 12:53:41 -08:00
Piotr Osiewicz
0e19da3107 Bring back semantic search UI 2023-12-13 12:53:41 -08:00
Conrad Irwin
9ff73d3a0a Port project_symbols 2023-12-13 13:43:39 -07:00
Marshall Bowers
5c8257585a Fix styling of project search tabs (#3633)
This PR fixes the styling of the project search tabs.

We now have spacing between the icon and the tab label, as well as use
the correct color for the icon based on whether the tab is active or
not.

Release Notes:

- N/A
2023-12-13 14:50:15 -05:00
Marshall Bowers
9a7de98242 Don't show empty documentation labels in completions menu (#3632)
This PR fixes an issue where we would sometimes have extra blank lines
in the completions menu.

This was due to some items including documentation labels that were
empty strings.

Release Notes:

- N/A
2023-12-13 14:15:03 -05:00
Julia
fd428dfa66 Bump livekit client (#3625)
Release Notes:

- Bump LiveKit client version.
2023-12-13 13:51:13 -05:00
Marshall Bowers
61ab1834c7 Deploy the buffer search in a second row in the toolbar (#3630)
This PR updates the buffer search to deploy to a second row in the
toolbar, instead of trying to deploy into the initial row.

This is the same way it works in Zed1.

Release Notes:

- N/A
2023-12-13 13:06:53 -05:00
Joseph T. Lyons
07a266d93f collab 0.31.0 2023-12-13 12:39:29 -05:00
Piotr Osiewicz
b2965469e1 collab_ui: Wire up project picker (#3628)
Release Notes:

- N/A
2023-12-13 18:26:10 +01:00
Joseph T. Lyons
1055f59925 v0.118.x dev 2023-12-13 12:24:10 -05:00
Julia
cfc050e3fe Fire focus handlers on draw to avoid timing with newly created item
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-12-13 12:16:39 -05:00
Marshall Bowers
599f81f345 Wire up tooltips on tab bar actions (#3629)
This PR wires up the tooltips on the actions in the tab bar.

Release Notes:

- N/A
2023-12-13 12:11:33 -05:00
Piotr Osiewicz
6147530156 fixup! collab_ui: Wire up project picker 2023-12-13 17:58:17 +01:00
Piotr Osiewicz
a71365a1d3 collab_ui: Wire up project picker
Co-authored-by: Conrad <conrad@zed.dev>
2023-12-13 17:56:49 +01:00
Marshall Bowers
988fb91a61 Fix tab bar drop target sizing (#3627)
This PR fixes an issue where the tab bar drop target was not receiving
any size.

The styling isn't 100% correct yet, as the updated background color has
a gap around it.

Release Notes:

- N/A
2023-12-13 11:44:51 -05:00
Nate Butler
226d4929b5 WIP New App Docs (#3624)
[[PR Description]]

These docs are intended to replace both docs.zed.dev and introduce
people to how to build Zed from source.

Add or edit markdown files in the docs/src folder, and follow the
instructions in `how-to-deploy.md` to deploy the docs on
[Vercel](https://vercel.com/zed-industries/zed-app-docs/45ZTNBKSCmCMAvfAcfcocGnmoMyd).

Add pages and organize/reorder docs in the SUMMARY.md.

When you are sure a page is added or is no longer needed you can delete
it from `docs/old`

Release Notes:

- N/A
2023-12-13 10:54:26 -05:00
Julia
5730d0ef21 Bump livekit client 2023-12-13 10:47:32 -05:00
Nate Butler
9cd55a8806 Add note 2023-12-13 10:35:54 -05:00
Nate Butler
d4e088cb11 Add deploy note 2023-12-13 10:35:03 -05:00
Piotr Osiewicz
2a714d6dd8 Bring back semantic search UI (#3623)
- [x] Add "Include ignored" to filters
- [x] There seems to be a bug (seemingly unrelated to this PR) where we
reindex the project on each launch. Edit: Seems to be the case on Zed1
as well if the indexing is interrupted.

Release Notes:
- N/A
2023-12-13 16:17:42 +01:00
Antonio Scandurra
801125974a Optimize inserting lots of primitives with the same StackingOrder 2023-12-13 15:32:32 +01:00
Piotr Osiewicz
b9023a3e49 Add inclusion of ignored files 2023-12-13 14:22:29 +01:00
Piotr Osiewicz
a13468e6ad Bring back semantic search UI 2023-12-13 12:53:53 +01:00
Antonio Scandurra
13c064186a Fix regressions in focus and active support (#3622)
Release Notes:

- N/A
2023-12-13 11:31:37 +01:00
Antonio Scandurra
d147db1db9 Don't mark interactive element as active if default prevented 2023-12-13 11:23:05 +01:00
Antonio Scandurra
5018072203 Automatically transfer focus on mouse down in Interactivity::paint 2023-12-13 09:57:18 +01:00
Conrad Irwin
19e842b860 fix overlay positions (#3621)
- Fix typo in overlay positioning code
- Make overlay positioning more robust

Release Notes:

- N/A
2023-12-12 22:13:56 -07:00
Conrad Irwin
e91ecec8cd fix modal exchange (#3620)
- Fix opening GoToLine from Command
- Extend test to cover go_to_line behavior too

Release Notes:

- N/A
2023-12-12 21:30:34 -07:00
Conrad Irwin
a055a4c163 Make overlay positioning more robust
Previously we would either switch anchor or snap, now we first try switching
anchor (if desired) and then snap to window regardless.
2023-12-12 21:28:58 -07:00
Conrad Irwin
fa36adbf1f Fix typo in overlay positioning code 2023-12-12 21:14:48 -07:00
Conrad Irwin
979db25e91 Extend test to cover go_to_line behavior too
(It's also been broken and fixed a few times along the way)
2023-12-12 20:53:54 -07:00
Conrad Irwin
a2f0accb74 Fix opening GoToLine from Command
This was broken because of the async hop introduced by should_dismiss.

Change that API to instead be syncronous, and require that implementors
(of which there is only one) to call dismiss again if they want to.
2023-12-12 20:46:27 -07:00
Conrad Irwin
c94c6614ed vim2 (#3619)
Fix vim command tests by restoring the spawn in command palette's
confirm


Release Notes:

- N/A
2023-12-12 19:40:54 -07:00
Conrad Irwin
630997db28 And restore the command palette spawn behaviour 2023-12-12 19:27:09 -07:00
Conrad Irwin
b65129437f vim2 (#3618)
- Uncomment editor event tests
- Uncomment vim search tests

Just command left

Release Notes:

- N/A
2023-12-12 19:19:08 -07:00
Conrad Irwin
1a86e4ff96 Fix vim toggle on welcome 2023-12-12 19:03:54 -07:00
Conrad Irwin
a9349267ec hah, oops 2023-12-12 18:54:35 -07:00
Marshall Bowers
573377d9f9 Fix workspace sizing (#3617)
This PR fixes the sizing of the workspace, specifically with regards to
the center pane.

This fixes the issue where the tab bar would get clipped when its width
exceeded the size of the screen.

<img width="1298" alt="Screenshot 2023-12-12 at 8 36 15 PM"
src="https://github.com/zed-industries/zed/assets/1486634/592d7c6d-6901-4bd4-b5e7-e30bcad67e21">

Release Notes:

- N/A
2023-12-12 20:42:48 -05:00
Conrad Irwin
a656d11fed Fix vim2 search tests 2023-12-12 18:39:42 -07:00
Max Brunsfeld
e09b07ddae Allow dragging tabs (#3616) 2023-12-12 16:20:37 -08:00
Max Brunsfeld
6362221363 Scroll the tab bar to show the active tab
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
2023-12-12 15:42:33 -08:00
Max Brunsfeld
a579713a45 Allow dragging and dropping tabs
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
2023-12-12 15:07:03 -08:00
Max Brunsfeld
a4a501603e Start work on dragging tabs 2023-12-12 15:04:49 -08:00
Julia
2e00da5a79 zed2 notification panel (#3603)
Release Notes:

- N/A
2023-12-12 18:04:47 -05:00
Joseph T. Lyons
5f5b86ee24 Use release channel display name for feedback (#3615)
Release Notes:

-  N/A
2023-12-12 17:50:29 -05:00
Julia
29413bc72c Fix defer handle double lease
Co-Authored-By: Conrad Irwin <conrad@zed.dev>
2023-12-12 17:47:12 -05:00
Joseph T. Lyons
a63b4c598c Use release channel display name for feedback 2023-12-12 17:29:37 -05:00
Conrad Irwin
3088cbea68 Uncomment vim search tests 2023-12-12 15:29:17 -07:00
Conrad Irwin
c43969eaed Uncomment editor event tests 2023-12-12 15:29:10 -07:00
Julia
51ceb52931 Fix borked layout 2023-12-12 17:22:49 -05:00
Joseph T. Lyons
c9aa4a0e00 Fix placeholder text 2023-12-12 16:56:43 -05:00
Marshall Bowers
e489e2e583 Enable scrolling in tab bar (#3614)
This PR enables scrolling horizontally in the tab bar.

Currently this requires holding down <kbd>Shift</kbd> for the scroll to
activate. We'll need to look into this.

Scrolling also currently works when there is a split in the editor, as
the non-split view goes down a different rendering path that does not
constrain the pane width, which breaks a number of things.

Release Notes:

- N/A
2023-12-12 16:38:25 -05:00
Julia
01d4e711d8 Get notifications rendering in panel 2023-12-12 16:31:26 -05:00
Julia
d3eff6371e Panel showing up 2023-12-12 16:14:43 -05:00
Marshall Bowers
d2feaa41a5 Extract TabBar component (#3613)
This PR extracts a new `TabBar` component from the tab bar
implementation in the workspace.

Release Notes:

- N/A
2023-12-12 15:50:05 -05:00
Max Brunsfeld
31a4892a55 Avoid notifying editor when wrap width changes (#3612)
Wrap width is already assigned from within draw. It can be called
multiple times as taffy iteratively computes the layout.

This fixes a hang we were seeing in nightly when opening the chat panel.
2023-12-12 12:32:05 -08:00
Max Brunsfeld
f5ca514bf0 Avoid notifying editor when wrap width changes
Wrap width is already assigned from within draw. It can be called multiple
times as taffy iteratively computes the layout.

Co-authored-by: Nathan <nathan@zed.dev>
2023-12-12 12:06:38 -08:00
Piotr Osiewicz
2a22208518 Project search2 tests (#3611)
Release Notes:

- N/A
2023-12-12 19:35:15 +01:00
Max Brunsfeld
b3e1514b00 Avoid caching zed build script if git state has changed (#3610)
@as-cii and @SomeoneToIgnore noticed a bug where Zed nightly would
continuously report that an update was available. This nightly
auto-update logic depends on the `ZED_COMMIT_SHA` constant, which is
compiled into the app via an rustc environment variable that is assigned
in the `zed2` build script.

I think the bug was caused by the `zed2` build script's output being
cached on our CI, when building the nightly app bundle. The result was
that the `publish-nightly` action updated the "current SHA" for nightly,
but uploaded an artifact whose `ZED_COMMIT_SHA` was cached from an
earlier version.

I've added a line to the `build.rs` that triggers a rerun if the
`.git/logs/HEAD` file has been changed. I think this should prevent the
unwanted caching.
2023-12-12 10:27:57 -08:00
Julia
52e72d9648 Builds and runs 2023-12-12 13:07:17 -05:00
Max Brunsfeld
e3ed3b8c1c Avoid caching zed build script if git state has changed 2023-12-12 09:55:56 -08:00
Julia
fd6320b136 Finish fixing up Avatar using URI 2023-12-12 11:49:04 -05:00
Antonio Scandurra
e53b9f5ccf Fix Div::active not working
Somehow a condition got inverted and caused the bug.
2023-12-12 17:40:22 +01:00
Antonio Scandurra
35670368a5 Remove focus_listeners from Interactive
We're not using these anymore.
2023-12-12 17:39:07 +01:00
Julia
44d40625fe Start changing Avatar to use URI
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-12-12 11:27:40 -05:00
Piotr Osiewicz
cc97b04627 Buffer search channel notes (#3608)
We were registering `deploy` only on editors, which did succeed for
channel notes; however, channel note does not have an associated
workspace (that we pulled from the editor). It made more sense to just
register these actions for a workspace, notwithstanding the editor.

This PR also fixes a bunch of cx.dispatch_action calls to call the
handler directly instead (e.g. instead of dispatching ReplaceNext we
just call buffer_search_bar.replace_next instead) as otherwise these
actions are not handled if the buffer search bar does not have the
focus.

Release Notes:

- N/A
2023-12-12 17:09:37 +01:00
Marshall Bowers
a334a21f3e Prevent panes from overflowing the window (#3609)
This PR fixes an issues where it was possible for panes to overflow the
window (for instance, by having a large number of tabs in the tab bar).

Release Notes:

- N/A
2023-12-12 11:01:49 -05:00
Antonio Scandurra
c0846d6f74 Set cursor style only if we're the active window (#3607)
Release Notes:

- N/A
2023-12-12 16:56:32 +01:00
Piotr Osiewicz
13f9fec0bd fixup! Move away from using cx.dispatch_action in buffer search 2023-12-12 16:38:58 +01:00
Piotr Osiewicz
be57059195 Move away from using cx.dispatch_action in buffer search 2023-12-12 16:36:00 +01:00
Antonio Scandurra
b87c45e6f5 Set cursor style only if we're the active window 2023-12-12 16:32:45 +01:00
Kirill Bulatov
4684440202 Fix focus issues with gpui2 docks (#3606)
* Fixed dock toggling not focusing the terminal element
* Fixed loosing focus on dock close (e.g. cmd-d on the last terminal in
the dock)
* Removed element stateless focus API since it would not work when the
element is not rendered, update all API usages to the stateful one via
`gpui::Subscription`

Release Notes:

- N/A
2023-12-12 17:26:39 +02:00
Kirill Bulatov
0140bb862e Fix the tests
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-12 17:19:12 +02:00
Kirill Bulatov
ca8e8d1065 Finish removing all dangerous focus APIs 2023-12-12 16:11:14 +02:00
Nate Butler
9f21fe2d50 Futher outline 2023-12-12 09:10:12 -05:00
Antonio Scandurra
97eae4b081 Improve rendering performance in gpui2 (#3605)
The biggest improvements come from preventing element moves where
unnecessary, and when they are absolutely needed, try to make the struct
we're moving as small as possible. Having big structs on the stack (such
as `Interactivity`) increases the cost of moving but also reduces
opportunities for other compiler optimizations (e.g., inlining).

One more notable change was using `FxHashMap` and `FxHashSet` in hot
code paths where we don't need those collections to be resistant to DoS.

Another thing I am seeing a lot in the profiler is interacting with
`StackingOrder` (cloning it, searching for it, inserting into the
`Scene`). I have some thoughts on how to optimize that but I punted on
it because performance seems to be pretty good now.

Release Notes:

- N/A
2023-12-12 15:09:43 +01:00
Antonio Scandurra
6159a59534 Fix assertions now that we use different hash functions 2023-12-12 14:59:51 +01:00
Kirill Bulatov
2cde1a2e15 Re-focus window on workspace on corresponding window blur
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-12 15:36:20 +02:00
Piotr Osiewicz
1c437a2b92 Register buffer search listener on new workspaces and not editors 2023-12-12 14:23:52 +01:00
Kirill Bulatov
717b2885f8 Attempt to remove the dangeous element focus API 2023-12-12 15:07:27 +02:00
Antonio Scandurra
43b8d65fee Transfer focus to the workspace when window focus is lost 2023-12-12 15:07:27 +02:00
Kirill Bulatov
137104e00e Fix dock panels not focusing their contents on toggle
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-12 15:07:27 +02:00
Kirill Bulatov
706dd8246e Improve project panel autoreveal ergonomics (#3604)
Part of
https://linear.app/zed-industries/issue/Z-1386/add-equivalent-setting-to-vscodes-auto-reveal-exclude
Deals with https://github.com/zed-industries/community/issues/800

* add a `project_panel::auto_reveal_entries` config entry to allow
disabling auto reveal in project panel (auto reveal is enabled by
default)
* add a `pane::RevealInProjectPanel` action (and a pane tab context menu
entry) to manually reveal any file entry
* stop auto revealing gitignored directories at all

We can add the auto reveal exclude globs later, if needed, but let's try
to keep the config simpler and start with a more minimalist approach.

Release Notes:

- Improved project panel auto reveal mechanics: gitignored files are not
auto revealed anymore; a `auto_reveal_entries = true` config option for
`project_panel` is added; a `pane::RevealInProjectPanel` action and a
corresponding buffer tab context menu were added
2023-12-12 14:37:30 +02:00
Antonio Scandurra
b871f906d6 Use FxHashMap and FxHashSet in hot code paths
We can also use these maps and sets in place of `SeaHasher` because
they are also deterministic. Note that we're not swapping std's
`HashMap` and `HashSet` wholesale inside of `collections` because
on the server we need cryptographically secure collections.
2023-12-12 13:35:22 +01:00
Antonio Scandurra
385c830bef Make each DispatchNode smaller by allocating more 2023-12-12 13:16:09 +01:00
Kirill Bulatov
d9b0828beb Port to gpui2 2023-12-12 13:50:23 +02:00
Kirill Bulatov
721d7615c3 Add tests 2023-12-12 13:34:46 +02:00
Antonio Scandurra
f312c58b30 Reduce the stack size of Interactivity at the cost of more allocations 2023-12-12 11:57:44 +01:00
Kirill Bulatov
27d6432c84 Rework the way project panel auto reveals entries
* gitignored entries are never auto revealed
* `project_panel::auto_reveal_entries = true` settings entry was added,
setting it to `false` will disable the auto reveal
* `pane::RevealInProjectPanel` action was added that activates the project panel and reveals the entry it got triggered on (including the gitignored ones)
2023-12-12 11:38:51 +02:00
Antonio Scandurra
e7094cc98d Don't move interactivity unnecessarily during layout 2023-12-12 09:43:33 +01:00
Antonio Scandurra
b503edf24f Avoid cloning entries in CollabPanel::render_signed_in 2023-12-12 09:42:35 +01:00
Julia
376716254f Start port of notification panel 2023-12-11 20:40:48 -05:00
Max Brunsfeld
a9f817fc14 Allow dragging and dropping files in the project panel (#3602)
Also, fix a bug that prevented drag and drop from working in the collab
panel.
2023-12-11 17:03:07 -08:00
Max Brunsfeld
6f5b1064ee Fix detection of topmost region under the dragged item
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-12-11 16:50:44 -08:00
Nate Butler
9cb6f6689e WIP 2023-12-11 19:17:13 -05:00
Marshall Bowers
39a115b264 Take a first pass at styling the welcome screen (#3601)
This PR is a first pass at styling the welcome screen in Zed2.

Here's the current state:

<img width="1237" alt="Screenshot 2023-12-11 at 7 00 43 PM"
src="https://github.com/zed-industries/zed/assets/1486634/a0cbd5ca-7331-4785-bf46-f83fc4cb3bb6">

Release Notes:

- N/A
2023-12-11 19:06:33 -05:00
Max Brunsfeld
cbce49ff68 Start work on dragging entries in the project panel 2023-12-11 15:52:58 -08:00
Max Brunsfeld
a208229a2c Use Box instead of Rc for List event handlers 2023-12-11 15:52:06 -08:00
Conrad Irwin
149e90c3d9 vim2 (#3597)
- MOAR TESTS

[[PR Description]]

Release Notes:

- N/A
2023-12-11 16:42:03 -07:00
Marshall Bowers
75c1f0e997 Add w_vw and h_vh to StyledExt for setting sizes in viewport units (#3600)
This PR adds `w_vw` and `h_vh` methods to `StyledExt`.

These methods are the same as their `w` and `h` counterparts, but
operate in viewport units, giving us the equivalent of `vw` and `vh` in
CSS.

You can see them in action in this story:

```
cargo run -p storybook2 -- components/viewport_units
```

Release Notes:

- N/A
2023-12-11 18:19:11 -05:00
Conrad Irwin
3779316e4e Fix scroll tests
For some reason in gpui2, the window contains an extra 59px of space..
2023-12-11 16:12:17 -07:00
Max Brunsfeld
805df4fd15 Implement welcome view in zed2. (#3599)
Still needs styling.
2023-12-11 14:54:03 -08:00
Max Brunsfeld
bb5fa6bc13 Implement welcome view in zed2.
Still needs styling.
2023-12-11 14:48:42 -08:00
Marshall Bowers
3120a9cbea theme_importer: Fall back to inactive tab background if no active tab background set (#3598)
This PR makes it so the `theme_importer` falls back to the inactive tab
background color if the theme doesn't not an active tab background
color.

This fixes the active tab color in the Synthwave 84 theme.

Release Notes:

- N/A
2023-12-11 17:39:46 -05:00
Conrad Irwin
2d59492aac Most tests now... 2023-12-11 15:25:30 -07:00
Conrad Irwin
e37173fe97 And some more 2023-12-11 15:14:13 -07:00
Conrad Irwin
3baf125f4a All the easy tests? 2023-12-11 15:13:11 -07:00
Marshall Bowers
050ff6b27d Adjust scrollbar track background and border colors (#3596)
This PR adjusts the colors we pull from the VS Code themes to use for
the scrollbar track background and border.

For the scrollbar track background we now use the `editor.background`,
and for the scrollbar track border we use `editorOverviewRuler.border`.

Release Notes:

- N/A
2023-12-11 17:08:39 -05:00
Conrad Irwin
d12eb0581a vim2 (#3594)
- First round of vim tests

Add `observe_keystrokes` back to gpui2
Allow multiple actions to match a given key event

[[PR Description]]

Release Notes:

- (Added|Fixed|Improved) ...
([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
2023-12-11 15:06:51 -07:00
Conrad Irwin
1e89092d62 MOAR TESTS 2023-12-11 15:06:33 -07:00
Conrad Irwin
303189e086 Fix warnings 2023-12-11 14:54:58 -07:00
Marshall Bowers
e271bbe896 Use editorOverviewRuler.border for the scrollbar track border 2023-12-11 16:54:53 -05:00
Marshall Bowers
dfd1b5aa11 Use the editor background for the scrollbar track background 2023-12-11 16:39:26 -05:00
Marshall Bowers
aa27e1bcec Draw the scrollbar track left border 2023-12-11 16:39:00 -05:00
Mikayla Maki
927d18b0f3 Add support for resizing splits and docks, as well as utilities (#3595)
Making this PR to upstream changes to util and GPUI2, resizing exists
but isn't working yet.

Release Notes:

- N/A
2023-12-11 13:32:06 -08:00
Mikayla
cb8109ab98 Remove some debug styles 2023-12-11 13:23:07 -08:00
Conrad Irwin
4942b1962b Moar tests 2023-12-11 14:21:21 -07:00
Mikayla
f03c0f6e63 Add double click handling 2023-12-11 13:17:42 -08:00
Conrad Irwin
d1805d8ada First round of vim tests 2023-12-11 14:16:25 -07:00
Max Brunsfeld
f12510b8b0 Defer drawing the window until the CoreAnimation displayLayer: method is called (#3592)
GPUI (both 1 and 2) currently performs rendering, layout, and painting
at the end of every effect cycle. This leads to poor performance when
the app receives events more frequently than the display refreshes. Such
rapid events can come from a terminal, an LSP, or a mouse with a high
polling rate.

This PR changes GPUI so that we don't render until the OS notifies us
that the content will be presented, via the `displayLayer:` callback.

Because render, layout, and paint have side effects that are sometimes
relied on in tests, we currently keep the old behavior (drawing after
every effects cycle) in tests.

This is similar to what @ForLoveOfCats explored in
https://github.com/zed-industries/zed/pull/3542, but slightly simpler,
in that we're not using the display link. As a follow-up, we could use
the display link to start rendering earlier, to possibly reduce latency
further.
2023-12-11 13:03:04 -08:00
Marshall Bowers
41f55e4d0d Use tab.inactiveForeground from VS Code for muted text color (#3593)
This PR updates the `theme_importer` to pull in the
`tab.inactiveForeground` color from VS Code to use as the muted text
color.

Release Notes:

- N/A
2023-12-11 15:57:47 -05:00
Max Brunsfeld
fdcb413e33 Fix unused field warning 2023-12-11 12:56:18 -08:00
Marshall Bowers
f02a3e8c68 Fix inactive tab styles (the verbose way) (#3591)
This PR fixes the inactive tab style to properly show the label using
the muted text color.

I went about fixing this in the most direct way possible, but the
solution leaves a lot to be desired, IMO. I plan to explore some ideas
on how we can improve the state of styling the tab content without
having the same styles repeated all over the place and subsequently
out-of-sync.

Release Notes:

- N/A
2023-12-11 15:42:17 -05:00
Max Brunsfeld
fbfe108317 Avoid double borrow in tests by drawing windows explicitly in flush_effects
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-11 12:37:10 -08:00
Conrad Irwin
fcbc18a380 vim-flight entertainment (#3574)
- vim2 compiling (but mostly commented out)
- More code written, similar lack of workingness so far

Still todo:
[ ] Figure out the focus/blur stuff
[ ] Uncoment more code
[ ] Fix VimTestContext
[ ] Uncomment the tests

Release Notes:

- N/A
2023-12-11 12:11:13 -07:00
Antonio Scandurra
9b94f1483a Redraw the window at the end of flush_effects in tests
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-12-11 20:10:27 +01:00
Joseph T. Lyons
906505f4dc Fix rustfmt in feedback_modal.rs 2023-12-11 13:56:58 -05:00
Antonio Scandurra
f1ebad22db Defer drawing the scene until macOS's display_layer callback
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-12-11 19:55:55 +01:00
Conrad Irwin
f35453caad COmment out tests again for now 2023-12-11 11:40:54 -07:00
Marshall Bowers
7f4d03fab2 Fix color for selections in scrollbar (#3590)
This PR fixes an issue where we weren't using the right color for
displaying selections within the scrollbar.

Release Notes:

- N/A
2023-12-11 13:34:29 -05:00
Joseph T. Lyons
22e99033e1 Use copy button for copy channel link (#3589)
Release Notes:

- N/A
2023-12-11 13:22:36 -05:00
Mikayla
3198eb6c6d Merge branch 'main' into splits 2023-12-11 10:16:32 -08:00
Joseph T. Lyons
9a36402fbe Use copy button for copy channel link 2023-12-11 13:14:29 -05:00
Marshall Bowers
10b015cdf0 Style scrollbars with theme colors (#3588)
This PR updates the styling of scrollbars in the editor to use the theme
colors.

Release Notes:

- N/A
2023-12-11 13:06:43 -05:00
Joseph T. Lyons
cd2abc7c1d Adjust nightly to build twice a day 2023-12-11 13:05:16 -05:00
Conrad Irwin
8ed642dc16 Clear out easy todo!()s 2023-12-11 11:00:55 -07:00
Marshall Bowers
fe46459982 Pull more scrollbar colors from VS Code 2023-12-11 12:59:03 -05:00
Marshall Bowers
6add17338c Style scrollbar when using diff markers 2023-12-11 12:58:55 -05:00
Marshall Bowers
3271cf1030 Map scrollbar colors from VS Code themes 2023-12-11 12:49:54 -05:00
Marshall Bowers
89884d289f Pull scrollbar colors from theme 2023-12-11 12:47:29 -05:00
Conrad Irwin
dd42adc4e5 Remove warnigns 2023-12-11 10:45:27 -07:00
Nate Butler
b9bbc4a698 Extend theme colors (#3587)
[[PR Description]]

Title

Release Notes:

- N/A
2023-12-11 12:34:06 -05:00
Conrad Irwin
af7c93b8d5 Merge branch 'main' into vim2 2023-12-11 10:19:18 -07:00
Conrad Irwin
c97c9f98c0 Revert actions! changes 2023-12-11 10:19:03 -07:00
Antonio Scandurra
76cea3e7e8 Fire click event only when using left button (#3584)
Release Notes:

- N/A
2023-12-11 18:00:13 +01:00
Nate Butler
ea9a42b802 Add new colors to themes
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-11 12:00:12 -05:00
Conrad Irwin
59d0feafc5 actions3 (#3586)
- Fix action registrations

Release Notes:

- N/A
2023-12-11 09:48:57 -07:00
Nate Butler
b8f9918f75 Add new colors to One Dark
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-11 11:43:49 -05:00
Conrad Irwin
b66b4915cf Merge branch 'main' into vim2 2023-12-11 09:38:23 -07:00
Nate Butler
6792a216a6 Add new color defs 2023-12-11 11:30:43 -05:00
Conrad Irwin
d678efe7dd Fix some action registrations 2023-12-11 09:29:10 -07:00
Piotr Osiewicz
bc130fd6b0 Project search2 (#3585)
Semantic search and tests are gonna be shipped in a separate PR.

Release Notes:

- N/A
2023-12-11 17:20:13 +01:00
Piotr Osiewicz
01ecbec2b0 Comment out the tests 2023-12-11 16:52:12 +01:00
Piotr Osiewicz
ddbd5cf2cb Merge branch 'main' into project_search2 2023-12-11 16:47:48 +01:00
Piotr Osiewicz
40c1ef88e2 Add Editor::Tab and CycleMode actions 2023-12-11 16:39:17 +01:00
Antonio Scandurra
0b7072bf67 Fire click event only when using left button 2023-12-11 16:20:46 +01:00
Piotr Osiewicz
9f1e7a1e21 fixup! Further warnings churn 2023-12-11 15:54:36 +01:00
Piotr Osiewicz
d4a246cae8 Further warnings churn 2023-12-11 15:53:19 +01:00
Piotr Osiewicz
3c49011d08 Fix up warnings (automatic) 2023-12-11 15:48:42 +01:00
Piotr Osiewicz
7330495709 Make match index 1-based 2023-12-11 15:47:02 +01:00
Piotr Osiewicz
3f87c3541f Make the query editor expand in size less 2023-12-11 15:40:38 +01:00
Piotr Osiewicz
66e0650f75 Forego using dispatch_action for click handlers 2023-12-11 15:32:28 +01:00
Antonio Scandurra
db39c3d582 Fix porting mistake that caused clicking on the editor to misbehave (#3583)
We used `width` instead of `height` in the "pixels-to-point" conversion
code, which would cause clicks to not work correctly when the width was
smaller than the `y` coordinate.

Release Notes:

- N/A
2023-12-11 14:30:06 +01:00
Antonio Scandurra
fdc0ef8ce0 Maintain focus correctly when activating panes in zed2 (#3582)
Previously, before emitting a `Focus` event from the pane inside of the
`focus_in` listener, we would erroneously check whether the pane's focus
handle was _not_ focused. However, by the time the pane was notified of
being "focused in", the focus handle would already be focused, which was
preventing the pane from ever emitting a `Focus` event. In turn, this
would cause the workspace to not maintain the active pane correctly.

This pull request maintains an explicit `was_focused` boolean as part of
the `Pane` state, which ensures we only emit the `Focus` event the first
time the pane receives focus.

As part of this, I also reworked how the outline view gets deployed to
allow clicking breadcrumbs even when the corresponding pane doesn't have
focus.

Release Notes:

- N/A
2023-12-11 14:29:56 +01:00
Antonio Scandurra
b4fc8ea6f7 Fix porting mistake that caused clicking on the editor to misbehave
We used `width` instead of `height` in the "pixels-to-point" conversion
code, which would cause clicks to not work correctly when the width was
smaller than the `y` coordinate.
2023-12-11 14:21:55 +01:00
Antonio Scandurra
3c56f6d5c1 Focus editor when confirming outline item 2023-12-11 14:18:46 +01:00
Piotr Osiewicz
38d844ab66 fixup! Add major text 2023-12-11 14:03:43 +01:00
Antonio Scandurra
5768f14a03 Emit Focus events from Pane only when it didn't have focus before 2023-12-11 14:02:08 +01:00
Antonio Scandurra
e7c14dacae Show the correct outline when clicking on a breadcrumb 2023-12-11 14:01:16 +01:00
Piotr Osiewicz
1599ee5467 Add major text 2023-12-11 13:56:02 +01:00
Piotr Osiewicz
fe315c6111 Bind actions on the whole search bar, not just the query editor 2023-12-11 13:35:25 +01:00
Kirill Bulatov
36d2ab4537 Always open new project search view workspace::NewSearch action (#3581) 2023-12-11 13:45:41 +02:00
Kirill Bulatov
cdf7c10b57 Add an action to expand Rust macros with rust-analyzer (#3580)
Deals with https://github.com/zed-industries/community/issues/1282

If rust-analyzer responds with non-empty message, opens a new singleton
multibuffer with the expansion result:

![image](https://github.com/zed-industries/zed/assets/2690773/5f790ffd-1ad8-4f02-a60d-49ee0756362d)

In gpui2, only editors with excerpts containing Rust languages get the
action, in gpui1 every editor (even the *.ts one) has this action
enabled but it does nothing.

Release Notes:

- Added an `editor::ExpandMacroRecursively` command to expand Rust
macros with rust-analyzer.
2023-12-11 13:11:59 +02:00
Kirill Bulatov
da44b51a53 Always open new project search view workspace::NewSearch action 2023-12-11 13:11:07 +02:00
Kirill Bulatov
55374e8ac0 Port to gpui1 2023-12-11 12:28:22 +02:00
Kirill Bulatov
3694265b6b Finalize the command 2023-12-11 12:28:22 +02:00
Kirill Bulatov
e3fc810b3d Draft an expand macro recusively command 2023-12-11 12:28:22 +02:00
Antonio Scandurra
db8e58b888 Introduce scrollbars in editor2 (#3579)
Release Notes:

- N/A
2023-12-11 11:07:11 +01:00
Antonio Scandurra
e3c0b1f903 Listen for mouse events on scrollbar 2023-12-11 10:33:05 +01:00
Antonio Scandurra
03df63aa00 Merge remote-tracking branch 'origin/main' into scrollbars2 2023-12-11 09:51:43 +01:00
Conrad Irwin
cea26c9d10 actions3 (#3578)
- Restore impl_actions! and remove derive(Action)

Originally in gpui2 we inferred the action's namespace from the module
it was
defined in. This worked most of the time (ignoring the "remove_the_2"
hack),
but caused unintended (and mostly invisible) behavior in crates with
multiple
modules.

#3577 restored the namespace parameter to actions!, and this PR
reintroduces
`impl_actions!` to do the same for non-unit structs.

I considered trying to keep the struct-attribute API work, because it
does feel
more stylistically appropriate for rust, but two problems lead to it
feeling less good than `impl_actions!` in practice:
1. You have to repeat the namespace for each struct (and usually you're
defining them all in the same namespace)
2. You can't pass an argument to a derive macro inline, you need to use
an attribute instead.


Release Notes:

- N/A
2023-12-10 19:59:53 -07:00
Conrad Irwin
c5367e3085 Correct docs re:Debug 2023-12-10 19:52:17 -07:00
Conrad Irwin
9ce7395b74 Restore impl_actions! and remove derive(Action) 2023-12-10 19:33:38 -07:00
Joseph T. Lyons
ff45b43857 Add code for submission and error states for feedback modal (#3572)
Release Notes:

- N/A
2023-12-10 20:49:14 -05:00
Conrad Irwin
62d6a85a71 Restore namespace parameter to actions! (#3577)
This does not yet fix `derive(Action)`, but will conflict with a lot so
wanted to merge quickly.

Although automatically deriving the namespace worked in many situations,
it was
unclear what to do with nested modules. Vim wanted all actions to be
registered
under vim, while collab_ui wanted one namespace per action.

It seems better to make the implicit explicit, and give the flexibility
to hide
implementation details from the end-users.

Release Notes:

- N/A
2023-12-09 15:27:17 -07:00
Conrad Irwin
4290c67b6a Restore namespace parameter to actions!
This does not yet fix `derive(Action)`, but will conflict with a lot so
wanted to merge quickly.
2023-12-09 15:18:31 -07:00
Max Brunsfeld
cbbb0d033e Paint code-action/fold buttons above the gutter (#3576)
Previously, clicking on fold indicators or code action lightning bolts
wouldn't work - it would instead perform the normal gutter click handler
(selecting the clicked line).

By painting those indicators at a higher z-index, we ensure that the
indicators' click handlers are invoked instead.
2023-12-09 11:56:07 -08:00
Max Brunsfeld
7b722c326f Paint code-action/fold buttons above the gutter 2023-12-09 11:41:40 -08:00
Max Brunsfeld
188d727d31 Fix crash due to mutating command palette's candidates on confirmation (#3575)
I was seeing a crash when confirming the command palette. It was caused
by getting the palette's `commands` (match candidates) and `matches`
getting out of sync because we mutated `commands` when removing the
selected command.
2023-12-09 11:16:04 -08:00
Max Brunsfeld
6b58eb202d Fix crash due to mutating command palette's candidates on confirmation 2023-12-09 11:08:33 -08:00
Piotr Osiewicz
0be58eb61a Add a bunch of tooltips 2023-12-09 12:24:55 +01:00
Piotr Osiewicz
b09d2219d0 Add basic landing page 2023-12-09 12:00:44 +01:00
Joseph T. Lyons
33c41fb5e1 Show negative numbers after limit is hit 2023-12-09 00:35:19 -05:00
Mikayla
0ee61e214d Fix drag events 2023-12-08 17:02:25 -08:00
Max Brunsfeld
4adafb1148 Improve the chat panel in zed 2 (#3573)
* render markdown formatting
* wrap the chat messages
* enable the status bar button
2023-12-08 16:54:10 -08:00
Conrad Irwin
a4ea7bf928 More code written, similar lack of workingness so far 2023-12-08 17:45:17 -07:00
Max Brunsfeld
f3b764b7d2 Enable tests for MentionsEditor 2023-12-08 16:42:36 -08:00
Max Brunsfeld
e4bc346723 Initialize toggle chat panel action 2023-12-08 16:42:22 -08:00
Mikayla
fd34d1da31 Add back semi-funcitonal pane resizing code 2023-12-08 16:29:42 -08:00
Joseph T. Lyons
9abf5a1bf2 Add code for submission and error states 2023-12-08 19:16:03 -05:00
Max Brunsfeld
049314e84e Restructure chat message rendering to allow text to wrap 2023-12-08 15:44:27 -08:00
Max Brunsfeld
d03e29d55d Start work on rendering formatted chat messages 2023-12-08 15:44:27 -08:00
Marshall Bowers
8feb11ccee Make clicking the breadcrumb toggle the symbol outline (#3571)
This PR wires up clicks on the breadcrumb to toggle the symbol outline.

Note that the behavior of the symbol outline is a little wonky at the
moment, due to the issues with pane focus.

Release Notes:

- N/A
2023-12-08 18:22:53 -05:00
Marshall Bowers
008c56a09b Adjust which status colors we import from the VS Code theme (#3570)
This PR adjusts the status colors we import from the VS Code theme to be
the right ones.

Instead of looking at the Git status colors, we use the
`editorGutter.addedBackground`, `editorGutter.modifiedBackground`, and
`editorGutter.deletedBackground` colors for added, modified, and deleted
status colors, respectively.

Release Notes:

- N/A
2023-12-08 17:56:25 -05:00
Marshall Bowers
cf42d95af6 Use the diff status colors defined in the theme (#3569)
This PR updates the diff status colors to use the ones defined in the
theme instead of placeholder colors.

Release Notes:

- N/A
2023-12-08 17:41:11 -05:00
Joseph T. Lyons
60e4311294 Fix bug where feedback dismissal prompt is shown twice (#3568)
Release Notes:

- N/A
2023-12-08 17:25:48 -05:00
Joseph T. Lyons
cbbcc3502a Fix bug where feedback dismissal prompt is shown twice 2023-12-08 17:21:14 -05:00
Marshall Bowers
44890d6966 Weight scope matches to improve results (#3567)
This PR adds a simple weighting system for the matching of scopes from
the VS Code theme.

This gives us more control over the matches we get out of the theme, and
subsequently improves the results.

Release Notes:

- N/A
2023-12-08 16:42:13 -05:00
Marshall Bowers
2517b1017b Weight scope matches to improve results 2023-12-08 16:35:54 -05:00
Joseph T. Lyons
1fecd3c327 Allow modals to override their dismissal (#3565)
Release Notes:

- N/A
2023-12-08 16:28:16 -05:00
Max Brunsfeld
be636348c4 Enable chat panel in zed 2 (#3564) 2023-12-08 13:25:59 -08:00
Joseph T. Lyons
13dad89a85 Move all dismissal logic into dismiss_event 2023-12-08 16:14:45 -05:00
Max Brunsfeld
324aeaa0de Enable chat panel test, remove comments 2023-12-08 13:08:47 -08:00
Julia
ad05f22256 zed2: Store constructed terminal tooltip (#3566)
Allows the terminal to actually render its hyperlink tooltip

Release Notes:

- N/A
2023-12-08 16:05:53 -05:00
Marshall Bowers
2ef3954700 Fix function colors in Gruvbox themes 2023-12-08 16:04:58 -05:00
Max Brunsfeld
650ca1f39b Tweak styling of channel header and editor footer in chat panel 2023-12-08 13:04:25 -08:00
Max Brunsfeld
fce0c1f4d0 Dedup actions in WindowContext::available_actions 2023-12-08 13:02:14 -08:00
Julia
e9730e0b5c zed2: Store constructed terminal tooltip 2023-12-08 15:57:22 -05:00
Joseph T. Lyons
cd93ac1d2a Fix error 2023-12-08 15:34:42 -05:00
Max Brunsfeld
c739906413 Make chat message list fill the available space 2023-12-08 12:31:07 -08:00
Max Brunsfeld
c7d8169cab Get the chat panel wired up again 2023-12-08 12:31:07 -08:00
Nathan Sobo
213ed2028c Get more of chat panel compiling, but lots of todos 2023-12-08 12:31:07 -08:00
Max Brunsfeld
6955579f19 Start work on chat panel and non-uniform list
Co-authored-by: Nathan <nathan@zed.dev>
2023-12-08 12:30:49 -08:00
Joseph T. Lyons
113c7287df Allow modals to override their dismissal
Co-Authored-By: Mikayla Maki <mikayla.c.maki@gmail.com>
2023-12-08 15:29:44 -05:00
Marshall Bowers
4353bdb9d5 Restore theme sorting in Zed2 (#3563)
This PR restores the sorting of themes in the theme selector in Zed2.

Release Notes:

- N/A
2023-12-08 15:08:35 -05:00
Julia
40b219f861 zed2: Position terminal context menu, wire up dismis, and unbork context menu (#3561)
Release Notes:

- N/A
2023-12-08 14:47:28 -05:00
Kirill Bulatov
f67d4cea31 Add more harness around nightly builds (#3562)
Part of
https://zed-industries.slack.com/archives/C05SJGT0M33/p1702039430077589
The nightly job managed to pass successfully (build & upload nightly),
logged a proper commit sha that got into latest_sha on the nightly
bucket — but Zed version in about was wrong.

* Log Zed build sha in release builds to ensure CI uses the right one
* make curl to return non-zero code for non-200 nightly file uploads

Release Notes:

- N/A
2023-12-08 21:47:02 +02:00
Kirill Bulatov
0dc02b8354 Log Zed build sha in release builds.
Also ensure that curl commands for nightly uploads return 200 (fail otherwise).
2023-12-08 21:38:28 +02:00
Marshall Bowers
133b1a8ad2 Fix palette/picker colors for imported themes (#3560)
This PR fixes the palette/picker colors for themes imported from VS
Code.

- We now use the `dropdown.background` to set the background color for
elevated surfaces
- We now pull in `list.activeSelectionBackground` to use as the color
for selected ghost elements

Release Notes:

- N/A
2023-12-08 14:27:12 -05:00
Julia
f0cc54a0b5 Comment the weirdness
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-12-08 14:26:02 -05:00
Marshall Bowers
27501d2929 Pull in colors for selected ghost elements 2023-12-08 14:19:56 -05:00
Marshall Bowers
77c8108f9b Use dropdown.background from VS Code for elevated surface background 2023-12-08 14:13:50 -05:00
Marshall Bowers
1c850f495c Use breadcrumb.background from VS Code for toolbar background (#3559)
This PR changes the color we use for the toolbar background from the VS
Code theme to `breadcrumb.background`.

If this value isn't set then we fall back to the `editor.background`.

Release Notes:

- N/A
2023-12-08 14:03:18 -05:00
Marshall Bowers
1d35a815a6 Use editorGroupHeader.tabsBackground from VS Code as tab bar background (#3558)
This PR changes the color we use for the tab bar background from the VS
Code theme to `editorGroupHeader.tabsBackground`.

Release Notes:

- N/A
2023-12-08 13:50:56 -05:00
Julia
79e6dedb7a Track focus shenanigans with context menu
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-12-08 13:49:42 -05:00
Conrad Irwin
32837d67be vim2 compiling (but mostly commented out) 2023-12-08 18:47:14 +00:00
Marshall Bowers
c653a02be3 Refine imported themes further (#3557)
This PR refines the imported themes further:

- Empty strings for color values in the VS Code theme are now ignored
- Pull Git status colors from VS Code themes
- Add `constant` colors as a fallback for `number` tokens

Release Notes:

- N/A
2023-12-08 13:35:11 -05:00
Joseph T. Lyons
f4c93abad5 🚧 Feedback modal UI 🚧 (#3536)
[[PR Description]]

TODO: 
- [x] Add placeholder text to editor
- [x] Add external link icon to "Community repo" button
- [x] Show `not-allowed` cursor for disabled buttons
- [ ] Add `Headline` ui component
- [ ] Finish UI pass
- [ ] Fix `IconPosition` on button (should swap the icon side)
- [ ] Add conditional tooltip for disabled "Send feedback" button.
- [ ] Add common/top feedback link.
- [ ] Add `vw`/`vh` units to allow sizing the modal based on viewport
size.

Release Notes:

- N/A
2023-12-08 13:30:42 -05:00
Marshall Bowers
62155f3a88 Add fallback to constant tokens for numbers 2023-12-08 13:20:00 -05:00
Joseph T. Lyons
fdde76c1a5 Cargo fmt 2023-12-08 13:15:12 -05:00
Marshall Bowers
ab5b76e943 Pull Git status colors from VS Code themes 2023-12-08 13:14:22 -05:00
Joseph T. Lyons
be6c909587 Remove some unused imports 2023-12-08 13:13:40 -05:00
Marshall Bowers
a5a0ad8b5c Add missing serde attribute to foreground 2023-12-08 13:13:19 -05:00
Max Brunsfeld
726d761646 Bump tree-sitter-vue to remove dangling submodule 2023-12-08 10:12:18 -08:00
Marshall Bowers
9ac9532d3d Treat empty strings as None when deserializing VS Code themes 2023-12-08 13:12:09 -05:00
Joseph T. Lyons
63cc9e5068 Move character counter up above editor in feedback modal 2023-12-08 13:07:16 -05:00
Joseph T. Lyons
0cab3de0ae collab 0.30.1 2023-12-08 12:48:02 -05:00
Joseph T. Lyons
21476ec51d Add release channel to panic collab upload (#3556)
Release Notes:

- N/A
2023-12-08 12:25:59 -05:00
Piotr Osiewicz
c51cedef14 Re-enable navigation with mouse navigation buttons (#3555)
Release Notes:

- N/A
2023-12-08 18:18:55 +01:00
Joseph T. Lyons
7964b35607 Add release channel to panic collab upload 2023-12-08 12:10:25 -05:00
Marshall Bowers
8987b2205c Fix line endings in Palenight source themes (#3554)
This PR fixes the line endings in the Palenight source themes.

Release Notes:

- N/A
2023-12-08 12:04:24 -05:00
Julia
970c7b8987 zed2: Properly position terminal context menu & hide on dismiss 2023-12-08 12:02:31 -05:00
Piotr Osiewicz
a283cbaf8f Re-enable navigation with mouse navigation buttons 2023-12-08 17:59:52 +01:00
Marshall Bowers
0b2aa052ea Refine imported themes (#3553)
This PR continues the work of refining the imported VS Code themes.

Release Notes:

- N/A
2023-12-08 11:53:56 -05:00
Marshall Bowers
52e4c577d2 Re-import Rosé Pine source themes 2023-12-08 11:45:08 -05:00
Joseph T. Lyons
4596e7a68a Use consistent text 2023-12-08 11:39:10 -05:00
Joseph T. Lyons
b66e1d2d58 Fix compiler error 2023-12-08 11:35:49 -05:00
Marshall Bowers
6f064cfc36 Improve matching for constructor tokens 2023-12-08 11:34:39 -05:00
Joseph T. Lyons
260a753005 Fix rustfmt 2023-12-08 11:33:18 -05:00
Marshall Bowers
6634a5e9f6 Improve matching for constant tokens further 2023-12-08 11:23:42 -05:00
Marshall Bowers
1b6721170a Improve matching for constant tokens 2023-12-08 11:21:40 -05:00
Marshall Bowers
63ce7cd407 Improve matching for preprocessor tokens 2023-12-08 11:17:05 -05:00
Marshall Bowers
40a95221ea Improve matching for string tokens 2023-12-08 11:13:28 -05:00
Marshall Bowers
a8a5b9524d Improve matching for punctuation tokens 2023-12-08 11:07:52 -05:00
Marshall Bowers
53d77b192a Don't match support.function for function 2023-12-08 10:48:27 -05:00
Antonio Scandurra
9b0bea32ed 🎨 2023-12-08 14:24:58 +01:00
Antonio Scandurra
28dfd3ab43 Paint scrollbars
We still need to wire up mouse listeners.
2023-12-08 14:17:38 +01:00
Piotr Osiewicz
53ff5ff724 buffer_search: Add tooltips, polish keybinds (#3552)
[[PR Description]]

Release Notes:

- N/A
2023-12-08 13:39:08 +01:00
Piotr Osiewicz
d0a673ec62 buffer_search: Add tooltips, polish keybinds 2023-12-08 12:37:20 +01:00
Marshall Bowers
7a9f764aa0 Add support for theme family-specific syntax mapping overrides (#3551)
This PR adds support for adding a specific set of mappings from Zed
syntax tokens to VS Code scopes for a particular theme family.

We can use this as a fallback when we aren't otherwise able to rely on
the mappings in the theme importer, as sometimes it isn't possible to
make a specific enough matcher that works across all of the themes.

Release Notes:

- N/A
2023-12-07 23:37:49 -05:00
Marshall Bowers
5b96ffbbd1 Set various bar backgrounds in imported themes (#3550)
This PR updates the imported themes to set the background colors for the
various bars (title bar, toolbar, tab bar, status bar).

Release Notes:

- N/A
2023-12-07 23:07:24 -05:00
Marshall Bowers
efb4ff816a Set tab bar and toolbar backgrounds 2023-12-07 22:32:41 -05:00
Joseph T. Lyons
1f51f74670 Add TODO 2023-12-07 22:24:32 -05:00
Marshall Bowers
a88372dc99 Set background colors for title and status bars 2023-12-07 22:24:10 -05:00
Marshall Bowers
f272881a6b theme_importer: Improve syntax token matching (#3549)
This PR improves the approach we use to match syntax tokens between Zed
and VS Code in the `theme_importer`.

We now use the list of scopes assigned to each Zed syntax token to rank
the possible candidates in the VS Code and then pick the candidate with
the highest rank.

So far this has proved to provide better colors across the board, but
we'll continue to refine the matching over time.

Release Notes:

- N/A
2023-12-07 22:11:31 -05:00
Mikayla
25df11dd26 Begin porting the PaneAxis element 2023-12-07 17:16:19 -08:00
Max Brunsfeld
89f62a1a69 Fix nightly auto-update failure due to version parsing error (#3548)
After https://github.com/zed-industries/zed/pull/3507, parsing the app's
semver version from the application plist stopped working, which caused
auto-updates to stop.

I don't think we need to put `-nightly` in the version number: it's
already in the release channel name, which is reported alongside the
version in most cases. For example, the 'About' dialog was saying `Zed
Nightly 2.0.0-nightly`.

I think even without nightly, it would be a good idea to include the
*release channel* name in the datadog alerts that show up in slack.
@JosephTLyons could you look into how to do that?

/cc @SomeoneToIgnore
2023-12-07 17:15:41 -08:00
Max Brunsfeld
f9d569f1b8 Remove '-nightly' suffix from nightly build version number
It breaks our semver parsing, and the release channel is already 'nightly'.
2023-12-07 16:51:53 -08:00
Max Brunsfeld
808a0626c0 Show a notification on auto-update check action if updates are disabled 2023-12-07 16:49:27 -08:00
Marshall Bowers
c7e19c0bcd Emit accurate hex colors in generated themes (#3547)
This PR fixes an issues where the hex colors in the generated themes
were not correct.

We're using the [`palette`](https://crates.io/crates/palette) crate to
perform the conversions, as this gives us accurate hex codes that match
the VS Code source themes.

Release Notes:

- N/A
2023-12-07 18:34:03 -05:00
Max Brunsfeld
0b78a401de Show current user avatar when in a room, even if not in a shared project (#3546) 2023-12-07 15:22:29 -08:00
Max Brunsfeld
58d8c51255 Show current user avatar when in a room w/o a shared project 2023-12-07 14:49:32 -08:00
Max Brunsfeld
5e3d0a6d03 Add following UI in collab titlebar (#3544)
* [x] restructure titlebar to show facepiles for each collaborator and
their followers
* [x] allow following collaborators by clicking their avatars in the
titlebar
* [x] show grayscale avatar for collaborators not focused on this
project
* [x] show collaborators' microphone activity and muted status in the
titlebar
* [x] in facepile, show leader in front of followers
2023-12-07 14:38:30 -08:00
Max Brunsfeld
aa34637705 Add tooltip for following collaborators in titlebar 2023-12-07 14:31:21 -08:00
Max Brunsfeld
901bd6ac87 Order face pile front to back, left to right 2023-12-07 14:22:36 -08:00
Julia
445f0da4d8 zed2: Port ProjectSharedNotification (#3545)
Release Notes:

- N/A
2023-12-07 17:21:24 -05:00
Max Brunsfeld
d1b47b4059 Indicate collaborators' presence (grayscale), speaking and muted status 2023-12-07 14:18:34 -08:00
Julia
f3cbce7e86 zed2: Port ProjectSharedNotification 2023-12-07 17:13:22 -05:00
Max Brunsfeld
04d019ef66 Remove comments 2023-12-07 13:17:22 -08:00
Marshall Bowers
d23bba5b18 Pull hint colors from VS Code themes (#3543)
This PR updates the `theme_importer` to pull hint colors from VS Code
themes.

Release Notes:

- N/A
2023-12-07 16:11:56 -05:00
Max Brunsfeld
282e4398a0 In titlebar, render followers and allow following people 2023-12-07 13:09:18 -08:00
Marshall Bowers
fa223e0c6f Pull editor inlay hints from VS Code themes 2023-12-07 15:36:13 -05:00
Marshall Bowers
6a164db700 Rename members in ZedSyntaxToken 2023-12-07 14:35:58 -05:00
Marshall Bowers
8ed04fe798 Remove commented-out code in theme importer 2023-12-07 14:33:37 -05:00
Marshall Bowers
853daf953b Remove padding from ButtonSize::None (#3541)
This PR removes the padding from buttons when using `ButtonSize::None`.

This fixes the size of the tab close buttons.

Release Notes:

- N/A
2023-12-07 14:11:30 -05:00
Joseph T. Lyons
8b9b19195d Fix bug with how characters limits are being displayed 2023-12-07 13:19:03 -05:00
Joseph T. Lyons
5c3b8a1af1 Merge branch 'feedback-modal-ui' of https://github.com/zed-industries/zed into feedback-modal-ui 2023-12-07 13:00:59 -05:00
Joseph T. Lyons
0ee4ad6ba0 Skip using map 2023-12-07 13:00:49 -05:00
Nate Butler
ef4bc5e20b Remove static status bar icons 2023-12-07 12:59:51 -05:00
Joseph T. Lyons
439df24526 Merge branch 'main' into feedback-modal-ui 2023-12-07 12:58:53 -05:00
Joseph T. Lyons
1fcd006b2a Remove when_else (#3540)
Release Notes:

- N/A
2023-12-07 12:46:20 -05:00
Joseph T. Lyons
7c19650a40 Remove when_else
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-07 12:37:07 -05:00
Marshall Bowers
393be3cedf Extract Tab component (#3539)
This PR extracts a new `Tab` component from the tabs implementation in
the workspace.

This will allow us to reuse this component anywhere that we need to use
tabs.

Like our other newer components, the `Tab` component has a relatively
open API.

It accepts `children` (or `child`) as well as a `start_slot` and
`end_slot` to position content in the slots on either end of the
content. These slots also respect the `TabCloseSide` and will switch
positions based on this value.

<img width="763" alt="Screenshot 2023-12-07 at 12 19 42 PM"
src="https://github.com/zed-industries/zed/assets/1486634/78e4db6a-f807-431e-8777-f52f2631e6ed">

Release Notes:

- N/A
2023-12-07 12:30:43 -05:00
Antonio Scandurra
c91207b450 Don't panic when trying to reuse an existing workspace (#3538)
Release Notes:

- N/A
2023-12-07 18:23:33 +01:00
Antonio Scandurra
7ad6d489a4 Fix clipping when rendering paths (#3537)
Release Notes:

- N/A
2023-12-07 18:23:19 +01:00
Joseph T. Lyons
794b795800 Add TODO 2023-12-07 12:04:04 -05:00
Piotr Osiewicz
1bf94f0251 Do not render multibuffer without matches 2023-12-07 17:55:03 +01:00
Antonio Scandurra
2461902086 Don't panic when trying to reuse an existing workspace
Co-Authored-By: Nathan <nathan@zed.dev>
2023-12-07 17:53:07 +01:00
Piotr Osiewicz
e9c40963ab Render multibuffer 2023-12-07 17:48:53 +01:00
Antonio Scandurra
08c4e1abdc Fix clipping when rendering paths
Co-Authored-By: Nathan <nathan@zed.dev>
2023-12-07 17:48:00 +01:00
Nate Butler
5e5eb25aab WIP
[no-ci]
2023-12-07 10:50:07 -05:00
Piotr Osiewicz
ed5c05b272 Searches work, but the results are not displayed 2023-12-07 16:43:34 +01:00
Piotr Osiewicz
b04838c23a WIP, search bar looks kinda okay 2023-12-07 16:26:40 +01:00
Kirill Bulatov
49d30b4f67 Fix more bugs around terminal2 and focus (#3534) 2023-12-07 16:52:59 +02:00
Kirill Bulatov
b9a904e552 Add initial OS file drag and drop support 2023-12-07 16:08:48 +02:00
Kirill Bulatov
6c653b9c05 Activate Zed window on external file drop 2023-12-07 16:08:47 +02:00
Kirill Bulatov
b692b4c9c2 Do not expand terminal selections on active drag 2023-12-07 16:08:47 +02:00
Kirill Bulatov
f829120f51 Convert OS file drag and drop enter event into a mouse move, not mouse click 2023-12-07 16:08:47 +02:00
Kirill Bulatov
2a82dff2fe Suppress unused variable r-a warning from action derive procmacro 2023-12-07 16:08:47 +02:00
Kirill Bulatov
646b74e0a7 Move actions to namespaces used in zed1 keybinding settings 2023-12-07 16:08:47 +02:00
Kirill Bulatov
75324abbb5 Do not panic when focusing empty dock 2023-12-07 16:08:47 +02:00
Piotr Osiewicz
0c23e6738b Barebones project search (no UI, but the crate compiles) 2023-12-07 14:36:45 +01:00
Piotr Osiewicz
ea708c50f0 Search2 fixups (#3533)
Fixes several issues with focus and unbound actions.
Release Notes:

- N/A
2023-12-07 14:01:21 +01:00
Piotr Osiewicz
8839bfa1df Remove unused import 2023-12-07 13:54:51 +01:00
Antonio Scandurra
2c2c633104 Fix confirming rename not working on editor2 (#3532)
Release Notes:

- N/A
2023-12-07 13:20:00 +01:00
Piotr Osiewicz
18393d1fac fixup! buffer search: Fix up rough edges 2023-12-07 13:13:38 +01:00
Antonio Scandurra
7842fdeb32 Fix confirming rename not working on editor2 2023-12-07 12:54:28 +01:00
Piotr Osiewicz
45f7ab876f buffer search: Fix up rough edges
Focus query editor when Deploy::focused is true, add missing bindings
2023-12-07 12:52:34 +01:00
Antonio Scandurra
cbd1ce64f7 Rework frame rendering (#3531)
This pull request replaces the old `previous_frame` and `current_frame`
with a new pair of `rendered_frame` and `next_frame` that are swapped
once we are ready to draw a finished frame.

This makes it clearer when to use which: `rendered_frame` should be used
to query the existing state, whereas `next_frame` should be used and
mutated when redrawing a dirty window.

The change was prompted by a bug I encountered in
`FocusHandle::contains`. The implementation was reading `current_frame`,
but that was the wrong field to access if e.g. we were reading it inside
of a `render` function or any other time while drawing the window.

Release Notes:

- N/A
2023-12-07 12:36:06 +01:00
Antonio Scandurra
9a916d71b0 Polish assistant in zed2 (#3530)
This fixes a few bugs that remained after merging #3513 

Release Notes:

- N/A
2023-12-07 12:16:39 +01:00
Antonio Scandurra
a40515dbb3 Rework frame rendering
This commit replaces the old `previous_frame` and `current_frame` with
a new pair of `rendered_frame` and `next_frame` that are swapped once
we are ready to draw a finished frame.

This makes it clearer when to use which: `rendered_frame` should be used
to query the existing state, whereas `next_frame` should be used and mutated when
redrawing a dirty window.

The change was prompted by a bug I encountered in `FocusHandle::contains`. The
implementation was reading `current_frame`, but that was the wrong field to
access if e.g. we were reading it inside of a `render` function or any other
time while drawing the window.
2023-12-07 12:10:49 +01:00
Antonio Scandurra
edb2f60d9d Use the correct icons in AssistantPanel header 2023-12-07 10:53:24 +01:00
Antonio Scandurra
02bd4fb1f1 Simplify focus management in AssistantPanel 2023-12-07 10:38:00 +01:00
Antonio Scandurra
e96197d63b Ensure editor gets focused when selecting conversation 2023-12-07 10:27:25 +01:00
Federico Dionisi
d02ff42608 Dismiss theme_selector on esc (#3502) 2023-12-07 09:07:13 +01:00
Nate Butler
f798b193d0 WIP 2023-12-07 01:46:28 -05:00
Nate Butler
197f355729 Add row-reverse and col-reverse to styled 2023-12-07 01:17:18 -05:00
Nate Butler
7a1eb54106 checkpoint 2023-12-07 00:33:24 -05:00
Nate Butler
42a3da4ba3 Update pickers (#3529)
[[PR Description]]

- Update the size of all pickers
- Additional styling fixes for File Finder and Outline palettes
- Extend the ui prelude to include common imports

Release Notes:

- N/A
2023-12-07 00:24:46 -05:00
Nate Butler
84aaeb4360 Fix incorrect text style in outline palette, use background highlights for matches 2023-12-07 00:16:24 -05:00
Nate Butler
da3ba35d1c add color_alpha function 2023-12-07 00:15:43 -05:00
Joseph T. Lyons
2a68a6171a Remove remnants from conflict resolution (#3528)
Fixes a couple of things that slipped through when going through the
last merge conflict

Release Notes:

- N/A
2023-12-06 23:44:49 -05:00
Nate Butler
fffe4f51fb Update pickers to have a consistently larger size 2023-12-06 23:38:47 -05:00
Joseph T. Lyons
27e1787ce3 Feedback 2 (#3527)
Ports feedback crate over to zed2. Introduces modal feedback. Feedback
submission works, but there are some TODOs in the code for things that
need to be done (needs a UI pass, dismissing the modal in certain cases,
etc), but I might merge this to reduce chances of conflicts (aleady had
to deal with a few).

<img width="1378" alt="SCR-20231206-udgp"
src="https://github.com/zed-industries/zed/assets/19867440/99f9e843-ac9c-4df1-b600-2522863e6459">

Release Notes:

- N/A
2023-12-06 23:25:45 -05:00
Nate Butler
9e6103f863 Merge branch 'main' into update-pickers 2023-12-06 23:21:42 -05:00
Joseph T. Lyons
d2362d7f12 Merge branch 'main' into feedback-2 2023-12-06 23:16:54 -05:00
Joseph T. Lyons
79e0d8ce3b WIP 2023-12-06 23:15:04 -05:00
Joseph T. Lyons
ee45db8a78 Use when_else() 2023-12-06 22:34:28 -05:00
Joseph T. Lyons
8fc15c05c5 Introduce when_else() 2023-12-06 22:34:14 -05:00
Joseph T. Lyons
256f0308ae Renove unused code 2023-12-06 21:32:33 -05:00
Joseph T. Lyons
d62b151bc0 Improve character limit bounds indicator 2023-12-06 21:23:51 -05:00
Mikayla Maki
b2a0c32148 Terminal2 (#3449)
Release Notes:

-
2023-12-06 16:49:46 -08:00
Joseph T. Lyons
668f5accab Highlight whole modal as needing UI tweaks 2023-12-06 19:35:41 -05:00
Mikayla
66b93212fe Fix selection backgrounds 2023-12-06 16:31:28 -08:00
Joseph T. Lyons
57a9f024e7 Remove email address from local database if user removes it 2023-12-06 19:26:25 -05:00
Mikayla
22cd62213b Tear out drag event listener 2023-12-06 16:22:25 -08:00
Joseph T. Lyons
bbe9986f38 Start work to add a confirmation when exiting feedback containing text 2023-12-06 19:13:38 -05:00
Joseph T. Lyons
1abc9800af Switch to if let statement 2023-12-06 19:11:24 -05:00
Joseph T. Lyons
034c11cc96 Hook up community repo button 2023-12-06 19:11:03 -05:00
Joseph T. Lyons
0885fa67fc Hook up submit 2023-12-06 19:10:45 -05:00
Marshall Bowers
c8cb1140b9 Clean up how we open the recent projects picker (#3526)
This PR performs some light cleanup of how we open the recent projects
picker, to bring it more in-line with our other picker code.

Release Notes:

- N/A
2023-12-06 19:10:15 -05:00
Mikayla
c092cfbfb3 Fix bug with IME
Adjust how IME works in the terminal

co-authored-by: nathan <nathan@zed.dev>
2023-12-06 15:54:08 -08:00
Marshall Bowers
a4b271e063 Port recent_projects to Zed2 (#3525)
This PR ports the `recent_projects` crate to Zed2 (`recent_projects2`).

Absent from this PR is wiring up the "Recent Projects" item in the title
bar. We'll come back to that soon.

Release Notes:

- N/A
2023-12-06 18:41:50 -05:00
Joseph T. Lyons
110612bf26 Reduce amount of state being stored 2023-12-06 18:36:17 -05:00
Mikayla
fd31e429f5 Merge branch 'main' into terminal-element 2023-12-06 15:20:04 -08:00
Mikayla
12e7f61f62 Fix a porting bugs for terminal2
co-authored-by: Nathan <nathan@zed.dev>
2023-12-06 15:11:49 -08:00
Max Brunsfeld
1a747c8457 Upgrade async-compression dep (#3524)
Fixes a build failure on CI:


https://github.com/zed-industries/zed/actions/runs/7120057787/job/19387718091

```
2023-12-06 21:26:40.468791 +00:00:00 [ERROR] `cargo metadata` exited with an error:     Updating crates.io index
error: failed to select a version for `async-compression`.
    ... required by package `zed2 v2.0.0-nightly (/Users/administrator/actions-runner-2/_work/zed/zed/crates/zed2)`
versions that meet the requirements `^0.3` (locked to 0.3.15) are: 0.3.15

the package `zed2` depends on `async-compression`, with features: `futures-bufread-nightly` but `async-compression` does not have these features.
```
2023-12-06 14:45:28 -08:00
Max Brunsfeld
2d18b949ad Upgrade async-compression dep 2023-12-06 14:29:09 -08:00
Joseph T. Lyons
46c998ca8d WIP 2023-12-06 17:27:10 -05:00
Max Brunsfeld
a8d96790cf Enable buffer font size adjustment in zed2 (#3523) 2023-12-06 14:15:25 -08:00
Marshall Bowers
b72c54fc31 Don't load the Vim keymap temporarily (#3522)
This PR removes the loading of the Vim keymap temporarily.

This cuts down on the noise from all of the Vim-related action warnings.

We can resume loading the Vim keymap once we're ready to add Vim
support.

Release Notes:

- N/A
2023-12-06 16:59:24 -05:00
Max Brunsfeld
89c8a7c242 Enable buffer font size adjustment in zed2
Co-authored-by: Nathan <nathan@zed.dev>
2023-12-06 13:52:33 -08:00
Marshall Bowers
41fc30f62e Always show tooltips on buttons (#3521)
This PR fixes an issue where tooltips weren't being shown on selected
buttons.

We now always show tooltips on buttons that have one.

Release Notes:

- N/A
2023-12-06 16:29:35 -05:00
Joseph T. Lyons
f4c7b13397 temp 2023-12-06 16:15:44 -05:00
Marshall Bowers
f7c995c4a0 Add "Toggle Inlay Hints" quick action (#3520)
This PR adds the "Toggle Inlay Hints" quick action to the toolbar.

Release Notes:

- N/A
2023-12-06 16:14:39 -05:00
Marshall Bowers
cd818f580c Wire up inline assist quick action (#3519)
This PR wires up the inline assist quick action in the toolbar.

Release Notes:

- N/A
2023-12-06 15:52:51 -05:00
Max Brunsfeld
dba94c5122 Bring back channel notes (#3506) 2023-12-06 12:45:46 -08:00
Marshall Bowers
dbb501d7eb Add gap between label and keybinding in Tooltip 2023-12-06 15:45:03 -05:00
Marshall Bowers
d711087529 Wire up inline assist quick action 2023-12-06 15:44:50 -05:00
Kirill Bulatov
ebddb612c8 Do not run the same workflow concurrently on non-main branches (#3518)
<img width="1376" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/681cca85-e1b9-4a99-8363-60c931ba1393">

For any push (or force-push) into a branch, a separate CI workflow is
run.
This is rather worker-consuming, esp. given the fact that GitHub will
wait for the last job to update PR's status.
So cancel every old job for the same branch if it's not `main`.
CI run on `main` might catch a regression brought in by specific PR
merged, so run CI on every commit due to that.


Release Notes:

- N/A
2023-12-06 22:35:02 +02:00
Kirill Bulatov
62c41e226b Use distinct version for zed2, append git hash to its nightly version (#3507)
Current panic reports are now harder to tell apart by the following
criteria:
* nightly or preview or stable
* zed2 or zed1
 

![image](https://github.com/zed-industries/zed/assets/2690773/d12c8272-9f78-403c-ba49-e5f05982c0f5)

Current PR adds uses a different version for zed2 (2.0.0, selected
relatively arbitrary as zed2 is nothing officially released now) and
adds a `-nightly` suffix to the version number of the nightly bundle.

Release Notes:

- N/A
2023-12-06 22:23:12 +02:00
Kirill Bulatov
b94c335605 Do not run the same workflow concurrently on non-main branches 2023-12-06 22:19:48 +02:00
Max Brunsfeld
6bbb1642b8 Fix propagation of active item to followers
Enable channel buffer integration tests.
2023-12-06 12:18:48 -08:00
Max Brunsfeld
e9dcca7712 Initialize channel notes to register the view as followable 2023-12-06 12:09:29 -08:00
Max Brunsfeld
1ec81e02da Allow opening channel notes from the channel list 2023-12-06 12:09:29 -08:00
Max Brunsfeld
bcdefb8ec8 Bring back channel notes 2023-12-06 12:09:27 -08:00
Kirill Bulatov
5644815c4c Use a better name for zed2 inlay style field 2023-12-06 22:06:30 +02:00
Kirill Bulatov
9e1d797445 Use distinct version for zed2, append git hash to its nightly version 2023-12-06 22:04:45 +02:00
Kirill Bulatov
ec798e6574 Pass proper theme colors for inlays and suggestions (#3517)
<img width="1728" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/f6f1e24f-71fe-4a2e-9bcf-9e98861c0da4">


![image](https://github.com/zed-industries/zed/assets/2690773/8434d8a1-ee7e-49c1-9e88-362363225d96)

Now inlays are visible in all zed2 themes, but 
* always have a blue color which is the same as some theme colors
* not other hint-related style changes like font width, background, etc.
seem to exist in the theme, ergo not propagated.

In general though, people want those style changes applied to their
hints, so we might want to do something about it later.
I've left a `// todo!("what about the rest of the highlight style parts
for inlays and suggestions?")` in the corresponding places for that.

Release Notes:

- N/A
2023-12-06 21:54:41 +02:00
Nathan Sobo
d7473ad6e7 Document geometry module and replace zero method with default (#3515)
Nothing earth-shattering here, but all our geometry types are now fully
documented.

Release Notes:

- N/A
2023-12-06 12:52:41 -07:00
Max Brunsfeld
5ebf1d9e4f Put ToggleZoom key binding back into the block with no context (#3516)
Fixes a regression in zed1 from
https://github.com/zed-industries/zed/pull/3491
2023-12-06 11:52:24 -08:00
Kirill Bulatov
f76e1cfd91 Pass proper theme colors for inlays and suggestions 2023-12-06 21:48:39 +02:00
Max Brunsfeld
489c25ac6a Put ToggleZoom key binding back into the block with no context 2023-12-06 11:32:05 -08:00
Nathan Sobo
ac07e230fa Document geometry 2023-12-06 12:28:44 -07:00
Marshall Bowers
147c99f1a7 Fix layout for terminal tabs (#3514)
This PR fixes the layout for terminal tabs.

We need to use an `h_stack` here to get the icon and the label to
position themselves next to each other instead of one on top of the
other.

Release Notes:

- N/A
2023-12-06 14:28:31 -05:00
Joseph T. Lyons
02a6a2e1a3 Revert "Delete feedback_modal.rs"
This reverts commit eff925cb6a.
2023-12-06 14:13:56 -05:00
Joseph T. Lyons
4863c9ac25 WIP 2023-12-06 14:13:44 -05:00
Marshall Bowers
2c350a71fe Port assistant crate to Zed2 (#3513)
This PR contains the initial port of the `assistant` crate to Zed2.

Release Notes:

- N/A
2023-12-06 14:04:01 -05:00
Marshall Bowers
fdd64832e7 Update inline assist styles 2023-12-06 13:52:38 -05:00
Joseph T. Lyons
eff925cb6a Delete feedback_modal.rs 2023-12-06 13:26:20 -05:00
Antonio Scandurra
7e2ff63270 Paint blocks on top of the editor
This ensures blocks get mouse events before the editor beneath
them.

Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-06 19:15:09 +01:00
Antonio Scandurra
cc9eff89f5 Use a handler instead of an action for clicks
This prevents dispatching actions on buttons that were not the target of the click.

Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-06 19:12:01 +01:00
Antonio Scandurra
e4884f1d76 Move assistant actions to the top of the crate
Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-06 18:39:50 +01:00
Joseph T. Lyons
02e7a06cbb Merge branch 'main' of https://github.com/zed-industries/zed 2023-12-06 12:37:20 -05:00
Joseph T. Lyons
5ae2060783 collab 0.30.0 2023-12-06 12:34:47 -05:00
Antonio Scandurra
14def2a1a3 Merge remote-tracking branch 'origin/main' into assistant-2
# Conflicts:
#	crates/ui2/src/components/icon.rs
2023-12-06 18:18:58 +01:00
Antonio Scandurra
7c9e2f6b7d Introduce app menus in zed2 (#3511)
Release Notes:

- N/A
2023-12-06 18:17:02 +01:00
Marshall Bowers
f6a7a6c4d4 v0.117.x dev 2023-12-06 12:03:59 -05:00
Antonio Scandurra
2aee3e3192 Make Node::context optional as well
This was an oversight in d09dfe0.

Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-06 18:02:45 +01:00
Antonio Scandurra
5e558e2a58 Make more menu-related platform methods no-ops 2023-12-06 17:57:18 +01:00
Antonio Scandurra
80f315106d Add key context to ConversationEditor
Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-06 17:56:05 +01:00
Marshall Bowers
80c8fd1f4c Fix toolbar not appearing for initial pane (#3512)
This PR fixes an issues where the toolbar would not appear for the
center pane when Zed2 initially loads.

We resolved this by adding a call to initialize the center pane when the
workspace is initialized

Due to changes in the way subscriptions work we can on longer observe an
event that is emitted in the same event cycle in which the subscription
is created.

Because of this we need to explicitly initialize the center pane, as it
won't get performed by the subscription.

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2023-12-06 11:54:59 -05:00
Antonio Scandurra
886ec79d58 Make TestPlatform::set_menus a no-op 2023-12-06 17:45:59 +01:00
Antonio Scandurra
c8ddc95caa Take a Keymap when setting app menus
For a brief period on this branch, we were taking a `DispatchTree`. Doing so
resulted in more accurate key bindings but it meant that we would have had to
recompute the app menus every time the key context changed.

We decided to err on the side of keeping things simple and work in the same
way they worked back in zed1.

Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-06 17:26:54 +01:00
Marshall Bowers
8f1c74b8bc Factor out LabelLike to share common label styles (#3510)
This PR factors out a new `LabelLike` component to share common styles
between the `Label` and `HighlightedLabel` components.

Release Notes:

- N/A
2023-12-06 11:17:12 -05:00
Nate Butler
0ced310f93 Merge branch 'main' into update-pickers 2023-12-06 11:10:30 -05:00
Nate Butler
74e3b12a26 Update file finder style 2023-12-06 11:10:27 -05:00
Nate Butler
cf971f706c Extend ui prelude 2023-12-06 11:07:46 -05:00
Nate Butler
b5c790ed29 WIP update picker style 2023-12-06 11:01:22 -05:00
Piotr Osiewicz
6549a9a091 Let WindowContext::dispatch_action handle global actions
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-06 16:52:52 +01:00
Nate Butler
3b8c566f31 Merge branch 'main' into update-pickers 2023-12-06 10:48:50 -05:00
Marshall Bowers
f833cd7c16 Use specified color for non-highlighted text in HighlightedLabel (#3509)
This PR fixes an issue where the specified color for a
`HighlightedLabel` was not respected as the default color for
non-highlighted text.

Release Notes:

- N/A
2023-12-06 10:41:49 -05:00
Piotr Osiewicz
e9002ab10a Merge branch 'main' into app-menus 2023-12-06 16:34:55 +01:00
Antonio Scandurra
a1c8f01ff3 WIP 2023-12-06 16:34:07 +01:00
Antonio Scandurra
54ea7257ff Copilot2 (#3498)
Add hidden_action_types to CommandPaletteFilter.
WindowContext.available_actions now returns global actions as well.

Release Notes:

- N/A
2023-12-06 16:31:30 +01:00
Piotr Osiewicz
d09dfe01f5 Wire up global actions
Added an ephemeral root node so that even if there's no window/focused handle we still have something to dispatch to.

Co-authored-by: Antonio <antonio@zed.dev>
2023-12-06 16:15:53 +01:00
Kirill Bulatov
3c1bf0260c Disable bundling for main commits (#3508)
(in the first commit, this PR autoformats both yaml files with Zed's
default prettier, to be able to edit those in prettier from now on)

Bundling is a relatively long procedure, and now we have nightly builds
for zed2 (with their own lifecycle, CI file and tag for triggering it
from non-main branch).

Hence, bundling zed1 and/or zed2 by default looks wasteful and
unnecessary, disable them by default.
There's still a `run-build-dmg` label that enables bundling for any PR
needed, and a `startsWith(github.ref, 'refs/tags/v')` check in the CI
run to keep the releases working.

Release Notes:

- N/A
2023-12-06 17:08:48 +02:00
Nate Butler
cb0880cf3c Add debugging colors to styled_ext 2023-12-06 10:08:26 -05:00
Kirill Bulatov
a58f393458 Do not bundle Zed on main branch commits 2023-12-06 16:58:49 +02:00
Kirill Bulatov
0ef97edd6e Format the CI file with Zed's default prettier 2023-12-06 16:58:18 +02:00
Piotr Osiewicz
1f538c5fdd Merge branch 'main' into copilot2 2023-12-06 15:13:13 +01:00
Antonio Scandurra
5f172a52a4 Load assistant panel 2023-12-06 14:23:05 +01:00
Piotr Osiewicz
1b0ec82caa Remove old UI code, remove dbg! 2023-12-06 14:16:19 +01:00
Piotr Osiewicz
7998e8281c Barebones Copilot prompt
Filter out sign in/sign out when user is signed in/not signed in
2023-12-06 14:14:18 +01:00
Piotr Osiewicz
3f9fe58c48 Signed out state is looking good 2023-12-06 13:00:57 +01:00
Antonio Scandurra
9eb98122ec Merge remote-tracking branch 'origin/main' into assistant-2 2023-12-06 12:53:02 +01:00
Antonio Scandurra
1de02cf6e5 Start wiring up assistant2 2023-12-06 12:51:25 +01:00
Antonio Scandurra
b29cea287b WIP 2023-12-06 12:12:28 +01:00
Mikayla
735f2029e9 Add more debugging 2023-12-05 17:31:33 -08:00
Marshall Bowers
5660c8f655 Add missing semicolon 2023-12-05 20:24:43 -05:00
Marshall Bowers
ed31d36ac1 Wire up NewWindow action 2023-12-05 20:24:01 -05:00
Marshall Bowers
d8757845a9 Wire up NewFile action 2023-12-05 20:21:35 -05:00
Mikayla
2ee0ecb677 Add back the main structure 2023-12-05 16:52:29 -08:00
Nathan Sobo
d2fe9f8f9b Merge remote-tracking branch 'origin/main' into app-menus 2023-12-05 16:54:38 -07:00
Nathan Sobo
c9dd6b879c Fix cmd-n in the project panel context menu and bind global workspace actions (#3491)
This PR binds the `workspace::NewFile` action only in the `Workspace`
context. It previously was bound globally, which caused us to hijack the
`cmd-n` binding when the context menu was visible in the project panel.
In the process, we also added some other global workspace actions and
move some other global bindings to be bound only on `Pane` and
`Workspace`.

Release Notes:

- N/A
2023-12-05 16:52:39 -07:00
Nathan Sobo
65bb05af4c Merge main
Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-05 16:47:13 -07:00
Nathan Sobo
82534b6612 Get app menus basically working
- Everything is still disabled when there is no active window.

Co-Authored-By: Marshall <marshall@zed.dev>
2023-12-05 16:37:01 -07:00
Nathan Sobo
79567d1c87 Add AppContext::dispatch_action and use it for app menu actions
Co-Authored-By: Marshall <marshall@zed.dev>
Co-Authored-By: Julia <julia@zed.dev>
2023-12-05 15:49:06 -07:00
Max Brunsfeld
5b6fd3ac1d Implement following and more collaboration panel features in zed2 (#3504) 2023-12-05 13:59:15 -08:00
Joseph T. Lyons
a27db35ff7 temp 2023-12-05 16:37:43 -05:00
Marshall Bowers
02e507b973 Update breadcrumb rendering (#3505)
This PR updates the rendering of the breadcrumb.

Release Notes:

- N/A

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
2023-12-05 16:34:21 -05:00
Max Brunsfeld
f2faa70f73 Make Window::on_next_frame work in tests 2023-12-05 13:34:12 -08:00
Max Brunsfeld
863222edc5 Get following working
Restore a single event type on Item trait, so that the workspace can
subscribe to it and handle following events.
2023-12-05 12:57:23 -08:00
Joseph T. Lyons
fdc2d7ce42 Remove unused code 2023-12-05 15:53:52 -05:00
Joseph T. Lyons
dec4cfa0b4 Implement feedback action
OpenZedCommunityRepo
2023-12-05 15:52:02 -05:00
Joseph T. Lyons
78e1c0f9c3 Implement feedback actions
CopySystemSpecsIntoClipboard
RequestFeature
FileBugReport
2023-12-05 15:48:41 -05:00
Nathan Sobo
631e264e3c Start on app menus 2023-12-05 13:17:59 -07:00
Mikayla
ab140ee4c2 Add event based drag API to GPUI, continue binding mouse handlers to terminal 2023-12-05 12:07:17 -08:00
Marshall Bowers
fc9b662042 Update Tab Bar & Toolbar (#3503)
- Work on default light theme
- Update tab bar and tabs
- Port quick_action_bar crate to zed2
- Add `Indicator` component
- Add `v_stack` & `h_stack` to ui::prelude::*

Release Notes:

- N/A
2023-12-05 14:23:13 -05:00
Marshall Bowers
8141f4fd86 Format code 2023-12-05 14:17:22 -05:00
Marshall Bowers
e0ca7f844a Merge branch 'main' into pane-toolbar-tabbar-ui 2023-12-05 14:15:18 -05:00
Nate Butler
4c4b235b13 make ci happy
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-05 14:09:42 -05:00
Nate Butler
dc7e4a4b17 Remove debugging colors
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-05 14:09:29 -05:00
Nate Butler
27703a3279 Update tabs rendering, fix tab spacing bug
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-05 14:04:12 -05:00
Max Brunsfeld
7b4b068230 Render chat and notes buttons below the current channel 2023-12-05 10:40:51 -08:00
Federico Dionisi
ceb5d0c69e Dismiss theme_selector on esc 2023-12-05 19:31:20 +01:00
Max Brunsfeld
38d41acf9b Fix rendering of shared screens in collab panel 2023-12-05 10:29:19 -08:00
Antonio Scandurra
d86da04584 WIP 2023-12-05 19:27:15 +01:00
Max Brunsfeld
5e79807f6f Fix tree branch rendering in collab panel 2023-12-05 10:14:40 -08:00
Joseph T. Lyons
a80b145d0d temp 2023-12-05 13:04:53 -05:00
Joseph T. Lyons
ce8bdb354b Merge branch 'main' into feedback-2 2023-12-05 12:49:48 -05:00
Nate Butler
dccdcd3221 Add indicator component
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-05 12:41:54 -05:00
Mikayla
8d57d6ca6f Merge branch 'main' into terminal-element 2023-12-05 09:33:46 -08:00
Antonio Scandurra
e534c5fdcd WIP 2023-12-05 18:14:24 +01:00
Nate Butler
eed5a698cf Update tab close button 2023-12-05 11:59:23 -05:00
Piotr Osiewicz
412c6157b1 Port quick_action_bar to zed2
Co-authored-by: Nate <nate@zed.dev>
2023-12-05 17:33:35 +01:00
Nathan Sobo
dffe0ea058 Reintroduce menu-related platform callbacks 2023-12-05 09:23:24 -07:00
Marshall Bowers
2299730538 Fix an issue with the text in the theme selector editor not showing (#3501)
This PR fixes an issues where the text—both placeholder and
user-entered—would not show up in the editor in the theme selector.

It seems to be the min width on the wrapper element that fixes this.

Release Notes:

- N/A
2023-12-05 10:59:15 -05:00
Antonio Scandurra
ede86d9187 WIP 2023-12-05 16:49:36 +01:00
Piotr Osiewicz
c9b50c8bab Add v_stack and h_stack to ui::prelude 2023-12-05 16:48:17 +01:00
Nate Butler
7c5df51d2e Update button sizes 2023-12-05 10:11:18 -05:00
Antonio Scandurra
30725d6a17 Port semantic_index to gpui2 (#3500)
Release Notes:

- N/A
2023-12-05 15:52:38 +01:00
Antonio Scandurra
09db455db2 Port semantic_index to gpui2
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-12-05 15:38:36 +01:00
Kirill Bulatov
c2d3e74ffb Allow opening files without project entries (#3497)
Addresses
https://zed-industries.slack.com/archives/C04S5TU0RSN/p1701428432093989
by allowing to open a buffer without a corresponding project `Entry`.
This means that now, we can remove the "fake worktree" requirement and
open files without creating any (the PR does not do this step yet, but
prepares the code for it).

In addition, the PR fixes glob matching code and adds more tests on
excluded files queries.

Release Notes:

- Fixed search query inclusions/exclusions working incorrectly
- Fixed excluded files being opened via CLI and failing or spoiling
project tree view
2023-12-05 16:33:30 +02:00
Federico Dionisi
62bfa3b452 Fix panic opening the theme selector (#3499)
I noticed a panic when trying to open the theme selector. Removing `Arc`
from `cx.global::<Arc<ThemeRegistry>>()` did the job. Is that the right
way? I'm not sure. 😅

Release Notes:

- N/A
2023-12-05 15:28:48 +01:00
Kirill Bulatov
16b5d4b35c Port to gpui2 2023-12-05 16:13:39 +02:00
Federico Dionisi
20352c51c1 Fix panic opening the theme selector 2023-12-05 15:12:37 +01:00
Nate Butler
f9efaebddf Update icon size 2023-12-05 09:10:16 -05:00
Nate Butler
af72772a72 Expand toolbar tools 2023-12-05 09:02:21 -05:00
Piotr Osiewicz
b73ccc8180 Start out Copilot2;
Add hidden_action_types to CommandPaletteFilter.
WindowContext.available_actions now returns global actions as well.

Co-authored-by: Antonio <antonio@zed.dev>
2023-12-05 14:57:20 +01:00
Piotr Osiewicz
d433da1e70 Editor2 tests (#3486)
Release Notes:

- N/A
2023-12-05 14:52:20 +01:00
Piotr Osiewicz
001ce47a0c Fix up the inlay_hint_cache proper (document the bug around inserting at inlay hint).
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Kirill <kirill@zed.dev>
2023-12-05 14:10:10 +01:00
Kirill Bulatov
e5616bce98 Fix the test 2023-12-05 12:45:44 +02:00
Kirill Bulatov
92fbdb429c Add project search in gitignored test 2023-12-05 12:45:44 +02:00
Kirill Bulatov
b43dc480a9 Integration tests for excluded files 2023-12-05 12:45:44 +02:00
Kirill Bulatov
2c2c14a360 Fix the regex matcher 2023-12-05 12:45:36 +02:00
Piotr Osiewicz
dffe323e73 Fix up test_lkayout_with_placeholder_text_and_blocks (uncomment last standing assert) 2023-12-05 11:31:17 +01:00
Kirill Bulatov
f0ca7141b8 Ignore excluded files on worktree entry refresh 2023-12-05 12:30:29 +02:00
Kirill Bulatov
1f6c69c7dc Allow opening buffers without a project entry 2023-12-05 12:30:29 +02:00
Piotr Osiewicz
590238bcca test_layout_with_placeholder_text_and_blocks (incomplete, one assert commented out)
We need to wire up a field from element state
2023-12-05 11:28:25 +01:00
Piotr Osiewicz
caa5fccbc4 test_wrapped_invisibles_drawing and test_invisibles_dont_appear_in_certain_editors 2023-12-05 11:11:06 +01:00
Piotr Osiewicz
1dd6625dd4 test_all_invisibles_drawing 2023-12-05 11:09:23 +01:00
Piotr Osiewicz
ee695bbb34 Fix up test_vim_visual_selections 2023-12-05 10:59:09 +01:00
Piotr Osiewicz
9695ea1017 test_shape_line_numbers 2023-12-05 10:29:09 +01:00
Piotr Osiewicz
e0ec5032e9 Fix highlight tests (and a quirky behaviour where the highlights were not dismissed when user clicks on something that's not a brace) 2023-12-05 10:17:27 +01:00
Piotr Osiewicz
948c065f86 test_copilot_multibuffer 2023-12-05 10:17:27 +01:00
Max Brunsfeld
ae6ddceb67 Enable more collab UI features (#3496)
* Current Call section of the collab panel
* Improve the collab titlebar
* Add basic UI for following

Following only partially works, but the UI for following is now in
place.
2023-12-04 18:01:11 -08:00
Max Brunsfeld
eff3a72fb5 Start work on following in zed2
Co-authored-by: Nathan <nathan@zed.dev>
2023-12-04 17:51:53 -08:00
Nate Butler
591dc9d82a Remove double first item border in tabs 2023-12-04 20:13:52 -05:00
Max Brunsfeld
71a1125e88 Allow joining remote projects in zed2
Co-authored-by: Nathan <nathan@zed.dev>
2023-12-04 17:06:08 -08:00
Max Brunsfeld
959b2961ff Revert "Decouple workspace from call (#3380)"
This reverts commit 6da57cbc6e, reversing
changes made to 62b1843704.

Also, adjust new code that was written using the "call handler".
2023-12-04 17:05:44 -08:00
Conrad Irwin
45992b0d63 Fix project panel context menu (#3495)
Release Notes:

- N/A
2023-12-05 00:51:07 +00:00
Conrad Irwin
9162f299a7 Fix project panel context menu 2023-12-05 00:41:00 +00:00
Max Brunsfeld
2c5603032d Allow sharing projects
Co-authored-by: Nathan <nathan@zed.dev>
2023-12-04 15:46:56 -08:00
Max Brunsfeld
63667ecf6f Start bringing back the current call section of the collab panel
Co-authored-by: Nathan <nathan@zed.dev>
2023-12-04 15:46:56 -08:00
Conrad Irwin
13bb16577c action dispatch target (#3494)
- Ensure the candidate keybinding matches the correct context
- Fix context key matching
- I was soooo close
- Dispatch actions on focused node

[[PR Description]]

Release Notes:

- (Added|Fixed|Improved) ...
([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
2023-12-04 23:43:26 +00:00
Conrad Irwin
1c9b984738 Remove cx param 2023-12-04 23:35:31 +00:00
Nate Butler
7db0a9e105 Draw border under tabs 2023-12-04 18:21:50 -05:00
Nate Butler
fc16e4509a Fix double border 2023-12-04 18:13:28 -05:00
Conrad Irwin
c82fea375d Dispatch actions on focused node
Allows us to implement context menu matching nicely
2023-12-04 22:58:04 +00:00
Nate Butler
5bdaf0e074 Work on light theme, update tab 2023-12-04 17:54:37 -05:00
Marshall Bowers
c10d8a8110 Fix cursor styles not displaying properly (#3493)
This PR fixes an issue where an element with a cursor style set would
not update the cursor when hovering over it.

Previously the cursor style would only appear by interacting with the
element in some way, for instance, by clicking on the element or by
having a `.hover` with some other style being applied.

Release Notes:

- N/A
2023-12-04 16:40:53 -05:00
Conrad Irwin
79773178c8 I was soooo close 2023-12-04 21:37:47 +00:00
Conrad Irwin
2c2e5144c9 Fix context key matching
* You need to check all layers of the context stack
* When in command, the context should be based on where focus was (to
  match `available_actions`.
2023-12-04 21:28:37 +00:00
Julia
059959a4ab Zed2 outline view (#3490)
Release Notes:

- N/A
2023-12-04 15:56:53 -05:00
Marshall Bowers
3627ff87f0 Ensure the candidate keybinding matches the correct context 2023-12-04 15:53:38 -05:00
Julia
9a2fd18425 Avoid panic with outline highlight ranges
Previously we were using a function in `editor` to combine syntax
highlighting and fuzzy match positions, it would operate on the full
text as put into the label. However we now have a method `ranges` on
`StringMatch` itself which operates on just the match text.

The outline view has some pretty specific behavior around path/normal
matches and how they are highlighted. So let's just give the match the
full text before it leaves the search function so it can freely index
2023-12-04 15:37:34 -05:00
Marshall Bowers
4cb4033a36 Merge branch 'main' into editor2_tests 2023-12-04 14:09:23 -05:00
Marshall Bowers
b212aab00d Add support for copying diagnostic messages to the clipboard (#3489)
This PR adds support for copying diagnostics messages to the clipboard.

This was already working, but we were missing implementations
clipboard-related methods in the `TestPlatform` that were causing the
tests to fail when the copying functionality was added.

Release Notes:

- N/A
2023-12-04 14:08:05 -05:00
Nate Butler
584a3a7627 Add temp nightly icon (#3488)
[[PR Description]]

This will get updated – I need to get the template derek used for the
new preview icon. Sorry for the jank in advance.

Release Notes:

- N/A
2023-12-04 12:57:22 -05:00
Nate Butler
63213b5a57 Add temp nightly icon 2023-12-04 12:48:21 -05:00
Marshall Bowers
4a5f703c32 Diagnostics style 2 (#3483)
[[PR Description]]

Merge past diagnostic multibuffer style work + some extras

Release Notes:

- N/A
2023-12-04 12:02:14 -05:00
Piotr Osiewicz
a3e4559b0e Fix git test compilation 2023-12-04 17:57:43 +01:00
Marshall Bowers
26c797c358 Format code 2023-12-04 11:57:10 -05:00
Marshall Bowers
fb352af35c Merge branch 'main' into diagnostics-style-2 2023-12-04 11:56:55 -05:00
Nate Butler
4f4029524f Style Grab Bag (#3487)
[[PR Description]]

- Add pane empty state
- Ensure tab bar doesn't resize when a tab is added
- Make ButtonLike respect the style of a disabled button
- Add additional cursors to gpui2

Release Notes:

- N/A
2023-12-04 11:55:26 -05:00
Nate Butler
e928ed44a0 Mark copy button as incomplete
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:53:46 -05:00
Nate Butler
23626aa902 Update diagnostic style
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:50:26 -05:00
Julia
5ab6874ae9 zed2: Port outline view 2023-12-04 11:44:32 -05:00
Nate Butler
4ee4e4e8d8 Fix ci error – Copy to clipboard isn't implemented in tests
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:41:31 -05:00
Nate Butler
7a0aa1e5df Clean up ButtonLike
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:32:25 -05:00
Nate Butler
b6ed3b258c Remove debug
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:32:14 -05:00
Nate Butler
50fe683305 Fix typos
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:28:22 -05:00
Piotr Osiewicz
b5924d6b11 Add simulate_window_resize.
Fixes up tests for movement in editor/scrolling.

Co-authored-by: Antonio <antonio@zed.dev>
2023-12-04 17:27:48 +01:00
Nate Butler
80ae640060 Add additional cursors to gpui2
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-04 11:20:41 -05:00
Nate Butler
0af0c5549c Update toolbar left tool group 2023-12-04 11:14:59 -05:00
Piotr Osiewicz
68d309e79c Fix disparity between editor2 and edito1 wrt copilot completions.
Fixes test test_copilot.

Co-authored-by: Antonio <antonio@zed.dev>
2023-12-04 16:39:40 +01:00
Piotr Osiewicz
fd2f1c2594 Fix up copilot2 test
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-04 16:26:34 +01:00
Piotr Osiewicz
2108ddf621 Defer activating Subscriptions that are invoked as a part of an effect.
Fixes test test_edit_events.

Co-authored-by: Antonio <antonio@zed.dev>
2023-12-04 16:17:33 +01:00
Julia
ee23fa43dc Disable instance handshake in dev builds (#3485)
It feels like every week or two someone hits this and is confused about
why the app isn't launch, been meaning to disable this in dev builds for
a while. This does mean that it'll be possible to hit the local DB lock
panic in a dev build if you run it multiple times but that is so rare
that it seems preferable

Release Notes:

- N/A
2023-12-04 09:43:24 -05:00
Nate Butler
118c9b5fe8 Ensure disabled buttons don't have interaction styles 2023-12-04 09:37:56 -05:00
Piotr Osiewicz
b9a917f42a Fix up condition not checking the condition at the start.
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-04 15:31:35 +01:00
Nate Butler
7dc22fef24 Prevent tab bar from growing in height when a tab is added 2023-12-04 09:24:08 -05:00
Piotr Osiewicz
24b08921fe Revert "fix display map tests"
This reverts commit 0f7fc8c1a0.
2023-12-04 15:22:29 +01:00
Julia
dfe4fc4d0a Disable instance handshake in dev builds 2023-12-04 09:16:04 -05:00
Nate Butler
11c16258eb Update empty pane state 2023-12-04 09:13:00 -05:00
Piotr Osiewicz
ff734d494f uncomment and augment mouse_context_menu tests 2023-12-04 15:06:56 +01:00
Piotr Osiewicz
3b1a0652ae inlay hints: Relax the test condition.
We've investigated another spurious failure, this time with test_multiple_excerpts_large_multibuffer; sadly it didn't really get us anywhere, so for now we're relaxing an assert.

Co-authored-by: Kirill <kirill@zed.dev>
2023-12-04 14:50:10 +01:00
Piotr Osiewicz
b3e741b397 Fix up test_highlighted_ranges
Returned highlights were okay, but the test was trying to normalize the output by sorting the highlights by color. The ordering is different between gpui1 Color and gpui2 Hsla.
2023-12-04 13:47:50 +01:00
Piotr Osiewicz
1a5f6f604b Uncomment & fix up test_transpose 2023-12-04 13:42:48 +01:00
Piotr Osiewicz
0f7fc8c1a0 fix display map tests
These tests failed due to an indefinite hang in buffer.condition in the following code:
\`\`\`rust
    let buffer = cx
        .add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
    buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
`\`\`
In both gpui1 and gpui2 \`.with_language\` spawns a task that notifies the context once it's done. The \`condition\` waits for notifications to be raised. The gist of the problem was that in gpui2, the spawned task was scheduled straight away, so we never really saw the notification with \`condition\`, causing us to wait indefinitely. This is probably a difference in test between schedulers in gpui1 and gpui2, but I kind of sidestepped the issue by spawning a condition before firing off a parsing task with \`set_language\`.
2023-12-04 13:14:03 +01:00
Piotr Osiewicz
9ffe78d264 Fix up random wrap map test 2023-12-04 12:10:01 +01:00
Kirill Bulatov
45230dcaf3 Log project path that was no open due to no project entry found 2023-12-04 12:45:57 +02:00
Conrad Irwin
aa3c9b8568 In-flight entertainment (#3484)
- +language_selector2
- Language Selector 2 working!
- Prevent languages showing in wrong order first
- copilot_menu2 (though only tested offling, which is insufficient)
- Dismiss tooltips at capture
- Get ChannelModal opening

[[PR Description]]

Release Notes:

-  N/A
2023-12-03 13:06:29 -07:00
Conrad Irwin
859f2d2862 Get ChannelModal opening 2023-12-02 00:57:41 -07:00
Conrad Irwin
63b65b2b2e Dismiss tooltips at capture
(Otherwise they stay open when you hover over the editor, which
stops mouse move events propagating)
2023-12-01 23:51:06 -07:00
Conrad Irwin
1da18ebe9a copilot_menu2 (though only tested offling, which is insufficient) 2023-12-01 23:43:02 -07:00
Conrad Irwin
6426997abb Prevent languages showing in wrong order first 2023-12-01 21:26:23 -07:00
Conrad Irwin
6a5b5f022d Language Selector 2 working! 2023-12-01 21:23:20 -07:00
Conrad Irwin
2de6758443 +language_selector2 2023-12-01 21:23:20 -07:00
Mikayla
fe83919746 Get terminal turning on 2023-12-01 17:01:57 -08:00
Mikayla
6730ff3624 Merge branch 'main' into terminal-element 2023-12-01 16:21:38 -08:00
Mikayla
b0f9144167 Compiling layout!
co-authored-by: Max <max@zed.dev>
2023-12-01 16:21:12 -08:00
Nathan Sobo
0edd89a92f Bind CloseWindow and Open actions on workspace
Co-Authored-By: Max <max@zed.dev>
2023-12-01 16:17:48 -07:00
Nathan Sobo
6f7995c150 Enable workspace::Open global action 2023-12-01 16:08:57 -07:00
Nathan Sobo
379ba620b2 Move workspace bindings to workspace context
Without this, hitting cmd-n on the context menu in the project browser
invokes the workspace::NewFile action instead of the project::NewFile
action. We're considering changing the behavior so that bindings with no
context can only invoke global actions.

Co-Authored-By: Max <max@zed.dev>
2023-12-01 15:59:40 -07:00
Nate Butler
2bf48872b6 Progress on diagnostic multibuffer
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-01 16:53:09 -05:00
Nate Butler
03ebf0a5a9 Implement FixedWidth for all button types
[no-ci]

Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-01 16:20:39 -05:00
Max Brunsfeld
26ae31b503 Bump RPC protocol version in zed2 to match zed1 (#3482) 2023-12-01 13:14:49 -08:00
Max Brunsfeld
6b0457c52f Bump RPC protocol version in zed2 to match zed1 2023-12-01 13:08:45 -08:00
Piotr Osiewicz
d81fb3680e Uncomment copilot2 tests 2023-12-01 22:04:43 +01:00
Max Brunsfeld
d788bf3f1e Fix retrieval of app version in auto_update2 (#3481)
With this fix, auto-updates should work on nightly.
2023-12-01 12:59:02 -08:00
Nate Butler
c07455efa7 Update path header style
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-12-01 15:53:17 -05:00
Max Brunsfeld
d85db44552 Fix retrieval of app version in auto_update2 2023-12-01 12:52:26 -08:00
Nate Butler
89aa6a3726 Re-add diagnostic headers 2023-12-01 15:30:01 -05:00
Nate Butler
f5679f98d6 Update diagnostics style 2023-12-01 14:52:17 -05:00
Piotr Osiewicz
be509a5ce0 test_clipboard 2023-12-01 20:50:30 +01:00
Max Brunsfeld
5dd9db3607 collab 0.29.1 2023-12-01 11:50:05 -08:00
Max Brunsfeld
aace0d816d Prevent moving a channel into its descendant (#3480)
This fixes a bug where it was possible to put the channel tree into a
bad state by accidentally dragging a channel into itself or one of its
descendants.
2023-12-01 11:47:39 -08:00
Max Brunsfeld
4479628342 Prevent moving a channel into its descendant 2023-12-01 11:41:38 -08:00
Piotr Osiewicz
0a1765b01f test_following_with_multiple_excerpts (passes) 2023-12-01 20:32:12 +01:00
Marshall Bowers
c3e7732eab Add support for optional icon to Button (#3479)
This PR extends `Button` with support for an optional icon to be
displayed next to the label.

As part of this, the functionality for displaying an icon within a
button has been factored out into an internal `ButtonIcon` component.
`ButtonIcon` is now used by both `IconButton` and `Button` to
encapsulate the concerns of an icon that is rendered within a button.

Release Notes:

- N/A
2023-12-01 14:30:38 -05:00
Piotr Osiewicz
a985b7aab4 test_following (passes :)) 2023-12-01 20:25:47 +01:00
Piotr Osiewicz
20ae58eddd Bunch of new tests 2023-12-01 20:23:34 +01:00
Piotr Osiewicz
9408eecb6e test_navigation_history (pass) 2023-12-01 20:17:41 +01:00
Piotr Osiewicz
2ab84b81da test_edit_events 2023-12-01 20:16:40 +01:00
Piotr Osiewicz
e821e1fc35 Display map tests (3 flaky tests for chunks) 2023-12-01 20:14:14 +01:00
Piotr Osiewicz
237efc841e Another batch of tests 2023-12-01 19:39:53 +01:00
Marshall Bowers
4b23c5c658 Add selected_label to Button (#3478)
This PR adds a new `selected_label` method to `Button`.

This can be used to set a different label that should be rendered when
the `Button` is selected.

Release Notes:

- N/A
2023-12-01 13:29:11 -05:00
Piotr Osiewicz
1c52b936bc Uncomment flaky tests 2023-12-01 19:21:30 +01:00
Marshall Bowers
4f507756d6 Add selected_icon to IconButton (#3477)
This PR adds a new `selected_icon` method to `IconButton`.

This can be used to set a different icon that should be rendered when
the `IconButton` is selected.

Release Notes:

- N/A
2023-12-01 13:17:32 -05:00
Marshall Bowers
12b58f5b60 Add selected_icon to IconButton 2023-12-01 13:10:53 -05:00
Marshall Bowers
f09d9ef723 Clean up some ButtonLike doc comments 2023-12-01 13:09:35 -05:00
Julia
be5eb3e3b5 Zed2 breadcrumbs & tab bar context menus & pane focus events (#3475)
See title

Release Notes:

- N/A
2023-12-01 12:53:15 -05:00
Piotr Osiewicz
53f3f960d2 Another batch of tests 2023-12-01 18:43:44 +01:00
Julia
6f9e08ceea Merge branch 'main' into zed2-breadcrumbs 2023-12-01 12:28:36 -05:00
Nate Butler
85d72f63c1 Add Tinted button style placeholder and document ButtonLike (#3476)
- Adds a placeholder `ButtonStyle::Tinted`.
  - Note: Using this now will just give you a block of `gpui::red()`
- Documents ButtonLike and ButtonStyle to hopefully help make choosing a
button style easier.

Release Notes:

- N/A
2023-12-01 12:18:35 -05:00
Piotr Osiewicz
4c4ec221af Uncomment a bunch of tests in the editor 2023-12-01 18:11:27 +01:00
Julia
910aeb4f56 I'm a dunce 2023-12-01 11:47:04 -05:00
Nate Butler
0d33f01fa4 Collab UI Refinement - Part 1 (#3460)
[[PR Description]]

Still WIP, merging this to get menu changes in.

Release Notes:

- N/A
2023-12-01 11:42:31 -05:00
Piotr Osiewicz
e0ccaa60ff editor tests: Reintroduce block_on_ticks.
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-01 17:24:47 +01:00
Piotr Osiewicz
a40a5fb212 Revert "editor tests: Reintroduce block_on_ticks."
This reverts commit aed11ee8cb.
2023-12-01 17:24:20 +01:00
Piotr Osiewicz
aed11ee8cb editor tests: Reintroduce block_on_ticks.
Co-authored-by: Antonio <antonio@zed.dev>
2023-12-01 17:22:12 +01:00
Julia
13f4cc563c Merge branch 'main' into zed2-breadcrumbs 2023-12-01 11:02:34 -05:00
Nate Butler
ab75dbe7af Update collab_titlebar_item.rs 2023-12-01 10:52:11 -05:00
Nate Butler
88188b0c30 Merge branch 'main' into collab-ui-refinement 2023-12-01 10:50:25 -05:00
Julia
ce16e5b54a Unbreak toolbar layout & improve disabling ButtonLike 2023-12-01 10:33:44 -05:00
Antonio Scandurra
134f3eb79f Add mouse context menu to editor2 (#3473)
We observed some weird behavior in `ContextMenu`, specifically:

- It seems like we don't intercept actions that have been dispatched,
which causes the context menu to stay open.
- The key bindings for editor actions in the context menu seem to come
from Vim

Release Notes:

- N/A
2023-12-01 16:25:07 +01:00
Antonio Scandurra
5fefc121bf Add mouse context menu to editor2
Co-Authored-By: Piotr <piotr@zed.dev>
2023-12-01 16:01:56 +01:00
Antonio Scandurra
9fedb4c7ee Get editor2 mostly to feature-parity with editor (#3472)
The only missing piece is the context menu, which I'll work on next.

Release Notes:

- N/A
2023-12-01 14:44:56 +01:00
Antonio Scandurra
f0c205be5d Respond to modifiers changed event in editor2 and set cursor 2023-12-01 14:26:49 +01:00
Antonio Scandurra
f0bc4a04bd Uncomment git gutter painting 2023-12-01 13:45:14 +01:00
Antonio Scandurra
1abc876c15 Merge remote-tracking branch 'origin/main' into no-more-todos-in-editor
# Conflicts:
#	crates/editor2/src/items.rs
2023-12-01 12:40:48 +01:00
Antonio Scandurra
8e4f2fb25a Remove all todos in Editor's Item implementation 2023-12-01 12:32:30 +01:00
Antonio Scandurra
faa896343b Register NewFile and NewFileInDirection from Editor 2023-12-01 12:11:54 +01:00
Antonio Scandurra
a8a5785ec5 Re-enable opening excerpts in Editor 2023-12-01 12:03:21 +01:00
Antonio Scandurra
c9be637b6b Re-introduce auto-height in editor2 (#3471)
Release Notes:

- N/A
2023-12-01 12:02:45 +01:00
Antonio Scandurra
0b7e324d86 🔥 2023-12-01 11:53:47 +01:00
Antonio Scandurra
8c59a2d2c0 Delete commented out code for editor element 2023-12-01 11:52:40 +01:00
Antonio Scandurra
21d3ae603d Remove stray dbg statements 2023-12-01 11:49:25 +01:00
Antonio Scandurra
1b07fd6a70 Fix edits not being reported when rewrapping 2023-12-01 11:48:21 +01:00
Joseph T. Lyons
a75f1a4a95 Close feedback modal when clicking cancel 2023-11-30 22:07:10 -05:00
Joseph T. Lyons
eef6c3729e Fix bug preventing spaces from being used in filename (#3454)
This bug was my fault, something I changed months ago to be more
consistent with VS Code - really strange that it took months for someone
to find out spaces couldn't be used in the project panel.

~I didn't apply this fix to zed2 because I dont think the facilities are
in place to do so (@maxbrunsfeld, @mikayla-maki, is there a system in
place for this that I missed?). I did leave a TODO.~

Fix is now in zed 2.

Release Notes:

- Fixed a bug where spaces could not be inserted when editing file names
in the project panel
([#2308](https://github.com/zed-industries/community/issues/2308)).
2023-11-30 21:44:48 -05:00
Joseph T. Lyons
9849a0a6d8 Move project panel's dispatch_context() to render() 2023-11-30 21:37:41 -05:00
Marshall Bowers
6d62e6c562 Respect label_color for Buttons (#3469)
This PR makes `Button`s respect the `label_color` that is specified,
provided they are not disabled or selected.

Release Notes:

- N/A
2023-11-30 20:36:59 -05:00
Conrad Irwin
0441b4805a Show cursor position in status bar (#3464)
Passes the compiler so it must work!

(though it doesn't show up until we fix pane focus)

Release Notes:

- N/A
2023-11-30 16:40:42 -07:00
Conrad Irwin
e1c8369b3d Rename menu_handle to right_click_menu and child to trigger
This makes things more in-line with `popover_menu`.
2023-11-30 16:39:43 -07:00
Max Brunsfeld
4f74d65d0c Restore some workspace functionality, enable workspace tests (#3468)
* Pane history navigation
* Autosave on focus change
* Setting the window title based on the active path
2023-11-30 15:26:44 -08:00
Conrad Irwin
ebbbeca9a6 Flesh out a popover control 2023-11-30 16:13:44 -07:00
Max Brunsfeld
cb11c1282c Re-introduce active path tracking in workspace 2023-11-30 14:58:14 -08:00
Max Brunsfeld
a003a91212 Restore auto-save on focus change, re-enable workspace tests 2023-11-30 14:45:27 -08:00
Nate Butler
bac39e85a8 Add some text to clarify no api key state (#3462)
This PR is a quick-and-dirty approach to clarify what is happening when
a user doesn't have an API key so the assistant opens with the key
editor.

- Adds some text explaining how to close the panel
- Explains that other subscriptions don't cover the assistant, it needs
an api key.
- Helps people understand where to go to get an API key.

This is a Zed 1 change only, a bandaid. Zed 2 will have a better
solution: [Working
issue](https://github.com/zed-industries/zed/issues/3461)

Release Notes:

- N/A
2023-11-30 17:26:50 -05:00
Nate Butler
e228422f14 Update feedback modal UI
Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com>
2023-11-30 16:42:58 -05:00
Nate Butler
8b7be8f614 Get feedback2 compiling
Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com>
Co-Authored-By: Conrad Irwin <conrad.irwin@gmail.com>
2023-11-30 16:07:54 -05:00
Marshall Bowers
e5a5b1e84c Rework ListHeader to be more open (#3467)
This PR reworks the `ListHeader` component to be more open.

The `meta` method can now be used to append meta items of any element to
the `ListHeader`, and they will be rendered with the appropriate spacing
between them.

Release Notes:

- N/A
2023-11-30 15:55:31 -05:00
Nate Butler
5098fafa02 Add v_stack and h_stack to the UI prelude
Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com>
Co-Authored-By: Conrad Irwin <conrad.irwin@gmail.com>
2023-11-30 15:28:18 -05:00
Nate Butler
f987a640fd init feedback2
Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com>
Co-Authored-By: Conrad Irwin <conrad.irwin@gmail.com>
2023-11-30 15:23:42 -05:00
Max Brunsfeld
c63ca09eed Reintroduce pane navigation history in zed2 2023-11-30 12:10:25 -08:00
Nate Butler
164084c61c Update collab_titlebar_item.rs 2023-11-30 14:55:59 -05:00
Nate Butler
180ba42456 WIP update popover_menu 2023-11-30 14:54:44 -05:00
Max Brunsfeld
bd6fa66a7c Bump Tree-sitter to fix another crash triggered by a markdown file (#3466)
Bumps Tree-sitter for
https://github.com/tree-sitter/tree-sitter/pull/2802

This fixes a regression introduced in the last Tree-sitter upgrade.

Release Notes:

- Fixed a crash that occurred when editing certain Markdown files.
2023-11-30 11:46:00 -08:00
Max Brunsfeld
16dc978bb4 Bump Tree-sitter for a crash fix 2023-11-30 11:36:36 -08:00
Joseph T. Lyons
f922ad9f7f Fix bug preventing spaces from being used in filename (zed2)
Co-Authored-By: Mikayla Maki <mikayla.c.maki@gmail.com>
2023-11-30 14:24:00 -05:00
Mikayla
39887be7d0 WIP: layout compiling 2023-11-30 11:01:36 -08:00
Julia
d516ae0d8a Pane context menus & focus shenanigans
Co-Authored-By: Conrad Irwin <conrad@zed.dev>
2023-11-30 14:01:09 -05:00
Marshall Bowers
3ffcb97da2 Implement Selectable for list components (#3465)
This PR implements the `Selectable` trait for the `ListItem` and
`ListHeader` components.

These components already had their own selectable behavior, but weren't
formally adhering to the interface defined by the trait.

Release Notes:

- N/A
2023-11-30 13:37:29 -05:00
Marshall Bowers
e78538e162 Implement Selectable for ListItem and ListHeader 2023-11-30 13:26:12 -05:00
Marshall Bowers
865baaa1a1 Remove unused GraphicSlot enum 2023-11-30 13:25:26 -05:00
Nate Butler
5fdfdb046c Remove unused import 2023-11-30 13:13:22 -05:00
Nate Butler
5eb89781e3 Clean up left side titlebar buttons 2023-11-30 13:11:20 -05:00
Mikayla Maki
b34b197d89 Update main.rs 2023-11-30 10:06:08 -08:00
Nate Butler
679851e349 Add render_project_name and render_project_branch
Co-Authored-By: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2023-11-30 12:51:55 -05:00
Nate Butler
8d4652a4db Scaffold out render_project_owner
Co-Authored-By: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2023-11-30 12:41:17 -05:00
Julia
21dfe58ad9 Missing return 2023-11-30 12:23:55 -05:00
Conrad Irwin
75afb65b26 Show cursor position in status bar 2023-11-30 10:16:24 -07:00
Marshall Bowers
0a8a84f656 Rename button-related enums (#3463)
This PR renames the `ButtonStyle2` and `ButtonSize2` enums to
`ButtonStyle` and `ButtonSize`, respectively.

Release Notes:

- N/A
2023-11-30 12:04:23 -05:00
Antonio Scandurra
0e1597d385 WIP 2023-11-30 18:00:41 +01:00
Julia
428c517693 Merge branch 'main' into zed2-breadcrumbs 2023-11-30 11:38:29 -05:00
Nate Butler
81098312ef Add some text to clarify no api key state
Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com>
2023-11-30 11:20:51 -05:00
Antonio Scandurra
5243401afa Pass a WindowContext in request_measured_layout
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-30 17:12:40 +01:00
Julia
591299b4c7 Start getting pane focus code ported 2023-11-30 10:47:28 -05:00
Antonio Scandurra
d9b8c07b1e Lift Send and Sync restriction for measurements 2023-11-30 16:37:57 +01:00
Nate Butler
e20309f560 Update collab_titlebar_item.rs
[no ci]
2023-11-30 09:44:33 -05:00
Nate Butler
daf6201deb Start plugging selected states into collab ui 2023-11-30 09:35:37 -05:00
Kirill Bulatov
b64fe04803 Trim file_finder input queries (#3459)
Addresses a small bit of influencer's feedback

Release Notes:

- Fixed file finder query's whitespaces not being trimmed
2023-11-30 16:22:14 +02:00
Nate Butler
82b3efa16c Update collab button styles 2023-11-30 09:12:39 -05:00
Kirill Bulatov
cdae8ebd06 Exclude gitignored files' diagnostics from project panel and its indicator. (#3458)
Based on influencer's feedback.

The diagnostics are collected and available still, since that might
become a settings/UI toggle later.
Also, buffer diagnostics are still updated for gitignored files.

Release Notes:

- Excluded gitignored files' diagnostics from project panel and its
indicator.
2023-11-30 16:06:16 +02:00
Kirill Bulatov
1b39916438 Trim file_finder input queries 2023-11-30 16:05:45 +02:00
Kirill Bulatov
091d93533d Port to gpui2 2023-11-30 15:46:35 +02:00
Kirill Bulatov
69bfd47cf9 Exclude gitignored files' diagnostics from project panel and its indicator.
The diagnostics are collected and available still, since that might become a settings/UI toggle later.
Also, buffer diagnostics are still updated for gitignored files.
2023-11-30 15:46:30 +02:00
Kirill Bulatov
7b76db4b50 Fix TypeScript diagnostics (#3457)
Deals with https://github.com/zed-industries/community/issues/2124

* sends more ClientCapabilities LSP data, diagnostics capabilities in
particular: those are now required by typescript-language-server LSP to
start publishing diagnostics
* sends more parameters during eslint workspace initialization, so it is
able to correctly look up project's typescript config
Presumably, it's not enough and some convoluted project set ups may
break still, but let's wait for examples and feedback.

Release Notes:

- Fixed typescript-language-server diagnostics not appearing for newer
server versions; fixed eslint diagnostics using wrong directory for
typescript config lookup
2023-11-30 13:25:25 +02:00
Antonio Scandurra
74a5ae5633 Re-introduce screen-sharing in zed2 (#3456)
Release Notes:

- N/A
2023-11-30 12:14:11 +01:00
Antonio Scandurra
fac029b808 Fix errors in theme2 2023-11-30 12:08:00 +01:00
Kirill Bulatov
f052329a6b Port to gpui2 2023-11-30 13:07:47 +02:00
Antonio Scandurra
d2c1897385 Fix error in tests 2023-11-30 11:45:10 +01:00
Kirill Bulatov
14993e0876 Declare more supported client capabilities 2023-11-30 12:22:23 +02:00
Antonio Scandurra
624ea58f2e Polish screen sharing 2023-11-30 11:22:01 +01:00
Antonio Scandurra
cc0bc444b1 Merge remote-tracking branch 'origin/main' into surfaces
# Conflicts:
#	crates/ui2/src/components/avatar.rs
2023-11-30 10:48:20 +01:00
Kirill Bulatov
f9cd45269a Fix eslint diagnostics by passing worktree root during workspace init 2023-11-30 11:38:16 +02:00
Conrad Irwin
02174084ca Collab panel2: Now with scrolling and keyboard (#3455)
Also introducing: .track_scroll() for non-uniform lists.

Release Notes:

- N/A
2023-11-30 00:13:52 -07:00
Conrad Irwin
90b8acfb34 Allow toggling subchannel visiblity without joining channel 2023-11-29 23:55:14 -07:00
Conrad Irwin
4fbbb28da2 Restore logical offset preservation behaviour 2023-11-29 23:51:31 -07:00
Conrad Irwin
54cfcef0aa Keybaord shortcut context menu 2023-11-29 23:04:33 -07:00
Conrad Irwin
a37f86f294 Scroll the collab panel when keyboard focusing 2023-11-29 22:39:15 -07:00
Nate Butler
92d2048aa4 WIP 2023-11-30 00:28:05 -05:00
Conrad Irwin
8638fc4149 Add a ScrollHandle to gpui2 for the collab panel 2023-11-29 22:20:06 -07:00
Conrad Irwin
012543052b Add filtering support 2023-11-29 20:58:15 -07:00
Joseph T. Lyons
1a1451a943 Fix bug preventing spaces from being used in filename 2023-11-29 22:04:41 -05:00
Max Brunsfeld
b4a5701e12 Implement activity indicator in zed2 (#3453)
No more hard-coded "Checking..."
2023-11-29 17:25:12 -08:00
Max Brunsfeld
e49325080c Implement activity indicator in zed2 2023-11-29 17:18:21 -08:00
Max Brunsfeld
c95a7c7387 Address some TODOs in editor2 crate (#3445)
* Fix crash when jumping to definition
* Enabling resolution of completions
* Make links in interactive text clickable
* Enable code paths that use `select_anchors`
2023-11-29 16:32:02 -08:00
Marshall Bowers
bda1dc7721 Use ButtonLike for breadcrumb (#3452)
This PR updates the (placeholder) breadcrumb to use the new `ButtonLike`
component.

Eventually this will need custom styling (e.g., syntax highlighting for
the symbol children), which is why we're using a `ButtonLike` as opposed
to a regular `Button`.

Release Notes:

- N/A
2023-11-29 19:21:34 -05:00
Marshall Bowers
9d53287341 Implement Selectable for buttons (#3451)
This PR implements the `Selectable` trait for `ButtonLike`, `Button`,
and `IconButton`.

Release Notes:

- N/A
2023-11-29 18:46:41 -05:00
Conrad Irwin
481e42ade9 collab panel2 (#3447)
[[PR Description]]

Release Notes:

- N/A
2023-11-29 16:38:35 -07:00
Max Brunsfeld
27a6761afc Fix crash when jumping to a definition in the same buffer 2023-11-29 15:36:22 -08:00
Conrad Irwin
4c27f4453c Merge branch 'main' into collab-panel2 2023-11-29 16:32:25 -07:00
Marshall Bowers
04bbd107c1 Fix the selected state for the panel icons in the status bar (#3450)
This PR fixes a bug where the selected state for the panel icons in the
status bar was not correctly reflecting whether the panel was open.

It was erroneously using the `is_open` state for the context menu.

Release Notes:

- N/A
2023-11-29 18:20:08 -05:00
Marshall Bowers
b357ae4dc3 Add new Button and IconButton components (#3448)
This PR adds new `Button` and `IconButton` components built on top of
our new button abstractions.

Both of these buttons are built from the common `ButtonLike` base, and
implement the `ButtonCommon` (name TBD) trait in order to provide a
common interface.

There are still some visual tweaks that we'll need to make to the new
buttons, but those should be straightforward to make after we land this.

Release Notes:

- N/A
2023-11-29 17:41:44 -05:00
Conrad Irwin
cd60d466b1 Merge branch 'main' into collab-panel2 2023-11-29 15:40:16 -07:00
Conrad Irwin
0eef4d03ed Scrollyscrolly 2023-11-29 15:30:50 -07:00
Max Brunsfeld
0b8f9067ff Implement highlighting of go-to-definition links 2023-11-29 14:30:31 -08:00
Conrad Irwin
4f040208c6 Fix mouse interaction on contact subsections 2023-11-29 15:26:19 -07:00
Max Brunsfeld
9451bd605d Implement completion-resolution in editor2
Enable the completion tests in editor2.
2023-11-29 14:20:47 -08:00
Conrad Irwin
3bdd51cb2a Availability indicators 2023-11-29 15:06:58 -07:00
Conrad Irwin
4ebae4d8bd Make LeftSlot take an element 2023-11-29 14:20:26 -07:00
Marshall Bowers
df5de47a78 Make toggle method accept impl Into<Option<bool>> (#3446)
This PR makes the `toggle` method on the various list components accept
an `impl Into<Option<bool>>` instead of just an `Option<bool>`.

This allows a caller with just a `bool` avoid having to wrap the
`Option` themselves.

Release Notes:

- N/A
2023-11-29 16:13:41 -05:00
Max Brunsfeld
2bd428102b Use pointer cursor style for clickable ranges in InteractiveText 2023-11-29 12:42:09 -08:00
Max Brunsfeld
fc2251e7ed Implement SelectionsCollection::select_anchor_ranges 2023-11-29 12:41:22 -08:00
Conrad Irwin
fb377aed73 Context menu, Dragon Drop, for collab panel (#3441)
Release Notes:

- N/A
2023-11-29 13:40:46 -07:00
Marshall Bowers
d8ed7c0721 Move GraphicSlot out of components module (#3444)
This PR moves the `GraphicSlot` definition out of the `components`
module, as it doesn't contain a component.

Release Notes:

- N/A
2023-11-29 15:29:33 -05:00
Conrad Irwin
6ab3825ab1 No moar toggleable 2023-11-29 13:25:29 -07:00
Conrad Irwin
87cf0cf5ac Merge branch 'main' into collab-panel2 2023-11-29 13:09:57 -07:00
Nate Butler
0d31722589 Style hover popover (#3443)
[[PR Description]]

This PR styles the hover popover, and sets it on the [second elevation
level](https://zed-docs.vercel.app/doc/ui2/trait.StyledExt.html#method.elevation_2).

![CleanShot 2023-11-29 at 14 55
55@2x](https://github.com/zed-industries/zed/assets/1714999/01589b2a-d6e2-4baa-ba5d-050f06cf2c58)


Release Notes:

- N/A
2023-11-29 15:07:46 -05:00
Nate Butler
3c05d57e6a Style hover popover 2023-11-29 14:56:23 -05:00
Marshall Bowers
509504d4d2 Rework Disclosure component (#3442)
This PR reworks the `Disclosure` component.

The primary goal here was to remove the illegal state where a
`Disclosure` is rendered with a `Toggle::NotToggleable` state, as the
`Disclosure` shouldn't exist at all in this case.

Release Notes:

- N/A
2023-11-29 14:46:39 -05:00
Conrad Irwin
139708d486 Merge branch 'main' into collab-panel2 2023-11-29 12:37:29 -07:00
Conrad Irwin
7e5aa5ce77 More drag'n'drop fixes
Co-Authored-By: Max <max@zed.dev>
2023-11-29 12:35:25 -07:00
Marshall Bowers
e77846d2dc Rework Toggle into Toggleable and ToggleState 2023-11-29 14:33:52 -05:00
Conrad Irwin
e377bd805b Add channel drag'n'drop
Co-Authored-By: Max <max@zed.dev>
2023-11-29 12:24:04 -07:00
Marshall Bowers
6f5cc0af94 Rework Disclosure component 2023-11-29 14:07:48 -05:00
Marshall Bowers
e99fa77bda Reorganize list components (#3440)
This PR reorganizes the list components so that each of the sub
components lives in its own file.

### Motivation

I've seen a number of folks have trouble finding the `ListItem`
definition while pairing, so having it in its own file seems more
self-explanatory.

Release Notes:

- N/A
2023-11-29 13:45:59 -05:00
Conrad Irwin
41e7653906 Channel Context Menu 2023-11-29 11:39:26 -07:00
Marshall Bowers
c7b79c9aef Remove unnecessary constructor and Clone derives 2023-11-29 13:38:05 -05:00
Marshall Bowers
390b0d8d56 Reorganize list components 2023-11-29 13:34:50 -05:00
Conrad Irwin
ee260a5e24 Progress 2023-11-29 11:06:24 -07:00
Max Brunsfeld
960ef7116d Add a context menu to the project panel (#3393)
This PR adds a context menu to the project panel in Zed2.

* [x] Allow the context menu to extend outside of the project panel's
bounds
* [x] Add keyboard shortcuts to the context menu
* [x] Dismiss the context menu
    * [x] when running an action
    * [x] when changing selection in the project panel

Release Notes:

NA
2023-11-29 10:04:41 -08:00
Marshall Bowers
3e2c517dd1 Add Disableable trait (#3439)
This PR adds a new `Disableable` trait to use for elements that are
capable of being disabled.

Release Notes:

- N/A
2023-11-29 13:01:26 -05:00
Max Brunsfeld
dbfc7d3555 Merge branch 'main' into project-panel-context-menu 2023-11-29 09:45:31 -08:00
Conrad Irwin
c23a610d52 Move padding on uniform list inside the scrollable area (#3437)
Release Notes:

- N/A
2023-11-29 10:43:04 -07:00
Max Brunsfeld
233aac5573 Make DismissEvent a unit struct
Co-authored-by: Conrad <conrad@zed.dev>
2023-11-29 09:42:54 -08:00
Marshall Bowers
043cef89eb Remove unneeded wrapping divs in ListItem left content (#3438)
This PR removes some unneeded wrapping `div`s around the left content in
`ListItem`s.

Release Notes:

- N/A
2023-11-29 12:40:35 -05:00
Max Brunsfeld
ac34229118 Add keyboard control over context menus
Co-authored-by: Conrad <conrad@zed.dev>
2023-11-29 09:39:20 -08:00
Marshall Bowers
912c30c05b Remove unneeded .clones 2023-11-29 12:35:39 -05:00
Marshall Bowers
97e6fd295a Remove unneeded wrapping divs in ListItem left content 2023-11-29 12:29:48 -05:00
Nate Butler
a8bf0834e6 Button2 – Part1 (#3420)
## TODO

- [x] Remove `InteractionState`
- [ ] `Selectable` should use `Selection` instead of a boolean
- [x] Clean out ui2 prelude
- [ ] Build out button2 button types
- [ ] Port old buttons

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-29 12:23:09 -05:00
Antonio Scandurra
a4035f15af WIP 2023-11-29 18:18:15 +01:00
Conrad Irwin
35481e2c79 Move padding on uniform list inside the scrollable area 2023-11-29 10:05:31 -07:00
Conrad Irwin
5d59108b97 Inviting/Responding/Creating Channels... etc. (#3433)
Release Notes:

- n/a
2023-11-29 09:29:25 -07:00
Joseph T. Lyons
f735f5287e v0.116.x dev 2023-11-29 11:08:32 -05:00
Kirill Bulatov
a62c4845a9 Make prettier more robust to offline work and errors (#3434)
Refactors prettier support:

* moves away prettier-related code into a separate module, makes
prettier installation & start phases more type-restricted
* ensures prettier installation and start attempts are capped with a
fixed attempts amount: after it's exceeded, no further attempts to
install and/or start prettier area made
* improves default prettier management, by correctly preserving its
plugin set between (re) installations and always installing the prettier
server part
* slightly improves prettier logging 

Release Notes:

- N/A
2023-11-29 14:02:31 +02:00
Kirill Bulatov
d92153218c Log prettier installation start & success 2023-11-29 13:44:19 +02:00
Kirill Bulatov
3e3b64bb1c Fix the tests 2023-11-29 12:10:41 +02:00
Kirill Bulatov
3796e7eecb Port to gpui2 2023-11-29 11:52:26 +02:00
Kirill Bulatov
6e44f53ea1 Style fixes 2023-11-29 11:33:29 +02:00
Kirill Bulatov
f1314afe35 Simplify default prettier installation function 2023-11-29 10:58:22 +02:00
Kirill Bulatov
96f6b89508 Clear failed installation task when error threshold gets exceeded 2023-11-29 10:58:22 +02:00
Kirill Bulatov
acd1aec862 Properly determine default prettier plugins to install 2023-11-29 10:58:22 +02:00
Kirill Bulatov
64259e4a0b Properly increment installation attempts 2023-11-29 10:58:22 +02:00
Kirill Bulatov
43d28cc0c1 Ignore initialized LSP request in prettier wrapper 2023-11-29 10:58:22 +02:00
Kirill Bulatov
465e53ef41 Always install default prettier 2023-11-29 10:58:22 +02:00
Kirill Bulatov
46ac82f498 Do not attempt to run default prettier if it's not installed yet 2023-11-29 10:58:22 +02:00
Kirill Bulatov
938f2531c4 Always write prettier server file 2023-11-29 10:58:22 +02:00
Kirill Bulatov
eab3476304 Split prettier code off to a separate module 2023-11-29 10:58:22 +02:00
Kirill Bulatov
e7e56757dc Limit prettier installation and start attempts 2023-11-29 10:58:22 +02:00
Kirill Bulatov
c288c6eaf9 Use enum variants for prettier installation and startup phases 2023-11-29 10:58:22 +02:00
Kirill Bulatov
d010f5f98d Exctract the common code 2023-11-29 10:58:22 +02:00
Conrad Irwin
5fbc60d8da Inviting/Responding/Creating Channels... etc. 2023-11-28 22:47:04 -07:00
Conrad Irwin
396ecefa4f use the right click event for buttons (#3432)
Previously we used mouse down

Release Notes:

- N/A
2023-11-28 21:27:42 -07:00
Conrad Irwin
0d4839b973 use the right click event for buttons 2023-11-28 21:14:48 -07:00
Marshall Bowers
e36c7dd301 Remove ID hack in ListItem (#3431)
This PR removes the ID hack in `ListItem`, since the underlying issue
was fixed in #3430.

Release Notes:

- N/A
2023-11-28 23:11:29 -05:00
Conrad Irwin
9e7a90a3d4 Fix stateful elements in Components (#3430)
No more wrapper divs for buttons

Release Notes:

- N/A
2023-11-28 20:59:58 -07:00
Conrad Irwin
db5ded0252 Remove useless method
We need to move state from layout to paint in any case
2023-11-28 20:53:46 -07:00
Conrad Irwin
8d1518d70c Fix stateful elements in Components
Previously a component assumed its element was stateless, this was
incorrect!
2023-11-28 20:47:11 -07:00
Max Brunsfeld
77acba9e4c Right-align key bindings in context menus
Remove extra div in `List` that prevented list items from
filling the available space.

Co-authored-by: Marshall <marshall@zed.dev>
2023-11-28 17:23:49 -08:00
Mikayla Maki
fbe440250e Restore welcome page (#3429)
Also restores the theme selector and base keymap selector. This does not
include a UI port.

Release Notes:

- N/A
2023-11-28 16:34:41 -08:00
Max Brunsfeld
6bf7ad71eb Show action key bindings in context menus 2023-11-28 16:07:42 -08:00
Conrad Irwin
7677998470 Channel joining (#3428)
- Remove debugging
- Basic channel joining!

[[PR Description]]

Release Notes:

- N/A
2023-11-28 17:00:21 -07:00
Conrad Irwin
60ce75c34a Togglable channels, the greatest since sliced bread 2023-11-28 16:52:12 -07:00
Mikayla
a41c857855 Merge branch 'main' into welcome2 2023-11-28 15:38:51 -08:00
Mikayla
ed8e62cd18 Restore welcome page and several pickers 2023-11-28 15:25:28 -08:00
Conrad Irwin
4c2348eb53 Fix tests, notify errors 2023-11-28 16:20:54 -07:00
Mikayla
d927c2f497 Implement all but the UI 2023-11-28 15:18:19 -08:00
Max Brunsfeld
bcf449d3fe Add a basic context menu to the project panel 2023-11-28 15:12:46 -08:00
Conrad Irwin
af3fa4ec0b Basic channel joining! 2023-11-28 16:10:18 -07:00
Conrad Irwin
75891e83f3 Fix mouse events when element is occluded (#3417)
This commit adds a depth map to the frame so we can find the "top most"
opaque layer by stacking order for a given point.

This lets us determine whether the element being hovered was part of the
given layer in the previous frame, which fixes the hover issue.

Still to do:
* [ ] Fix up mouse events too (I tried this, but it's not working yet,
and I want to understand why)

Release Notes:

- N/A
2023-11-28 15:57:22 -07:00
Conrad Irwin
b13638fa76 Remove debugging 2023-11-28 15:33:44 -07:00
Conrad Irwin
450f2bf6b6 More progress on collab panel (#3426)
Release Notes:

- n/a
2023-11-28 15:30:51 -07:00
Marshall Bowers
5666066db4 Merge branch 'main' into collab-panel2 2023-11-28 17:07:40 -05:00
Marshall Bowers
ee027bc112 Fix tooltips not showing on IconButtons (#3427)
This PR fixes tooltips not showing on `IconButton`s.

The "fix" here is the same hack that we used to fix `on_click` handlers
for `ListItem`s, where we introduce another layer of wrapping with an
element with an ID set.

This PR also adds a story for the `IconButton` so this issue can be
tested/observed in isolation.

Release Notes:

- N/A
2023-11-28 17:01:53 -05:00
Conrad Irwin
a85e0db1f4 More progress on collab panel 2023-11-28 14:40:32 -07:00
Marshall Bowers
8ee84249ec storybook2: Unsuppress and fix warnings (#3425)
This PR unsupresses the warnings in `storybook2` and summarily fixes
them.

Release Notes:

- N/A
2023-11-28 15:43:43 -05:00
Marshall Bowers
64376ad37a ui2: Reference theme2 and settings2 crates without the 2 (#3424)
This PR updates the `ui2` crate to reference the `theme2` and
`settings2` crates without the `2` the way we do in our other crates.

Release Notes:

- N/A
2023-11-28 15:30:29 -05:00
Nate Butler
d8fd422cf4 Refine keybindings (#3368)
[[PR Description]]

Refine the `Keybinding` component.

Still some issues:

![CleanShot 2023-11-28 at 15 15
38@2x](https://github.com/zed-industries/zed/assets/1714999/4fc1dde4-fe65-4e1d-acf5-6faefa12f053)

Lots of things moving so want to get this in.

Changes:

- use icons for some keys & modifiers
- updates some icons
- updates some state colors

Release Notes:

- N/A

Zed 2 Release Notes:

- Keybindings now use icons for common keys and modifiers
2023-11-28 15:22:38 -05:00
Nate Butler
b24a3b8fcb Unused imports 2023-11-28 15:17:09 -05:00
Nate Butler
525fe70de6 Fix missing arrows 2023-11-28 15:15:49 -05:00
Nate Butler
21755c7d20 Update keybinding.rs 2023-11-28 15:12:40 -05:00
Nate Butler
3855413725 Merge branch 'main' into refine-keybindings 2023-11-28 15:12:22 -05:00
Nate Butler
40bbbd3226 Update command_palette.rs 2023-11-28 15:06:37 -05:00
Marshall Bowers
070674a4fd ui2: Unsuppress and fix warnings (#3423)
This PR unsupresses the warnings in `ui2` and summarily fixes them.

Release Notes:

- N/A
2023-11-28 14:44:19 -05:00
Nate Butler
a9cb6589dd Update keybinding.rs 2023-11-28 14:40:26 -05:00
Marshall Bowers
874fde09ab Add inset variant to ListItem (#3422)
This PR adds an inset variant to the `ListItem` component.

We're now using this inset variant for the `ListItem`s we render in
pickers.

Release Notes:

- N/A
2023-11-28 14:27:19 -05:00
Nate Butler
f33cd3d463 Merge branch 'main' into refine-keybindings 2023-11-28 14:00:09 -05:00
Conrad Irwin
132ad5989c Add logged out collab panel (#3412)
Release Notes:

- N/A
2023-11-28 11:49:09 -07:00
Conrad Irwin
01d3432a40 Merge branch 'main' into collab-panel2 2023-11-28 11:40:58 -07:00
Marshall Bowers
9411898720 Use ListItems in the project panel (#3421)
This PR reworks the project panel to render its items using the
`ListItem` component.

There are a few hacks in here in order to get click handlers working for
the `ListItem`, but we'll want to get these fixed in GPUI.

Release Notes:

- N/A
2023-11-28 13:11:43 -05:00
Kirill Bulatov
4a01726e5e Use proper npm arguments and clean its inherited env vars (#3419)
Previously, `npm i` command could take too much time to download
dependencies, which was caused by wrong flags used.

Since we run `node` and `npm` processes ourselves and try to isolate
them from potentially "bad" user configs, it seems safer to remove any
ways to re-configure the tools via env vars, so strip off those too.

Release Notes:

- N/A
2023-11-28 19:14:10 +02:00
Conrad Irwin
a761e6ca0e More mouse occlusion work 2023-11-28 09:59:58 -07:00
Kirill Bulatov
5451db9c96 Use proper npm arguments and clean its inherited env vars 2023-11-28 18:39:03 +02:00
Antonio Scandurra
3ac545088a WIP: preserve aspect ratio of images 2023-11-28 17:33:11 +01:00
Marshall Bowers
179ad03ebc Render Picker matches using ListItems (#3418)
This PR updates the `PickerDelegate` implementations to render their
matches using the `ListItem` component so that they can have a
consistent style.

At some point it might make sense to move the `ListItem` rendering up
into the `Picker` implementation itself, and just have the delegate
responsible for giving us the inner content of the `ListItem`.

Release Notes:

- N/A
2023-11-28 10:56:27 -05:00
Marshall Bowers
ecb3bd7f59 Use ListItems in Picker story 2023-11-28 10:52:17 -05:00
Antonio Scandurra
600b564bbf Introduce surface rendering
Co-Authored-By: Julia <julia@zed.dev>
2023-11-28 16:47:31 +01:00
Marshall Bowers
1ee109cec7 Use ListItem when rendering picker matches 2023-11-28 10:44:57 -05:00
Marshall Bowers
63bd4ac999 Allow render_match to return an Option to represent no matches 2023-11-28 10:33:13 -05:00
Piotr Osiewicz
9fb3cb6a69 fixup! Add contact finder, change ui::List's on_click handler signature 2023-11-28 14:37:53 +01:00
Piotr Osiewicz
6c37393dd1 Add contact finder, change ui::List's on_click handler signature 2023-11-28 14:16:51 +01:00
Conrad Irwin
ca0dcf741f Use layers to correct mouse event handling too 2023-11-28 00:07:26 -07:00
Conrad Irwin
17b5f9294c Fix hover state when element is occluded 2023-11-27 23:18:25 -07:00
Conrad Irwin
26121713b3 Show channels and users in the sidebar 2023-11-27 19:57:55 -07:00
Mikayla Maki
eac4b2d076 Add Uiua language and Nu LSP (#3416)
Adds support for Uiua to my favorite editor in advance of AOC and
integrates the new nushell lsp. Change made to both zed1 and zed2.

Release Notes:

- Added support for the integrated Nushell LSP
- Added support for the Uiua language
2023-11-27 18:55:51 -08:00
Mikayla
8faa1f6e58 Merge branch 'main' into welcome 2023-11-27 18:55:23 -08:00
Mikayla
a760508080 Add uiua and nu languages 2023-11-27 18:46:56 -08:00
Nate Butler
84ee833d0c 🚧 Completion & Popover Styles 🚧 (#3406)
TODO:

- [ ] Add y axis padding to list instead of popover container
- [x] Combined max-width between completions list and docs
- [ ] Allow docs to be taller than completion list
- [x] Use the workspace bounds to set doc position
- [ ] Clean up markdown styles
- [x] (🐜) Changing items should set doc scroll position to the top
- [x] (🐜) popovers should render on top of all panes
- [ ] (extra) Try adding code block backgrounds to md rendering
- [ ] (extra) Use ui font for markdown text, buffer font for markdown
code.
- [ ] (extra) Try lining up inline code in completions menu

[[PR Description]]

Release Notes:

- N/A
2023-11-27 21:08:21 -05:00
Conrad Irwin
2a8f4f19d6 Scaffolding for update notification too (#3410)
Release Notes:

- N/A
2023-11-27 19:05:15 -07:00
Nate Butler
979ff70196 Update popover.rs 2023-11-27 20:12:20 -05:00
Nate Butler
ca2052b0c1 Merge branch 'main' into completions-styles 2023-11-27 19:22:07 -05:00
Conrad Irwin
eca49ef02e remove zed1 notification mess (#3415)
Accidentally added while testing zed2

Release Notes:

- N/A
2023-11-27 16:52:51 -07:00
Conrad Irwin
f3d142b7e9 remove zed1 notification mess 2023-11-27 16:27:02 -07:00
Conrad Irwin
4a25fae51e TEMP 2023-11-27 16:22:01 -07:00
Max Brunsfeld
369803cbc8 Upgrade Tree-sitter for stack-overflow bugfix (#3413)
Fixes https://github.com/zed-industries/community/issues/2290

This PR bumps Tree-sitter for
https://github.com/tree-sitter/tree-sitter/pull/2788.

Release Notes:

- Fixed a crash that could happen when opening certain large markdown
files.
2023-11-27 15:13:55 -08:00
Max Brunsfeld
6468fe737e Use 'any-match?' predicate in elixir embedding query 2023-11-27 15:06:20 -08:00
Conrad Irwin
e6aab57656 Merge branch 'main' into collab-panel2 2023-11-27 15:57:09 -07:00
Conrad Irwin
ccfc4fc0b9 Fix esc in command palette (#3414)
Also: add editor.register_action

Release Notes:

- N/A
2023-11-27 15:47:01 -07:00
Conrad Irwin
47b4d9942f Fix panic on quit 2023-11-27 15:33:47 -07:00
Conrad Irwin
107c3d7f67 Fix esc in command palette
Also: add editor.register_action
2023-11-27 14:32:22 -07:00
Max Brunsfeld
1c62abbf79 Upgrade Tree-sitter for stack-overflow bugfix 2023-11-27 13:16:50 -08:00
Julia
d551b41aae zed2: Mostly port breadcrumbs & improve StyledText api
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-11-27 16:09:31 -05:00
Piotr Osiewicz
f8614b5909 fixup! Await toggle of mute 2023-11-27 21:46:03 +01:00
Piotr Osiewicz
b0d9e3c8fa Await toggle of mute 2023-11-27 21:44:53 +01:00
Marshall Bowers
24f3034106 Add ListItem story (#3411)
This PR adds a story for the `ListItem` component, so we can work on it
in isolation.

Release Notes:

- N/A
2023-11-27 15:24:41 -05:00
Piotr Osiewicz
52119ca203 call: Restore mute_on_join behaviour 2023-11-27 21:06:06 +01:00
Conrad Irwin
6f839a1b48 Add logged out collab panel 2023-11-27 13:00:12 -07:00
Conrad Irwin
4f885252ab [Zed2] Collab UI (#3403)
Release Notes:

- N/A
2023-11-27 12:40:59 -07:00
Marshall Bowers
19ecccb107 Add ListItem story 2023-11-27 14:20:33 -05:00
Marshall Bowers
feb7753a73 Fix typo in uniform_list's doc comment 2023-11-27 14:20:27 -05:00
Conrad Irwin
e31a8f0536 Scaffolding for update notification too 2023-11-27 12:07:26 -07:00
Conrad Irwin
82f6f77117 Use editor's overlay implementation 2023-11-27 11:49:51 -07:00
Conrad Irwin
212d9254e1 Merge branch 'main' into completions-styles 2023-11-27 11:49:39 -07:00
Conrad Irwin
b48b74559c Break content mask for hoverables (#3409)
Release Notes:

- N/A
2023-11-27 11:49:20 -07:00
Conrad Irwin
7a8aba329b Break content mask for hoverables 2023-11-27 11:43:14 -07:00
Piotr Osiewicz
1c2f906e48 Position incoming call notification on right hand side.
Co-authored-by: Conrad <conrad@zed.dev>
2023-11-27 19:37:04 +01:00
Antonio Scandurra
e9e621518f Introduce hover popover in editor2 (#3408)
<img width="1245" alt="image"
src="https://github.com/zed-industries/zed/assets/482957/ba35e43c-82a6-493e-938b-8798352e636d">

Release Notes:

- N/A
2023-11-27 19:28:14 +01:00
Antonio Scandurra
3ea12ad0d7 Remove commented out code 2023-11-27 19:19:56 +01:00
Antonio Scandurra
fc4b621b32 Use element_hover instead of red 2023-11-27 19:16:06 +01:00
Antonio Scandurra
8c53f1b9c2 Uncomment hover popover tests 2023-11-27 19:14:58 +01:00
Antonio Scandurra
1e6214440d Show diagnostic hover popover 2023-11-27 19:08:25 +01:00
Piotr Osiewicz
b2b5df4428 close notification handler proper on accept/decline
Co-authored-by: Conrad <conrad@zed.dev>
2023-11-27 18:57:25 +01:00
Antonio Scandurra
a5951df21f Start on hover popover 2023-11-27 18:56:37 +01:00
Nate Butler
ab83f4319b revert popover changes
[no ci]
2023-11-27 11:51:42 -05:00
Conrad Irwin
fd165206bf gpui2: Notifications (#3407)
Release Notes:

- N/A
2023-11-27 09:22:31 -07:00
Nate Butler
8a35a02863 Checkpoint - try using overlay for completions popover
[no ci]
2023-11-27 11:22:19 -05:00
Nate Butler
1acc6b462f Start on completions styles 2023-11-27 10:29:29 -05:00
Kirill Bulatov
cd4ea344a6 Slightly less errors in terminal_element2 2023-11-27 16:55:48 +02:00
Piotr Osiewicz
8ca9f4e12a Fix some more warnings 2023-11-27 15:49:47 +01:00
Piotr Osiewicz
30af3ffaf3 fixup! Use AppContext instead of AsyncWindowContext to quit 2023-11-27 15:47:00 +01:00
Piotr Osiewicz
44876062c9 Use AppContext instead of AsyncWindowContext to quit
Fixes refcell-related panic
2023-11-27 15:44:19 +01:00
Nate Butler
56d3d663a5 UI progress (#3405)
[[PR Description]]

- Start outlining unfinished elements in `gpui::red()`
- Update tabs so they don't jump when activated
- Mock out a number of ui elements we haven't started yet.

Release Notes:

- N/A
2023-11-27 09:25:52 -05:00
Antonio Scandurra
0d1d01687b Finish autocomplete (#3404)
![image](https://github.com/zed-industries/zed/assets/482957/f1f40bec-4e8b-457b-8801-ce10ecb6fc80)


TODO:

- [x] Respect max height imposed by editor element
- [x] Ensure mouse down event handler in `EditorElement` doesn't prevent
links from being clicked

Release Notes:

- N/A
2023-11-27 15:24:59 +01:00
Antonio Scandurra
3ba5dbb9e8 Prevent mousedown on docs from being propagated to the editor 2023-11-27 15:15:57 +01:00
Piotr Osiewicz
a71180257d Add deafening support 2023-11-27 15:12:13 +01:00
Nate Butler
c40ab3c3da Update theme2.rs 2023-11-27 09:11:51 -05:00
Piotr Osiewicz
bf4211b03a Use facepile for avatars 2023-11-27 14:56:51 +01:00
Antonio Scandurra
eb647be685 Pass max height manually 2023-11-27 14:56:46 +01:00
Piotr Osiewicz
714b45157b Replace unrendered frame with a frame counter
Move facepile to the left hand side
2023-11-27 13:02:21 +01:00
Piotr Osiewicz
9590f253a9 Fix warnings 2023-11-27 12:46:07 +01:00
Piotr Osiewicz
5cbe8deb50 Fix up tests 2023-11-27 12:33:44 +01:00
Piotr Osiewicz
4c1514edc4 fixup! Remove shared_screen from workspace 2023-11-27 12:10:07 +01:00
Piotr Osiewicz
fad1f8db96 Remove shared_screen from workspace 2023-11-27 12:09:36 +01:00
Conrad Irwin
039c933d8e gpui2: Notifications 2023-11-26 22:28:53 -07:00
Nate Butler
2e61a84bd6 Update tab borders 2023-11-24 16:57:50 -05:00
Nate Butler
48a9f2be4c Update one theme colors 2023-11-24 16:57:35 -05:00
Antonio Scandurra
682712f132 Account for previous line lengths when returning index 2023-11-24 18:32:48 +01:00
Antonio Scandurra
047cfe5108 Fix painting when underlines and quads appeared after a wrap boundary 2023-11-24 18:23:08 +01:00
Antonio Scandurra
f227c3284d Consume newline from run if it spans it 2023-11-24 17:53:48 +01:00
Piotr Osiewicz
481c19fbaf WIP, frames are being sent to the other end 2023-11-24 17:31:47 +01:00
Antonio Scandurra
0baa9a782b Start on wiring up render_parsed_markdown 2023-11-24 17:28:59 +01:00
Antonio Scandurra
d31b53b912 Extract a gpui::combine_highlights function 2023-11-24 16:31:38 +01:00
Nate Butler
d965ddba27 Start debugging tab borders 2023-11-24 09:33:44 -05:00
Nate Butler
6a3925c3e1 Add copilot icons 2023-11-24 09:22:31 -05:00
Nate Butler
31565a810d Outline statusbar items 2023-11-24 09:16:19 -05:00
Piotr Osiewicz
6ebe5d5053 Add mute handling 2023-11-24 15:16:03 +01:00
Antonio Scandurra
e5b6b0ee9e WIP 2023-11-24 15:05:04 +01:00
Nate Butler
3c46e81533 More outlines 2023-11-24 09:03:14 -05:00
Nate Butler
7e61d340ce Outline some unfinished elements 2023-11-24 08:58:49 -05:00
Antonio Scandurra
19bfed165b Show single-line docs in autocomplete and apply completion on mousedown 2023-11-24 13:22:25 +01:00
Antonio Scandurra
54357d6553 Syntax highlight completions 2023-11-24 13:10:56 +01:00
Antonio Scandurra
bf39968105 Return TextRuns in combine_syntax_and_fuzzy_match_highlights 2023-11-24 13:00:20 +01:00
Kirill Bulatov
700168467e Avoid binary target name conflicts (#3401)
Fixes binary build warnings such as 

https://github.com/zed-industries/zed/actions/runs/6980107733/job/18994693722#step:5:9

```
warning: output filename collision.
The bin target `dotenv` in package `collab2 v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab2)` has the same output filename as the bin target `dotenv` in package `collab v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab)`.
Colliding filename is: /Users/administrator/actions-runner-1/_work/zed/zed/target/debug/dotenv
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
warning: output filename collision.
The bin target `dotenv` in package `collab2 v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab2)` has the same output filename as the bin target `dotenv` in package `collab v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab)`.
Colliding filename is: /Users/administrator/actions-runner-1/_work/zed/zed/target/debug/dotenv.dSYM
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
warning: output filename collision.
The bin target `seed` in package `collab2 v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab2)` has the same output filename as the bin target `seed` in package `collab v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab)`.
Colliding filename is: /Users/administrator/actions-runner-1/_work/zed/zed/target/debug/seed
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
warning: output filename collision.
The bin target `seed` in package `collab2 v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab2)` has the same output filename as the bin target `seed` in package `collab v0.28.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/collab)`.
Colliding filename is: /Users/administrator/actions-runner-1/_work/zed/zed/target/debug/seed.dSYM
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
```

by suffixing the collab2 binaries with `2`.

Release Notes:

- N/A
2023-11-24 13:44:23 +02:00
Kirill Bulatov
e19860d3b0 Avoid binary target name conflicts 2023-11-24 13:29:01 +02:00
Kirill Bulatov
bc2dfcfa8e Ignore excluded entries' children FS events (#3400)
Deals with https://github.com/zed-industries/community/issues/2295 and
https://github.com/zed-industries/community/issues/2296

Release Notes:

- Fixed excluded .git files appearing in worktree after FS events
2023-11-24 13:12:44 +02:00
Kirill Bulatov
879a069b35 Port to gpui2 2023-11-24 13:05:53 +02:00
Kirill Bulatov
31a4acf98a Fix collab tests 2023-11-24 12:56:58 +02:00
Kirill Bulatov
95a413847a Properly ignore FS events of excluded files 2023-11-24 11:59:13 +02:00
Kirill Bulatov
4a060db801 Add a test 2023-11-24 11:59:13 +02:00
Joseph T. Lyons
22e6803fc6 Add app close events (#3399)
Release Notes:

- N/A
2023-11-23 21:23:13 -05:00
Joseph T. Lyons
18d3b1c397 Add app close events 2023-11-23 21:14:59 -05:00
Piotr Osiewicz
f2b62c3946 Start emitting notifications for calls 2023-11-24 00:52:44 +01:00
Antonio Scandurra
510320bb47 Introduce InteractiveText (#3397)
This new element will let us react to click events on arbitrary ranges
of some rendered text, e.g.:

```rs
InteractiveText::new(
    "element-id",
    StyledText::new("Hello world, how is it going?").with_runs(vec![
        cx.text_style().to_run(6),
        TextRun {
            background_color: Some(green()),
            ..cx.text_style().to_run(5)
        },
        cx.text_style().to_run(18),
    ]),
)
.on_click(vec![2..4, 1..3, 7..9], |range_ix, cx| {
    println!("Clicked range {range_ix}");
})
```

As part of this, I also added the ability to give text runs a background
color.

Release Notes:

- N/A
2023-11-23 19:35:03 +01:00
Antonio Scandurra
7eeb8078f6 Allow setting text background color via TextStyle 2023-11-23 19:22:18 +01:00
Antonio Scandurra
1ad22231d2 Allow providing a background color in a TextRun 2023-11-23 18:55:16 +01:00
Kirill Bulatov
df7b89b6cb Allow to include gitignored files into project search (#3394) 2023-11-23 19:28:11 +02:00
Piotr Osiewicz
204218b5f5 chore: Use "limited" debug info (#3396)
This one does not contain variable-level debug info; since we mostly
rely on backtraces though, this should be fine. It also improves compile
times a bunch for both incremental and clean scenarios.

Release Notes:

- N/A
2023-11-23 18:17:55 +01:00
Antonio Scandurra
56d043f671 Allow finding which ranges were clicked on an InteractiveText 2023-11-23 18:08:49 +01:00
Piotr Osiewicz
41b7acc1de chore: Use "limited" debug info
This one does not contain variable-level debug info; since we mostly rely on backtraces though, this should be fine. It also improves compile times a bunch for both incremental and clean scenarios.
2023-11-23 17:54:43 +01:00
Piotr Osiewicz
e754c6626d Allow calling an user, render contacts in collab panel 2023-11-23 16:19:25 +01:00
Piotr Osiewicz
2a2b3b5e91 Authenticate on app startup 2023-11-23 15:08:21 +01:00
Piotr Osiewicz
b463454062 Remove redundant comment 2023-11-23 15:03:54 +01:00
Piotr Osiewicz
390ecb5b0c Merge branch 'main' into collab_ui-zed2 2023-11-23 15:02:55 +01:00
Piotr Osiewicz
c191943849 Add basic call/user UI in top-right corner.
Allow ui::Avatar to take custom data instead of always relying on URI resolution
2023-11-23 15:00:13 +01:00
Piotr Osiewicz
72a1ada0ae Add scripts for running Zed2 collab environment (#3395)
./script/zed-local now looks for "--zed2" flag in its args and runs Zed2
binaries instead of zed1. 'foreman start' can be launched with '-f
Procfile.zed2" argument to launch zed2 collab server.

Release Notes:

- N/A
2023-11-23 14:45:52 +01:00
Piotr Osiewicz
0b67983ddf Add scripts for running Zed2 collab environment
./script/zed-local now looks for "--zed2" flag in its args and runs Zed2 binaries instead of zed1.
'foreman start' can be launched with '-f Procfile.zed2" argument to launch zed2 collab server.
2023-11-23 14:19:06 +01:00
Antonio Scandurra
2cc1df9053 Rename element-related traits (#3390)
@as-cii I think this naming makes more sense. If you like it, can you
push forward?

Release Notes:

- N/A
2023-11-23 13:03:05 +01:00
Antonio Scandurra
3b918bfee8 Merge branch 'main' into rename-element-traits
# Conflicts:
#	crates/gpui2/src/elements/uniform_list.rs
#	crates/ui2/src/components/context_menu.rs
#	crates/ui2/src/components/list.rs
2023-11-23 12:47:46 +01:00
Kirill Bulatov
cee6fd8dd3 Port to gpui2 2023-11-23 10:44:05 +02:00
Kirill Bulatov
eee63835fb Exclude more ignored/worktree-less/project-less buffers from inlay hint requests 2023-11-23 10:30:52 +02:00
Kirill Bulatov
c2751c717e Parallelize ignored entries for search lookup 2023-11-23 09:53:00 +02:00
Joseph T. Lyons
f33c0e80a6 Add app events (#3372)
Adds app events (`first open` and `open`). For the time being, I'm
abandonding trying to add `close`, after running into many issues
trying. The code is in place for me to continue on that work, but at the
moment, we require having the telemetry settings in hand when calling
any of the methods that log an event, so we can honor the user's
preference for sending telemetry or not, but when running the
`on_app_close` method, to send off an app `close` event, the settings
are no longer available (probably the order of teardown?), which causes
some tests to end up failing. I'm not sure how to solve this. Maybe we
keep the settings on the telemetry struct and update it each time any
event is logged, then, on app shutdown, when logging the app `close`
event, we can use the stored version (idk).

Release Notes:

- N/A
2023-11-22 23:16:28 -05:00
Joseph T. Lyons
35f35dd476 Remove unused import 2023-11-22 23:03:11 -05:00
Joseph T. Lyons
575ab81409 Disable app close event 2023-11-22 23:01:31 -05:00
Joseph T. Lyons
a876b6f700 Remove comments 2023-11-22 23:01:18 -05:00
Julia
88af458f62 zed2(ish) Cancel completion resolution when new list (#3391)
Release Notes:

- N/A
2023-11-22 18:44:34 -05:00
Mikayla Maki
f01a04a8e0 Fix pane splitting panic (#3381)
Also opens the window on startup.

Release Notes:

-
2023-11-22 14:12:28 -08:00
Piotr Osiewicz
ebeb0fda28 ci: Add ci-config.toml in .cargo folder. (#3392)
Release Notes:

- N/A
2023-11-22 22:54:16 +01:00
Mikayla
6e84d8fbc0 Merge branch 'main' into panic-hunting 2023-11-22 13:47:17 -08:00
Mikayla
eb74ad7caa Fix failing test 2023-11-22 13:41:48 -08:00
Piotr Osiewicz
6da57cbc6e Decouple workspace from call (#3380)
This PR decouples `call2` from `workspace2` in order to improve our
compile times.
Why pick such a small, innocent crate as `call`? It depends on
`live_kit_client`, which is not-so-innocent and is in fact stalling our
clean builds.
In this PR, `call2` depends on `workspace2`; workspace crate defines a
`CallHandler` trait for which the implementation resides in `call`; it
it then all tied together in `zed`, which passes a factory of `Box<dyn
CallHandler>` into workspace's `AppState`.
Clean debug build before this change: ~1m45s
Clean debug build after this change: ~1m25s

Clean release build before this change: ~6m30s
Clean release build after this change: ~4m30s

~Gonna follow up with release timings where I expect the change to be
more impactful (as this allows 2/3 of the infamous trio of
"project-workspace-editor" long pole to proceed quicker, without being
blocked on live-kit-client build script)~.
This should have little effect (if any) in incremental scenarios, where
live_kit_client is already built.
[release
timings.zip](https://github.com/zed-industries/zed/files/13431121/release.timings.zip)

Release Notes:
- N/A
2023-11-22 22:31:47 +01:00
Julia
37e3cc1291 zed2(ish) Cancel completion resolution when new list 2023-11-22 16:26:27 -05:00
Joseph T. Lyons
0def2bc0d2 Remove dbg 2023-11-22 16:26:15 -05:00
Piotr Osiewicz
c04f123e44 ci: Add ci-config.toml in .cargo folder. 2023-11-22 22:25:26 +01:00
Joseph T. Lyons
ee2b6834bd Revert "Convert telemetry into a model"
This reverts commit 6e4268a471.
2023-11-22 16:16:44 -05:00
Joseph T. Lyons
c86e99928b Merge branch 'main' into add-app-events 2023-11-22 16:16:11 -05:00
Joseph T. Lyons
6e4268a471 Convert telemetry into a model
Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com>
2023-11-22 16:16:10 -05:00
Julia
62b1843704 zed1: Cancel completion resolution when new list (#3389)
Release Notes:

- Fixed a bug where Zed would continue to request documentation for
completion lists which were stale or no longer visible.
2023-11-22 14:23:09 -05:00
Julia
f0c7b3e6ee Update copilot when we are the last task 2023-11-22 14:03:43 -05:00
Antonio Scandurra
2611b5d6d7 Fix positioning editor2's context menu when scrolling up/down (#3388)
Release Notes:

- N/A
2023-11-22 19:56:36 +01:00
Piotr Osiewicz
b45234eece Fix warnings in unimplemented function 2023-11-22 19:24:38 +01:00
Piotr Osiewicz
fa74c49dbb Add dummy call handler for tests 2023-11-22 19:23:58 +01:00
Nathan Sobo
c23f17ee0b Reorganize element-related traits 2023-11-22 11:19:43 -07:00
Julia
9abce4bdd9 zed1: Cancel completion resolution when new list
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-11-22 13:16:52 -05:00
Joseph T. Lyons
10c4df20e9 collab 0.29.0 2023-11-22 13:05:29 -05:00
Antonio Scandurra
524f892fb0 Correctly swap position of context menu 2023-11-22 19:02:44 +01:00
Joseph T. Lyons
7b0b87380d v0.115.x dev 2023-11-22 12:57:32 -05:00
Marshall Bowers
cac6e22e8e Rework ListItem to use children (#3387)
This PR reworks the `ListItem` component to accept `children` rather
than just a `Label`.

This is a step towards making the `ListItem` component more open.

As part of this the `ContextMenu` was simplified to only construct the
various list components in `render` rather than holding them as part of
its state.

Release Notes:

- N/A
2023-11-22 12:55:20 -05:00
Marshall Bowers
fd5793ddec Use children for ListItems 2023-11-22 12:44:51 -05:00
Marshall Bowers
031fca4105 Simplify ContextMenu by not storing list components 2023-11-22 12:41:29 -05:00
Marshall Bowers
2c8d243d22 Comment out todo!() to fix panic when opening context menus 2023-11-22 12:41:06 -05:00
Antonio Scandurra
eaf90a4fbd Fix drawing uniform list elements when scrolling 2023-11-22 18:32:02 +01:00
Antonio Scandurra
ca1d9dd0e5 Fix scrolling in gpui2::UniformList (#3386)
Release Notes:

- N/A
2023-11-22 18:10:49 +01:00
Kirill Bulatov
71e9bd8fa3 Use a git file icon for toggle gitignored search option 2023-11-22 19:09:10 +02:00
Kirill Bulatov
566857b0b7 Output non-ignored files first 2023-11-22 19:03:27 +02:00
Antonio Scandurra
8aaa46a1b6 Track scroll in editor's context menu 2023-11-22 17:58:00 +01:00
Antonio Scandurra
bd4a710cef Use interactivity's base style for UniformList 2023-11-22 17:50:34 +01:00
Antonio Scandurra
f37ace63e0 Initial autocomplete support for editor2 (#3383)
Release Notes:

- N/A
2023-11-22 16:02:37 +01:00
Kirill Bulatov
92953fb53d If enabled, search in ignored files during project search 2023-11-22 16:41:02 +02:00
Piotr Osiewicz
ce3acf17cf CI: use global config toml (#3384)
It looks like we should keep RUSTFLAGS consistent in CI if possible;
some commands augmented RUSTFLAGS with "-D warnings" which overrode
`.cargo/config.toml`, causing unnecessary rebuilds even for non-bundling
runs. Tl;dr: for the last few days our average CI time spiked
significantly.
There are several solutions:
- We can place `-D warnings` in our `.cargo/config.toml`. That's not a
good solution, because then you wouldn't ever be able to build Zed with
warnings locally. A true PITA!
- We can place another config.toml somewhere in the search path
(https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure)
and rely on the merging of properties. That way we can avoid having `-D
warnings` on developer machines while being able to override CI
behaviour at will.

This PR implements the latter approach by creating the new config file
manually. Ideally we should have it a a separate file in repository
that's moved into $HOME/.cargo on each CI run. Maybe we should even
place it somewhere more local to the checked out Zed version, as placing
it in a global spot is kinda bad too - what if we start building
multiple cargo projects on our CI machines?

Release Notes:

- N/A
2023-11-22 15:25:43 +01:00
Piotr Osiewicz
492c3a1e83 Bump artifact size limit for CI to 100GB 2023-11-22 15:14:24 +01:00
Piotr Osiewicz
6f8e03470c Use printf instead of echo 2023-11-22 14:41:26 +01:00
Piotr Osiewicz
8a6d3094c4 Change tabs to spaces 2023-11-22 14:37:41 +01:00
Piotr Osiewicz
552f03c49d chore/CI: place .cargo/config.toml augmentations in ~/.cargo/config.toml 2023-11-22 14:36:10 +01:00
Antonio Scandurra
2b6e8de11f Don't perform wrapping in completions 2023-11-22 14:23:09 +01:00
Antonio Scandurra
fff2d7955e Round up line width 2023-11-22 14:01:22 +01:00
Antonio Scandurra
3a8e9b5697 Avoid holding borrow to editor while painting child elements 2023-11-22 11:40:38 +01:00
Joseph T. Lyons
a4a1e6ba98 WIP
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-21 22:32:07 -05:00
Mikayla Maki
c199d92dac Update main.rs 2023-11-21 18:14:14 -08:00
Mikayla
469b05684f Fix a few identity mixups in GPUI
co-authored-by: nathan <nathan@zed.dev>
2023-11-21 17:12:32 -08:00
Piotr Osiewicz
7e7a778d11 Move CallFactory into AppState
Fix crash caused by double borrow of window handle
2023-11-21 22:04:02 +01:00
Mikayla
e557eb4afe Fix no window showing up on startup
co-authored-by: Marshall <marshall@zed.dev>
2023-11-21 12:45:25 -08:00
Mikayla
663bbb06d9 WIP 2023-11-21 12:40:00 -08:00
Piotr Osiewicz
abe5a9c85f Finish up decoupling workspace from call 2023-11-21 20:51:53 +01:00
Marshall Bowers
a94cf54aab Fix storybook (#3379)
This PR fixes storybook and gets it back into a compiling and running
state.

Release Notes:

- N/A
2023-11-21 14:28:00 -05:00
Piotr Osiewicz
ebccdb64bc Move CallHandler impl into call2 2023-11-21 20:18:35 +01:00
Piotr Osiewicz
170291ff96 Start decoupling workspace and call crates 2023-11-21 19:57:24 +01:00
Nate Butler
6fe7b22164 UI refinements + Popover (#3377)
[[PR Description]]

- Update default ui_font_size
- Update toolbar style
- Start on ui::Popover

Release Notes:

- N/A
2023-11-21 13:49:10 -05:00
Marshall Bowers
1b05aad30c Extract Story into separate story crate (#3378)
This PR extracts the `Story` component into a separate `story` crate so
that it can be shared among various crates that define stories.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
2023-11-21 13:42:00 -05:00
Nate Butler
40a49e6896 Update popover doc
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-21 12:43:56 -05:00
Nate Butler
9a145a4d86 Correctly position popover aside, add list item states
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-21 12:40:48 -05:00
Nate Butler
9f2e3bab9b Allow popover to take an aside
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-21 12:26:10 -05:00
Nate Butler
dac7912e87 Start on popover 2023-11-21 12:10:06 -05:00
Marshall Bowers
f4b4bdfd83 ui2: Get component stories compiling again (#3376)
This PR gets the component stories defined in `ui2` compiling again.

Release Notes:

- N/A
2023-11-21 12:06:25 -05:00
Nate Butler
f04deeb5f9 Document InteractionState 2023-11-21 11:51:15 -05:00
Antonio Scandurra
f2c63781f9 Merge branch 'main' into editor2-autocomplete
# Conflicts:
#	crates/editor2/src/editor.rs
2023-11-21 17:43:09 +01:00
Nate Butler
bec61123fd Update toolbar.rs 2023-11-21 11:06:27 -05:00
Marshall Bowers
6cd0bcc578 Fix panic when opening settings in zed2 (#3375)
This PR fixes a panic that occurs when opening the settings in zed2.

We store the `ThemeRegistry` as a global without wrapping it in an
`Arc`, so we need to retrieve it the same way.

Release Notes:

- N/A
2023-11-21 10:58:52 -05:00
Nate Butler
6d6aa3b253 Update default ui font settings 2023-11-21 10:43:18 -05:00
Nate Butler
79c890f31a ui2 cleanup (#3374)
[[PR Description]]

Tidies up the `ui2` crate, removing all components we aren't using and
doing a bit of reorganization.

Release Notes:

- N/A
2023-11-21 01:55:00 -05:00
Nate Butler
209fb10731 Remove stories mod until it is re-enabled 2023-11-21 01:36:35 -05:00
Nate Butler
8947438510 Update elevation 2023-11-21 01:23:47 -05:00
Nate Butler
453aa5ffd7 TextColor -> Color 2023-11-21 01:05:29 -05:00
Nate Butler
56d45e72cd Add Shape enum to avatar component, refactor UITextSize to styles/typography.rs 2023-11-21 00:59:29 -05:00
Nate Butler
205607a9cd Clean out UI 2023-11-21 00:44:51 -05:00
Piotr Osiewicz
b4275008f9 Search2 (#3332)
This is just a buffer search (without project search), as the latter
needs a multibuffer from `editor`
Release Notes:

- N/A
2023-11-21 01:38:14 +01:00
Mikayla Maki
361801e527 WIP: Remove V parameter on elements (#3366)
This is an exploration of what it would take to remove the `V` generic
from the element type. Answer: less than I expected.

I added a new struct to GPUI2: `CallbackHandle<Event>`, and reworked the
interactivity related APIs to take this type. I also added a
`.callback()` function to `ViewContext` that can construct a
`CallbackHandle` to wrap our current `|&mut View, &Evt, &mut
ViewContext| {...}` based APIs. With these two changes, we can now
capture the context of the callsite of a click handler, allowing us to
capture all relevant types and data _before_ sending them into GPUI.
This lets us achieve a similar programing style to the existing system,
while also letting us remove all of the generics from the entire element
system. For an example of what this looks like in practice, here's a
side by side diff of the test in `interactive.rs` (which compiles and
passes):

<img width="1310" alt="Screenshot 2023-11-19 at 7 32 08 PM"
src="https://github.com/zed-industries/zed/assets/2280405/596f2a9a-9c8e-4158-bf6d-0003cf973015">

Note how the new arrangement of types is more amenable to rust's type
inference, allowing the code to be just as terse as before despite the
extra function call in the middle.

This approach also allows components to provide well typed APIs to
views, without ever knowing that view's type. This PR includes an
example rewrite of the button component in `ui2`, here's what it's
struct could look like now:

<img width="1105" alt="Screenshot 2023-11-19 at 7 24 28 PM"
src="https://github.com/zed-industries/zed/assets/2280405/fc98d3c2-6831-4c0f-a324-ab0fae33b0bc">

However, I have not yet ported the derive macro for Component to this
new structure, as I know @nathansobo is currently reworking that code.

Once that macro has been rewritten, it should be relatively easy to
rewrite the rest of Zed2 with this approach, the only major difference
that I can foresee is that the editor element would need to wrap it's
operations in an update callback. Though I can think of a few ways to
fix this with a new `ViewElement` trait, that does the wrapping for you.
2023-11-20 16:24:01 -08:00
Piotr Osiewicz
2138715fca Merge remote-tracking branch 'origin/callback-handles' into search2 2023-11-21 01:16:21 +01:00
Mikayla
a844bf7d41 Fix stories
co-authored-by: Piotrek <piotr@zed.dev>
2023-11-20 16:06:10 -08:00
Piotr Osiewicz
3e329861f9 Brave new world awaits
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-21 00:47:47 +01:00
Piotr Osiewicz
54a3b56935 Merge remote-tracking branch 'origin/callback-handles' into search2 2023-11-21 00:40:20 +01:00
Conrad Irwin
8049ec674e Fix last thing 2023-11-20 16:35:56 -07:00
Conrad Irwin
5c0f2e75ea Remove all the settings 2023-11-20 16:30:14 -07:00
Conrad Irwin
3e2dba36b4 Merge branch 'main' into callback-handles 2023-11-20 16:28:35 -07:00
Nate Butler
3096222672 Add deploy-docs script (#3373)
[[PR Description]]

Adds `script/deploy-docs`:
- If you don't already have it, it will clone the `zed-docs` repo into
`../zed-docs`
    - It will build the docs and output them in `../zed-docs`
    - Then it will open the docs.
- By default this "dry runs" (doesn't push) but you can pass `-p` to
push the changes.
    - If you add `-c` it will clean out the old docs before running.
    
If you run the script with `p` it will push up the changes, and vercel
will automatically deploy them.

Release Notes:

- N/A
2023-11-20 18:07:37 -05:00
Conrad Irwin
8b6b7e60be borrow less 2023-11-20 16:00:47 -07:00
Nate Butler
a83d626912 Make the script a bit more resilient 2023-11-20 17:50:25 -05:00
Mikayla
2c4d83c9af WIP
co-authored-by: conrad <conrad@zed.dev>
co-authored-by: Nathan <nathan@zed.dev>
2023-11-20 14:46:01 -08:00
Nate Butler
a87ffa58ff Add more crates 2023-11-20 17:07:14 -05:00
Nate Butler
3210d36727 Create deploy-docs 2023-11-20 16:13:18 -05:00
Marshall Bowers
652e3b0bb6 Rename ListEntry to ListItem (#3371)
This PR renames the `ListEntry` component to `ListItem` to better
reflect its intent.

Release Notes:

- N/A
2023-11-20 16:13:06 -05:00
Joseph T. Lyons
daddb03e7a Remove comments 2023-11-20 16:04:32 -05:00
Joseph T. Lyons
a0dcc9618e Mark app event as open if we fail to get installation_id
If we find a previous installation_id, then we send `open`. If we don't find a previous installation_id, then we sent as `first open`. If we fail, we mark it as `open` so that we don't accidentally bloat our `first open` stats.
2023-11-20 16:04:15 -05:00
Joseph T. Lyons
db3f487474 Fix zed2 compile error 2023-11-20 16:00:05 -05:00
Joseph T. Lyons
0926db9111 Add app events 2023-11-20 15:51:36 -05:00
Max Brunsfeld
4f4ef4a357 More fixes for zed nightly (#3370)
When running `script/bundle` with the new `-2` flag, we needed to adjust
the fat-binary creation step to look for the binary called `Zed2`.

We also fixed a source of intermittent build failures in `script/bundle`
due to running multiple `swift build` processes concurrently for the
`live_kit_client2` crate, building for the two architectures.

Release Notes:

NA
2023-11-20 12:36:25 -08:00
Max Brunsfeld
2731855487 Fix error when building live_kit_client2 for two architectures concurrently
Co-authored-by: Julia <julia@zed.dev>
2023-11-20 12:10:35 -08:00
Marshall Bowers
eb307b22f0 Rework List to use children (#3369)
This PR reworks the `List` component to use `children` instead of
accepting a `Vec<ListItem>` in its constructor.

This is a step towards making the `List` component more open.

Release Notes:

- N/A
2023-11-20 14:58:31 -05:00
Mikayla
6985b70859 fix gpui2 2023-11-20 11:55:27 -08:00
Conrad Irwin
25ed7c57c1 WIP 2023-11-20 12:34:08 -07:00
Conrad Irwin
d0dd44faad Merge branch 'main' into callback-handles 2023-11-20 12:21:42 -07:00
Max Brunsfeld
6e84f740aa Set -e in bump-nightly script
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-20 11:01:37 -08:00
Max Brunsfeld
c571273ab4 Allow bundling in debug mode with both architectures
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-20 11:00:12 -08:00
Max Brunsfeld
babd41916a Remove gpui (1) dependencies from zed2
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-20 10:46:16 -08:00
Mikayla
acbad4c7b7 WIP 2023-11-20 10:34:22 -08:00
Piotr Osiewicz
fe0a8b4be2 Fix warnings in tests 2023-11-20 19:25:14 +01:00
Piotr Osiewicz
d60855b06d Merge branch 'main' into search2 2023-11-20 19:23:35 +01:00
Piotr Osiewicz
99ef8ccd3f fixup dismissing of search bar 2023-11-20 19:07:37 +01:00
Conrad Irwin
6f0cdc35f3 Refine element and component traits (#3367)
This PR changes `Element::paint` to move self and introduces a new
`RenderOnce` trait, which renders into an element by moving self.
Elements are required to be `RenderOnce`, and `element_id` is now on
`RenderOnce` so we can get the id without moving self. The `child` and
`children` methods now expect `impl RenderOnce`.

```rust
pub trait Element<V: 'static>: 'static + RenderOnce<V> {
    type State: 'static;

    fn layout(
        &mut self,
        view_state: &mut V,
        element_state: Option<Self::State>,
        cx: &mut ViewContext<V>,
    ) -> (LayoutId, Self::State);

    fn paint(
        self,
        bounds: Bounds<Pixels>,
        view_state: &mut V,
        element_state: &mut Self::State,
        cx: &mut ViewContext<V>,
    );

    fn into_any(self) -> AnyElement<V> {
        AnyElement::new(self)
    }
}

pub trait RenderOnce<V: 'static>: Sized {
    type Element: Element<V> + 'static;

    fn element_id(&self) -> Option<ElementId>;

    fn render_once(self) -> Self::Element;

   // default helpers ...
}
```

To make a type a component, you can add `#[derive(RenderOnce)]`, which
will require your type to implement the `Component` trait:

```rust
pub trait Component<V: 'static>: 'static {
    type Rendered: RenderOnce<V>;

    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered;
}
```

I'm satisfied with this being what we open source for elements, aside
from maybe adding a `StatefulComponent` trait that uses element state.
Things finally feel like they slot into a coherent and simple narrative.

Release Notes:

- N/A
2023-11-20 10:48:50 -07:00
Piotr Osiewicz
3ddfc7ff61 Remove unused import 2023-11-20 18:44:51 +01:00
Piotr Osiewicz
fa8cd843ca fixup! Merge branch 'main' into search2 2023-11-20 18:42:15 +01:00
Piotr Osiewicz
07cc5904f8 Merge branch 'main' into search2 2023-11-20 18:37:41 +01:00
Piotr Osiewicz
3d28495c67 fixup! Remove dbg statements 2023-11-20 18:35:39 +01:00
Piotr Osiewicz
ad38708ede Remove dbg statements 2023-11-20 18:31:30 +01:00
Piotr Osiewicz
c1f0ac30a0 Fix up tests once and for good 2023-11-20 18:24:37 +01:00
Conrad Irwin
b19a6c0dcc Fix storybook2 2023-11-20 10:16:05 -07:00
Conrad Irwin
5d16f76518 fmt 2023-11-20 10:05:57 -07:00
Conrad Irwin
8599ba557d Fix more import errors 2023-11-20 10:01:21 -07:00
Conrad Irwin
f08426c7a0 shhhh 2023-11-20 10:00:37 -07:00
Conrad Irwin
f86480ba5d Merge followup mess 2023-11-20 09:58:05 -07:00
Conrad Irwin
0798cfd58c Merge branch 'main' into derive-element-redux 2023-11-20 09:15:38 -07:00
Nate Butler
35e0b53639 Merge branch 'main' into refine-keybindings 2023-11-20 10:46:37 -05:00
Nate Butler
176a68f90f kb 2023-11-20 10:46:23 -05:00
Joseph T. Lyons
385028865e Add timestamp delta to telemetry events (#3365)
- [X] assistant_events
- [X] call_events
- [X] copilot_events
- [X] cpu_events
- [X] download_events
- [X] editor_events
- [X] memory_events
- [X] page_events
- [X] panic_events
---
- [X] For each of the previous tables:
    - [X] backup events to comp
    - [X] make new table (changing any time fields to be datetime64)
    - [X] copy data
    - [X] rename old table
    - [X] rename new table
---
- [ ] Delete all old tables once (going to hold off on this for some
time - want to make sure things are working correctly)
    - [X] we see new data coming in on new tables
    - [X] old times truncate correctly
    - [X] new times show millisecond values
---
- [X] check dashboards to make sure queries didn't get broken from
changing time data type
    - [X] Fix broken charts
- [X] monitor vercel for runtime errors

---

- [X] zed.dev changes
- [X] chrono changes to panicked_on
    - [X] Make sure new panics are good
- [X] remove commented out code and dbg code
- [X] triple check that both crate versions have the changes
- [X] make sure all event function calls use the same original arguments
- [X] Finish testing version 2

Release Notes:

- N/A
2023-11-20 09:27:18 -05:00
Joseph T. Lyons
966bf56ddb Activate telemetry in zed2 2023-11-20 09:08:07 -05:00
Piotr Osiewicz
8d4828a2e8 Circumvent part of the tests 2023-11-20 13:43:56 +01:00
Mikayla
f3eb1d4abf Add button port example 2023-11-19 19:11:51 -08:00
Joseph T. Lyons
5dc3369cf6 Remove unused imports 2023-11-19 22:09:14 -05:00
Joseph T. Lyons
722ad5a01d Fix data types 2023-11-19 22:03:55 -05:00
Joseph T. Lyons
8e612e4287 Restore commented-out code 2023-11-19 22:00:01 -05:00
Joseph T. Lyons
e0f8615d52 Remoe dbg!()s 2023-11-19 21:55:19 -05:00
Joseph T. Lyons
3abd376d6a Add timestamp delta to telemetry events 2023-11-19 21:52:28 -05:00
Conrad Irwin
45371584b1 Focus workspace on launch (#3364)
Fixes action dispatch when no focus is there.

Release Notes:

- N/A
2023-11-19 19:38:41 -07:00
Mikayla
88024ca7c9 Remove V parameter on elements 2023-11-19 18:32:31 -08:00
Conrad Irwin
c0d85dc1dd Fix text wrapping when a child of a v_stack() (#3362)
Previously text that was rendered in a flex-column would reserve the
correct
amount of space during layout, and then paint itself incorrectly.

Release Notes:

- N/A
2023-11-19 11:37:31 -07:00
Piotr Osiewicz
b67193e322 ui2: Do not enable 'stories' feature by default. (#3363)
This cuts down LLVM IR size from 3 million lines to 700k in debug build.
This then leads to ~3s compile time in debug build (without incremental
on ui itself), as opposed to 10.5s on main.

Release Notes:

- N/A
2023-11-19 12:11:42 +01:00
Piotr Osiewicz
e83a297229 Move binding function back into keybinding module 2023-11-19 11:54:46 +01:00
Piotr Osiewicz
31336b0b7d ui2: Do not enable 'stories' feature by default.
This cuts down LLVM IR size from 3 million lines to 700k in debug build.
This then leads to ~3s compile time in debug build (without incremental on ui itself), as opposed to 10.5s on main.
2023-11-19 11:44:33 +01:00
Conrad Irwin
c5738a509c delint 2023-11-18 22:20:50 -07:00
Conrad Irwin
0711121586 Add some test cases 2023-11-18 22:16:28 -07:00
Nathan Sobo
33cd6f520a Clean compile with redesigned element traits 2023-11-18 21:51:47 -07:00
Conrad Irwin
cf47ed2c5f Fix wrapping when parent has a max_width
(Also avoid re-wrapping text we already wrapped)
2023-11-18 21:31:16 -07:00
Conrad Irwin
242a0b85b5 Update Taffy to latest main 2023-11-18 21:30:33 -07:00
Conrad Irwin
7172ce37b1 Fix text wrapping when a child of a v_stack() 2023-11-18 20:35:22 -07:00
Nathan Sobo
0673606de8 WIP 2023-11-18 20:22:43 -07:00
Nathan Sobo
adc355a1e6 Element refinement passing on ui2 2023-11-18 20:05:47 -07:00
Mikayla Maki
073405018e Revert changes 2023-11-18 16:37:34 -08:00
Mikayla Maki
b758d1b871 Update bundle 2023-11-18 13:06:23 -08:00
Mikayla Maki
fd8ebb4f17 Update bundle 2023-11-18 11:50:54 -08:00
Mikayla Maki
a73db6bfca Fix more bundling errors (#3361)
Release Notes:

- N/A
2023-11-18 10:59:35 -08:00
Mikayla
ede7656f02 please work 2023-11-18 10:46:35 -08:00
Mikayla Maki
30b7da0e4a N/t6 (#3360)
[[PR Description]]

- Fix broken pane group spacing
- Work on diagnostics styling (still wip)

Release Notes:

- N/A
2023-11-17 23:29:18 -08:00
Nathan Sobo
be33f000e2 WIP: Lots of errors, starting on resurrecting derive Element 2023-11-18 00:27:40 -07:00
Mikayla
55dbcf2039 Remove unused imports 2023-11-17 23:14:14 -08:00
Nate Butler
f9804feefa Remove flex_1 from bottom dock to fix pane group spacing 2023-11-18 02:06:51 -05:00
Nathan Sobo
23ffce9fbe WIP: Work toward eliminating Component trait
This refactor enhances the overall design by promoting reusable and composable UI component structures within the Zed project codebase.
2023-11-18 00:03:23 -07:00
Nathan Sobo
2515bbf990 Move self in Element::paint
Remove mutable state borrows in favor of state ownership in render processes to streamline element rendering.
2023-11-17 23:32:55 -07:00
Mikayla
461fa8d71e Testing the auto-updater 2023-11-17 22:17:42 -08:00
Mikayla Maki
e1285b9780 Add nightly release channel for zed2 (#3355)
Release Notes:

- N/A
2023-11-17 22:12:18 -08:00
Nate Butler
d6f173866e Use muted color for fold indicators 2023-11-18 01:02:40 -05:00
Nate Butler
b218ab4755 Remove default hover state for icon buttons 2023-11-18 01:02:21 -05:00
Nate Butler
101fe7fbb5 Update diagnostic status bar tool 2023-11-18 00:54:01 -05:00
Mikayla
fd6e47c167 Fix auto update command 2023-11-17 21:52:49 -08:00
Nate Butler
9969caf513 Style empty diagnostics as an editor 2023-11-17 23:39:40 -05:00
Nathan Sobo
0069dd5ce6 WIP 2023-11-17 20:05:37 -07:00
Mikayla
eb2e9a59b7 Fix bundling again 2023-11-17 18:27:11 -08:00
Mikayla
16f420528d Remove CLI from zed2 (for now) 2023-11-17 18:09:42 -08:00
Mikayla
e43696eb6b Temporary debug builds for faster ci testing 2023-11-17 17:46:05 -08:00
Mikayla
70d0421b3c Fix bundle script 2023-11-17 17:44:41 -08:00
Max Brunsfeld
ee753beebd Build nightly release based on 'nightly' tag, not any tag w/ nightly prefix 2023-11-17 17:21:35 -08:00
Mikayla
bf324c152b Add bump nightly script 2023-11-17 17:14:48 -08:00
Mikayla
dd283b471a Add autoupdate2
co-authoredby: max@zed.dev
2023-11-17 15:48:32 -08:00
Max Brunsfeld
a03d062120 Make the commit sha availabe in the app, via a global
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Kirill <kirill@zed.dev>
2023-11-17 14:52:43 -08:00
Max Brunsfeld
a8bea41ad8 Put release channel back to dev 2023-11-17 14:31:49 -08:00
Max Brunsfeld
6a22ab83cf Fix cargo check --tests invocation 2023-11-17 14:16:12 -08:00
Max Brunsfeld
5e2eb436ff Check out repo outside of reusable actions
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Kirill <kirill@zed.dev>
2023-11-17 14:14:03 -08:00
Max Brunsfeld
c684f08e30 Wire up release_nightly workflow, to upload artifacts to DO spaces
Co-authored-by: Kirill <kirill@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-17 14:07:51 -08:00
Julia
c9fc7eac4f zed2: Project diagnostics (#3359)
Release Notes:

- N/A
2023-11-17 16:54:00 -05:00
Julia
189ddf9380 Merge branch 'main' into unborked-git-zed2-diagnostics-view 2023-11-17 16:43:29 -05:00
Julia
62297a56c8 Render diagnostics header and summary 2023-11-17 16:32:35 -05:00
Julia
24c9bbf461 Render a more correct diagnostic indicator status panel button 2023-11-17 16:32:35 -05:00
Julia
3655a96e54 Merge branch 'main' into unborked-git-zed2-diagnostics-view 2023-11-17 16:32:35 -05:00
Julia
967ef9d414 Render diagnostics view and such a bit more 2023-11-17 16:32:35 -05:00
Julia
c6d22af416 Get diagnostic2 tests building and running 2023-11-17 16:32:35 -05:00
Julia
a464a7da2a Merge branch 'main' into unborked-git-zed2-diagnostics-view 2023-11-17 16:32:35 -05:00
Julia
f4eb219c75 Get diagnostics view almost building in the zed2 world
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-17 16:32:35 -05:00
Conrad Irwin
ec3bb0f090 Focus workspace on launch
Fixes action dispatch when no focus is there.
2023-11-17 14:11:39 -07:00
Kirill Bulatov
12b59daa1e Allow to exclude files from worktrees (#3356)
* Part of https://github.com/zed-industries/community/issues/70

Allows to fully remove from Zed certain files or file groups: no items
will be scanned or added into worktrees, so nothing will be shown in
project tree, project search and go to file would not see those,
corresponding FS events will be ignored.

One exclusion is .git files: those are still not shown or accessible by
default, yet tracked in the worktrees.

By default, is configured to 
```json
  "file_scan_exclusions": [
    "**/.git",
    "**/.svn",
    "**/.hg",
    "**/CVS",
    "**/.DS_Store",
    "**/Thumbs.db",
    "**/.classpath",
    "**/.settings"
  ],
```

* In addition, contains code preparations for "search in included files"
feature: new SearchOptions variant, search crate and RPC adjustments

Release Notes:

- Added `file_scan_exclusions` section to project settings to completely
ignore certain files in Zed
2023-11-17 23:06:47 +02:00
Mikayla Maki
7a8da8ce11 Adjust the type arrangement on ManagedViews (#3354)
Made the trait into a blanket-trait impl if you have it's two
constituent pieces (FocusableView and EventEmitter) to remove the
duplicated method. I also changed the struct to an enum for aesthetic
reasons (EventType::EventName feels self documenting to me) and added
some new `cx` APIs utilizing our new powers of dismissal.

Release Notes:

- N/A
2023-11-17 13:04:57 -08:00
Conrad Irwin
a6d6f8a193 shhh (#3358)
- Fix image errors


Release Notes:

- N/A
2023-11-17 14:01:44 -07:00
Kirill Bulatov
d352a63d9d Port new workspace logic to gpui2, uncomment most of the workspace2 tests 2023-11-17 22:54:19 +02:00
Mikayla
f44f60c6e6 WIP: 2023-11-17 12:54:16 -08:00
Nathan Sobo
c866c211b5 Make static str and SharedString implement Element 2023-11-17 13:48:01 -07:00
Conrad Irwin
6bfe6fa0e1 Fix image errors
* Firstly only log one error per image load, not per frame
* Secondly use an Icon not an image for rendering Icons
2023-11-17 13:47:07 -07:00
Mikayla
149b9d1aa6 Merge branch 'main' into managed-view-adjustment 2023-11-17 12:40:44 -08:00
Conrad Irwin
624bd0a05a Collab ui2 (#3357)
* Clickable context menus & movable panels – what will they think of
next?!

Release Notes:

- N/A
2023-11-17 13:33:18 -07:00
Conrad Irwin
ceb20dea96 Refactorings 2023-11-17 13:23:12 -07:00
Mikayla
6976af5029 Push some sketches 2023-11-17 12:16:55 -08:00
Kirill Bulatov
616bda85e9 Fix the tests 2023-11-17 21:40:57 +02:00
Kirill Bulatov
2759ed4d00 An attempt to ignore git regularly 2023-11-17 21:40:57 +02:00
Kirill Bulatov
8180938401 Fix most of the TODOs 2023-11-17 21:40:57 +02:00
Kirill Bulatov
6028cd90d4 Retract back to original scanning strategy
Do not descend into ignored directories, to avoid tracking their state.
2023-11-17 21:40:57 +02:00
Kirill Bulatov
906db58188 Defer ignored dirs scanning 2023-11-17 21:40:57 +02:00
Kirill Bulatov
30fefa0ef8 Use a better name 2023-11-17 21:40:57 +02:00
Kirill Bulatov
5f468970f0 Fix some of the old tests 2023-11-17 21:40:57 +02:00
Kirill Bulatov
d3ce82e82c Fix the new test 2023-11-17 21:40:57 +02:00
Kirill Bulatov
cafeba103b Exclude ignored opened buffers from search 2023-11-17 21:40:57 +02:00
Kirill Bulatov
ce2cfc6035 Fix the ! bug, better test draft 2023-11-17 21:40:57 +02:00
Kirill Bulatov
26f7e66b49 Add default scan excluded files settings 2023-11-17 21:40:56 +02:00
Kirill Bulatov
c52fe2f536 Move toggle ignored button into include directories editor 2023-11-17 21:40:56 +02:00
Kirill Bulatov
9373d38434 Rescan worktree on scan exclusions settings change 2023-11-17 21:40:56 +02:00
Kirill Bulatov
1612c90052 More lenient file path matchers 2023-11-17 21:40:56 +02:00
Kirill Bulatov
b8be720490 Fix the bugs 2023-11-17 21:40:56 +02:00
Kirill Bulatov
126e4cce8f Scan all ignored files by default now 2023-11-17 21:40:56 +02:00
Kirill Bulatov
401f85bed2 Properly ignore elements from configured exceptions 2023-11-17 21:40:56 +02:00
Kirill Bulatov
9072e5a507 Properly set ignore stacks and is_ignored values 2023-11-17 21:40:56 +02:00
Kirill Bulatov
7d97dfa6be Test and filter data draft 2023-11-17 21:40:56 +02:00
Kirill Bulatov
a5c615ceb4 Pass a new query parameter into the search 2023-11-17 21:40:56 +02:00
Conrad Irwin
eb04160d2d Dock menu 2023-11-17 12:14:06 -07:00
Conrad Irwin
9d742b90c3 Allow you to click on a context menu item 2023-11-17 11:57:51 -07:00
Mikayla
5cf953d559 Add error messages to server deployment for nightly 2023-11-17 10:31:10 -08:00
Mikayla
17d53d0e38 Rename again, add fun cx APIs using new traits 2023-11-17 10:06:41 -08:00
Mikayla
01d9d53f4a Adjust the type arrangement on ManagedViews 2023-11-17 09:51:11 -08:00
Max Brunsfeld
8837045abb Fix underspecified assertions in new tests for file reload race (#3353)
I accidentally left the test assertions unnecessarily vague in
https://github.com/zed-industries/zed/pull/3348. This PR makes the
assertions constrain the behavior more specifically. I also added a
second test for a related bug that was fixed in that PR, about
performing and undoing an edit while a buffer reload is in-progress.

Release Notes:

- NA
2023-11-17 09:32:00 -08:00
Max Brunsfeld
55548ca61a Fix underspecified assertions in new tests for file reload race 2023-11-17 09:14:31 -08:00
Piotr Osiewicz
3b5754a77e Clean up tests (they compile now) 2023-11-17 17:23:47 +01:00
Piotr Osiewicz
eb9959a0cf gpui: notifications now takes an entity instead of a model 2023-11-17 17:23:05 +01:00
Piotr Osiewicz
ae1ebc6858 fixup! Remove dead code (for now). 2023-11-17 17:22:52 +01:00
Piotr Osiewicz
27600b6b8d Remove dead code (for now).
Ensure actions are registed just once (previously some were registered on both Workspace and search bar itself).
2023-11-17 14:42:20 +01:00
Piotr Osiewicz
741e11cc11 Fix up action derive 2023-11-17 13:27:33 +01:00
Piotr Osiewicz
f23cc724d4 chore: cargo fmt 2023-11-17 13:23:42 +01:00
Piotr Osiewicz
dca2dc7b6b Merge branch 'main' into search2 2023-11-17 13:22:30 +01:00
Piotr Osiewicz
8845f5a183 Clean up warnings 2023-11-17 13:22:07 +01:00
Kirill Bulatov
6680e1e9fe Create new Zed release channel: nightly 2023-11-17 12:13:32 +02:00
Antonio Scandurra
27cd7b204d Merge branch 'main' into editor2-autocomplete 2023-11-17 11:03:25 +01:00
Kirill Bulatov
9d8184670a Remove binary target collisions between zed & zed2 (#3352)
Get rid of the following warnings:

```
The example target `test_app` in package `live_kit_client2 v0.1.0 (/Users/someonetoignore/work/zed/zed/crates/live_kit_client2)` has the same output filename as the example target `test_app` in package `live_kit_client v0.1.0 (/Users/someonetoignore/work/zed/zed/crates/live_kit_client)`.
Colliding filename is: /Users/someonetoignore/work/zed/zed/target/debug/examples/test_app
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
warning: output filename collision.
The example target `test_app` in package `live_kit_client2 v0.1.0 (/Users/someonetoignore/work/zed/zed/crates/live_kit_client2)` has the same output filename as the example target `test_app` in package `live_kit_client v0.1.0 (/Users/someonetoignore/work/zed/zed/crates/live_kit_client)`.
Colliding filename is: /Users/someonetoignore/work/zed/zed/target/debug/examples/test_app.dSYM
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.

---------------

warning: output filename collision.
The bin target `Zed` in package `zed2 v0.109.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/zed2)` has the same output filename as the bin target `Zed` in package `zed v0.113.0 (/Users/administrator/actions-runner-1/_work/zed/zed/crates/zed)`.
Colliding filename is: /Users/administrator/actions-runner-1/_work/zed/zed/target/debug/Zed.dSYM
The targets should have unique names.
Consider changing their names to be unique or compiling them separately.
This may become a hard error in the future; see <https://github.com/rust-lang/cargo/issues/6313>.
```

as we plant to build *.dmg of both versions for a while.

Release Notes:

- N/A
2023-11-17 10:02:27 +02:00
Kirill Bulatov
89e44d4901 Remove binary target collisions between zed & zed2 2023-11-17 09:56:28 +02:00
Mikayla Maki
41ac8ef909 Restore saving (#3351)
and a bunch of random workspace stuff!

Release Notes:

-
2023-11-16 23:22:01 -08:00
Mikayla
1693718637 Merge branch 'main' into saving-2 2023-11-16 23:11:38 -08:00
Mikayla
9a3cd073c7 Restore a bunch of random workspace stuff 2023-11-16 23:05:28 -08:00
Nathan Sobo
2fb13cf1ca Separate WrappedLines from ShapedLines (#3350)
ShapedLines are never wrapped, whereas WrappedLines are optionally
wrapped if they are associated with a wrap width. Originally, when
rewriting GPUI, I tried to combine everything because wrapping is
inherently optional for the Text element, but we have a bunch of APIs
that don't make sense on a line that may wrap, so we need a distinct
type for that case.

This is a precursor to implementing clickable links in markdown. I
noticed multiple places where we were confused about whether or not the
line was wrapped so this felt important.

Release Notes:

- N/A
2023-11-16 23:20:35 -07:00
Conrad Irwin
391ab8fa1b Add more detail to panel switcher interaction (#3344)
Also featuring:
* IconButton#action()
* gpui::ManagedView
* `menu_handle()`

Release Notes:

- N/A
2023-11-16 23:16:50 -07:00
Nathan Sobo
9558da8681 Separate WrappedLines from ShapedLines
ShapedLines are never wrapped, whereas WrappedLines are optionally wrapped if
they are associated with a wrap width. I tried to combine everything because
wrapping is inherently optional for the Text element, but we have a bunch of
APIs that don't make sense on a line that may wrap, so we need a distinct type
for that case.
2023-11-16 23:10:51 -07:00
Conrad Irwin
2d1d75f482 +ManagedView
And some games with rust traits
2023-11-16 23:02:10 -07:00
Conrad Irwin
2182cb2656 Ooh generics 2023-11-16 22:16:29 -07:00
Conrad Irwin
6d4276ea5f Merge branch 'main' into collab_ui2 2023-11-16 22:08:42 -07:00
Conrad Irwin
c0ad15756c More attachment configuration for context menus 2023-11-16 21:59:23 -07:00
Max Brunsfeld
e67c44a562 Fix file-reloading race condition (#3348)
### Summary

This PR fixes a bug that @as-cii and @osiewicz saw when the on-disk
contents of files changed due to running `git checkout` at the command
line. It caused a buffer's contents to diverge from the file's on disk
contents, but the buffer to show an *unmodified* status.

I've also introduced new APIs on gpui's deterministic executor, which
make it possible to write a test that reliably triggered the bug.

### Details

The bug is triggered by the following sequence of events:
1. A buffer's file changes on disk while the buffer is *unmodified*
2. Zed reloads the new content of the file
3. Before updating the buffer itself, Zed computes a *diff* between the
buffer's current contents, and the newly-loaded contents
4. While this diff is being computed, one of two things happens:
     1. the buffer changes on-disk *again*.
2. the user edits the buffer, but undoes the edit, so that the buffer
returns to an unmodified state

The bug itself was caused by a few things:
* The buffer diffing algorithm is pretty slow, because we perform a
character-wise diff
* We previously allowed multiple reload tasks to run concurrently
* When discarding an out-of-date diff, we failed to update parts of the
buffer's state (`saved_fingerprint`) which allow us to recognize that
the buffer's content differs from the file.

It was also difficult to reproduce the problem in tests, because under
deterministic execution, because it was extremely unlikely for other
tasks to make progress *after* a file had been reloaded, but *before*
the disk task has resolved. To help with testing, I introduced a pair of
executor APIs:

`spawn_labeled`, - for spawning a background task with a given *label*
`deprioritize_task` - for forcing tasks with a given label to run
*after* all other concurrent tasks.

I also made the `Model::next_event` test helper method more useful, in
that it no longer runs *until* parked in order to wait for the next
event to occur. It just steps the executor one poll at a time until the
model emits an event.

Release Notes:

- Fixed a bug that caused buffers to report incorrect modified/conflict
status when their buffers changed on disk multiple times in rapid
succession.
2023-11-16 20:19:16 -08:00
Max Brunsfeld
32979f3aca Rename deprioritize_task -> deprioritize
It applies to a family of tasks, not a task.
2023-11-16 20:03:18 -08:00
Max Brunsfeld
f3b6719c76 Rename both PlatformDispatcher::poll and Executor::run_step to 'tick'
Co-authored-by: Nathan Sobo <nathan@zed.dev>
2023-11-16 19:58:26 -08:00
Conrad Irwin
9547e88d88 TEMP 2023-11-16 19:50:31 -07:00
Conrad Irwin
547888942f Add storybook3 2023-11-16 19:42:25 -07:00
Mikayla Maki
61bd6bab09 Actions‽ (#3349)
This PR re-implements our actions with macros instead of a blanket impl.

Release Notes:

- N/A
2023-11-16 18:18:04 -08:00
Mikayla
432572c592 #RemoveThe2 2023-11-16 18:04:35 -08:00
Max Brunsfeld
17b2b112bc Don't update file's saved mtime when reload is aborted 2023-11-16 18:02:49 -08:00
Mikayla
49d3e1cc4b Add default derive 2023-11-16 17:39:05 -08:00
Mikayla
4de2c0f7ef Re-implement actions as derive macros instead of blanket impls 2023-11-16 17:32:02 -08:00
Max Brunsfeld
0bed5e4562 Port buffer reload bug fixes back to gpui1 crates 2023-11-16 16:03:14 -08:00
Max Brunsfeld
5f1acae0d3 Fix race conditions and bugs in Buffer::reload 2023-11-16 16:03:14 -08:00
Max Brunsfeld
89d73f713a Label the buffer's diff task so it can be deprioritized in tests 2023-11-16 16:03:13 -08:00
Max Brunsfeld
b2451d9dd6 Combine adjacent edits in buffer's diff 2023-11-16 16:03:05 -08:00
Conrad Irwin
074a221e0f Progress on ContextMenu 2023-11-16 16:59:27 -07:00
Max Brunsfeld
f9650b3111 Don't run until all the way until parked when waiting for a model's next event 2023-11-16 15:54:00 -08:00
Max Brunsfeld
6397c05835 Add the ability to deprioritize specific labeled tasks in tests 2023-11-16 15:54:00 -08:00
Conrad Irwin
9456f716c2 Only send one right click event 2023-11-16 15:30:53 -07:00
Nate Butler
83dfb191d6 UI size + other theme refinements (#3347)
[[PR Description]]

- Fixes bad titlebar states when the ui scale is set very low
- Tidies up a number of workspace styles

Release Notes:

- N/A
2023-11-16 16:30:47 -05:00
Nate Butler
3223e21d9f Add dock borders 2023-11-16 16:17:17 -05:00
Nate Butler
9c5f580012 Use Selected for active IconButtons 2023-11-16 16:17:10 -05:00
Nate Butler
3d8e63b93b Buttons should always use cursor_pointer 2023-11-16 16:09:11 -05:00
Nate Butler
c2d6d24952 Ensure the titlebar stays large enough even with small ui sizes 2023-11-16 16:01:42 -05:00
Nate Butler
a526f23c81 Add ui_font settings and set default rem size accordingly (#3346)
[[PR Description]]
- Adds ui_font_family, ui_font_features, ui_font_size to settings and
default settings
- Use the new ui font settings to set the rem size when the workspace is
created.

Release Notes:

- N/A
2023-11-16 15:40:33 -05:00
Conrad Irwin
267e07472d Checkpoint, MenuHandle can open one 2023-11-16 13:32:19 -07:00
Nate Butler
ffd092a098 Add ui_font_* for tests 2023-11-16 15:30:50 -05:00
Nate Butler
fa9f4a9355 Init rem_size in the workspace at the start of the render
Co-Authored-By: Mikayla Maki <mikayla.c.maki@gmail.com>
2023-11-16 13:43:36 -05:00
Mikayla
a0e976599c Salvage old distributed slice code 2023-11-16 10:32:55 -08:00
Nate Butler
08dddf0b26 Revert change to default buffer font size 2023-11-16 13:13:03 -05:00
Marshall Bowers
b559bfd80f Parameterize theme2::init to allow loading just the base theme (#3345)
This PR adds a parameter to the `theme2::init` method to indicate what
the theme-loading behavior should be.

This allows us to indicate when we want to load all of the additional
built-in user themes (like in the Zed binary and in the storybook), and
when we don't want to load the user themes (like in tests).

We're using an enum over just a `bool` here for clarity at the call
site.

Release Notes:

- N/A
2023-11-16 13:03:30 -05:00
Piotr Osiewicz
f8b91bd0f0 Fix some of the warnings 2023-11-16 18:36:19 +01:00
Conrad Irwin
d782426491 Dismiss tooltips on click 2023-11-16 10:26:09 -07:00
Conrad Irwin
4540f04dbe Add more detail to panel switcher interaction 2023-11-16 09:57:59 -07:00
Nate Butler
38d0fdc09a Remove todo
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-16 11:42:23 -05:00
Nate Butler
b2f9c454b0 Change the default buffer font size to 16
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-16 11:38:04 -05:00
Nate Butler
c71e522b4e Allow users to set UI font properties in their settings
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2023-11-16 11:37:46 -05:00
Piotr Osiewicz
6b6a30c3da Merge branch 'main' into search2 2023-11-16 17:16:15 +01:00
Kirill Bulatov
2aa7c6f2b4 Add a simple context menu into terminal2 (#3343)
Release Notes:

- N/A
2023-11-16 16:41:27 +02:00
Kirill Bulatov
ab0a3f19ab Add an actual context menu into terminal-2
(click the text, not the pane!)

Co-Authored-By: Piotr <piotr@zed.dev>
2023-11-16 16:36:44 +02:00
Antonio Scandurra
c08ce1c3b8 Start rendering autocompletion menu 2023-11-16 14:55:06 +01:00
Antonio Scandurra
781a95d2e3 Add back Completion::documentation 2023-11-16 14:47:45 +01:00
Kirill Bulatov
f638d4ce1d Add basic context menu element 2023-11-16 14:35:13 +02:00
Kirill Bulatov
0f0b7090b8 Fix more errors in terminal element 2023-11-16 13:10:43 +02:00
Kirill Bulatov
03b4c7c464 Terminal 2 (#3342)
Release Notes:

- N/A
2023-11-16 10:59:42 +02:00
Kirill Bulatov
fd61683c46 WIP 2023-11-16 10:40:02 +02:00
Kirill Bulatov
e3465fbcf9 Comment out the terminal view 2023-11-16 10:04:20 +02:00
Kirill Bulatov
a238368296 More compilation fixes 2023-11-16 10:04:18 +02:00
Kirill Bulatov
61d6cb880c Start porting terminal_element to gpui2
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-11-16 10:04:03 +02:00
Kirill Bulatov
d89c51135a Start porting terminal_view to gpui2
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-11-16 10:04:03 +02:00
Mikayla Maki
c8f83e2d4d Tabs n splits (#3340)
Adds back:

- [x] Tab clicking
- [x] Splits ~
- [x] Workspace deserialization

Also adds a new `FocusableView` API to GPUI

Release Notes:

- N/A
2023-11-15 23:52:43 -08:00
Mikayla
9da0b78ead Merge branch 'main' into tabs-n-splits 2023-11-15 23:41:25 -08:00
Mikayla
e9e1170976 Compile taffy in release mode 2023-11-15 23:15:36 -08:00
Mikayla
c70c0f9ae9 Remove initialize_workspace 2023-11-15 23:11:31 -08:00
Conrad Irwin
613add0aed collab ui2 (#3341)
Panel switching

* Also: Tooltip::text("whatever", cx);
* And: Tooltip::for_action("whatever", &collab_panel::Toggle, cx);
* And `overlay()` in Gpui2 (similar to `Overlay` in gpui).

Release Notes:

- N/A
2023-11-15 23:10:52 -07:00
Conrad Irwin
74afa62a55 Add Overlay component to gpui2 2023-11-15 23:00:36 -07:00
Conrad Irwin
8c14a8fa95 Merge branch 'main' into collab_ui2 2023-11-15 21:04:47 -07:00
Conrad Irwin
0a9fb3978b Enable panel switching 2023-11-15 21:01:00 -07:00
Nathan Sobo
e5ada92b7b Remove initialize from the Element trait (#3338)
Initially, we imagined registering keyboard handlers in the initialize
phase so we would understand the relationships between focus handles
during the layout pass, which would allow us to assign assign `focus_in`
styles that impact layout.

However, we soon realized that many elements aren't created until paint
time anyway, such as within the uniform list. Since it's impossible to
know prior to paint whether an element contains the focused element, it
makes more sense to eliminate the `focus_in` styling helper.

Release Notes:

- N/A
2023-11-15 19:36:35 -07:00
Mikayla
78cea69172 Add focusable view and restore workspace deserialization. Partially restore split and tab functions 2023-11-15 16:36:43 -08:00
Conrad Irwin
7f70712dac Uncomment enough to register the panel 2023-11-15 16:44:21 -07:00
Conrad Irwin
0e79a7f26b collab ui2 (#3339)
- Move placeholder titlebar render to collab_ui

Release Notes:

- N/A
2023-11-15 16:21:52 -07:00
Mikayla
e905ababcd Add back splitting 2023-11-15 15:21:19 -08:00
Conrad Irwin
793fa6e3a4 Move placeholder titlebar render to collab_ui 2023-11-15 16:09:21 -07:00
Conrad Irwin
84bcbf1128 Add collab_ui2 2023-11-15 16:08:03 -07:00
Mikayla
faf93aed4e checkpoint 2023-11-15 14:17:04 -08:00
Nathan Sobo
4f09633379 Remove focus_in styling helper 2023-11-15 14:17:49 -07:00
Nathan Sobo
c6b374ebc9 Remove initialize method from Element trait 2023-11-15 14:11:19 -07:00
Conrad Irwin
26d90a5e48 Leaky, but better, test abstraction (#3337)
Make test view creation a bit more ergonomic 

Release Notes:

- N/A
2023-11-15 14:09:02 -07:00
Conrad Irwin
0a51784dd0 Leaky, but better, test abstraction 2023-11-15 14:01:10 -07:00
Conrad Irwin
19c0b390d2 FileFinder tests (#3336)
Also including:
* Fixes for focus when closing the last item in a pane
* Workspace#active_item_as::<Editor>()
* cx.simulate_input()

Release Notes:

- N/A
2023-11-15 13:24:34 -07:00
Conrad Irwin
cebc8428c8 FileFinder tests 2023-11-15 13:16:28 -07:00
Antonio Scandurra
33a808a49b WIP 2023-11-15 20:41:09 +01:00
Antonio Scandurra
ffcc1cbf77 Clear the state of keystroke matchers when focus changes (#3335)
Release Notes:

- N/A
2023-11-15 20:41:01 +01:00
Antonio Scandurra
759ce7440c Avoid unnecessary call to with_element_id in RenderViewWith
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 20:12:30 +01:00
Antonio Scandurra
1d04dc5dbf Clear the state of keystroke matchers when focus changes
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 20:11:07 +01:00
Antonio Scandurra
3978d4e872 Show fold indicators (#3334)
Release Notes:

- N/A
2023-11-15 20:10:24 +01:00
Antonio Scandurra
08dfcba68a Fix panic when painting blocks 2023-11-15 20:02:35 +01:00
Antonio Scandurra
c225a3e5af Don't use Mutex or Arc now that app state is not Send
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 18:50:34 +01:00
Antonio Scandurra
c7b7f7dfd5 Move render_view into View::render_with
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 18:50:09 +01:00
Joseph T. Lyons
d791fc707a v0.114.x dev 2023-11-15 12:47:40 -05:00
Antonio Scandurra
9ff238921f Fix clicking on code action and fold indicators
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 18:29:53 +01:00
Antonio Scandurra
17b8e4a684 Handle clicking folded ranges
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 18:19:26 +01:00
Antonio Scandurra
3ff8c78b58 Return a Fold struct when querying the FoldMap
This contains a new `id` field that lets us distinguish among folds.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-11-15 17:09:59 +01:00
Piotr Osiewicz
69e01e67dc Bind cycle_mode action 2023-11-15 15:22:35 +01:00
Piotr Osiewicz
c37faf0ab3 Add query history and replace buttons 2023-11-15 15:05:48 +01:00
Antonio Scandurra
851a60a68e Render fold indicators in the gutter 2023-11-15 14:07:37 +01:00
Piotr Osiewicz
b11bfa8821 Merge branch 'main' into search2 2023-11-15 12:54:26 +01:00
Piotr Osiewicz
c14efb74d7 Finish up touchups for search UI.
Co-authored-by: Nate <nate@zed.dev>
2023-11-14 18:18:52 +01:00
Piotr Osiewicz
08dde94299 WIP styling
Co-authored-by: Nate <nate@zed.dev>
2023-11-14 17:59:53 +01:00
Piotr Osiewicz
6c69e40e5c WIP 2023-11-14 16:56:31 +01:00
Piotr Osiewicz
dfd68d4cb8 WIP: start search2 2023-11-13 20:38:37 +01:00
744 changed files with 117787 additions and 52618 deletions

12
.cargo/ci-config.toml Normal file
View File

@@ -0,0 +1,12 @@
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
# with preference for settings from Zeds config.toml.
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
# would be incovenient.
# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
[build]
rustflags = ["-D", "warnings"]

View File

@@ -0,0 +1,15 @@
name: 'Check formatting'
description: 'Checks code formatting use cargo fmt'
runs:
using: "composite"
steps:
- name: Install Rust
shell: bash -euxo pipefail {0}
run: |
rustup set profile minimal
rustup update stable
- name: cargo fmt
shell: bash -euxo pipefail {0}
run: cargo fmt --all -- --check

30
.github/actions/run_tests/action.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: "Run tests"
description: "Runs the tests"
runs:
using: "composite"
steps:
- name: Install Rust
shell: bash -euxo pipefail {0}
run: |
rustup set profile minimal
rustup update stable
rustup target add wasm32-wasi
cargo install cargo-nextest
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Limit target directory size
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
- name: Run check
shell: bash -euxo pipefail {0}
run: cargo check --tests --workspace
- name: Run tests
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast

View File

@@ -1,164 +1,149 @@
name: CI
on:
push:
branches:
- main
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
pull_request:
branches:
- "**"
push:
branches:
- main
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
pull_request:
branches:
- "**"
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
jobs:
rustfmt:
name: Check formatting
runs-on:
- self-hosted
- test
steps:
- name: Install Rust
run: |
rustup set profile minimal
rustup update stable
rustfmt:
name: Check formatting
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Set up default .cargo/config.toml
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
- name: cargo fmt
run: cargo fmt --all -- --check
- name: Run rustfmt
uses: ./.github/actions/check_formatting
tests:
name: Run tests
runs-on:
- self-hosted
- test
needs: rustfmt
env:
RUSTFLAGS: -D warnings
steps:
- name: Install Rust
run: |
rustup set profile minimal
rustup update stable
rustup target add wasm32-wasi
cargo install cargo-nextest
tests:
name: Run tests
runs-on:
- self-hosted
- test
needs: rustfmt
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Run tests
uses: ./.github/actions/run_tests
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Build collab
run: cargo build -p collab
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70
- name: Build other binaries
run: cargo build --workspace --bins --all-features
- name: Run check
run: cargo check --workspace
- name: Run tests
run: cargo nextest run --workspace --no-fail-fast
- name: Build collab
run: cargo build -p collab
- name: Build other binaries
run: cargo build --workspace --bins --all-features
bundle:
name: Bundle app
runs-on:
- self-hosted
- bundle
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
needs: tests
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
steps:
- name: Install Rust
run: |
rustup set profile minimal
rustup update stable
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
rustup target add wasm32-wasi
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
set -eu
version=$(script/get-crate-version zed)
channel=$(cat crates/zed/RELEASE_CHANNEL)
echo "Publishing version: ${version} on release channel ${channel}"
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
expected_tag_name=""
case ${channel} in
stable)
expected_tag_name="v${version}";;
preview)
expected_tag_name="v${version}-pre";;
*)
echo "can't publish a release on channel ${channel}"
exit 1;;
esac
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
- name: Generate license file
run: script/generate-licenses
- name: Create app bundle
run: script/bundle
- name: Upload app bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- uses: softprops/action-gh-release@v1
name: Upload app bundle to release
if: ${{ env.RELEASE_CHANNEL }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: target/release/Zed.dmg
body: ""
bundle:
name: Bundle app
runs-on:
- self-hosted
- bundle
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
needs: tests
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
steps:
- name: Install Rust
run: |
rustup set profile minimal
rustup update stable
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
rustup target add wasm32-wasi
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
set -eu
version=$(script/get-crate-version zed)
channel=$(cat crates/zed/RELEASE_CHANNEL)
echo "Publishing version: ${version} on release channel ${channel}"
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
expected_tag_name=""
case ${channel} in
stable)
expected_tag_name="v${version}";;
preview)
expected_tag_name="v${version}-pre";;
nightly)
expected_tag_name="v${version}-nightly";;
*)
echo "can't publish a release on channel ${channel}"
exit 1;;
esac
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
- name: Generate license file
run: script/generate-licenses
- name: Create app bundle
run: script/bundle
- name: Upload app bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- uses: softprops/action-gh-release@v1
name: Upload app bundle to release
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: target/release/Zed.dmg
body: ""
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

98
.github/workflows/release_nightly.yml vendored Normal file
View File

@@ -0,0 +1,98 @@
name: Release Nightly
on:
schedule:
# Fire every day at 1:00pm and 1:00am
- cron: "0 1,13 * * *"
push:
tags:
- "nightly"
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
jobs:
rustfmt:
name: Check formatting
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Run rustfmt
uses: ./.github/actions/check_formatting
tests:
name: Run tests
runs-on:
- self-hosted
- test
needs: rustfmt
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Run tests
uses: ./.github/actions/run_tests
bundle:
name: Bundle app
runs-on:
- self-hosted
- bundle
needs: tests
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Install Rust
run: |
rustup set profile minimal
rustup update stable
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
rustup target add wasm32-wasi
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Set release channel to nightly
run: |
set -eu
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Generate license file
run: script/generate-licenses
- name: Create app bundle
run: script/bundle -2
- name: Upload Zed Nightly
run: script/upload-nightly

817
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,16 @@
[workspace]
members = [
"crates/activity_indicator",
"crates/activity_indicator2",
"crates/ai",
"crates/assistant",
"crates/assistant2",
"crates/audio",
"crates/audio2",
"crates/auto_update",
"crates/auto_update2",
"crates/breadcrumbs",
"crates/breadcrumbs2",
"crates/call",
"crates/call2",
"crates/channel",
@@ -18,6 +22,7 @@ members = [
"crates/collab",
"crates/collab2",
"crates/collab_ui",
"crates/collab_ui2",
"crates/collections",
"crates/command_palette",
"crates/command_palette2",
@@ -31,6 +36,7 @@ members = [
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/diagnostics",
"crates/diagnostics2",
"crates/drag_and_drop",
"crates/editor",
"crates/feature_flags",
@@ -56,6 +62,7 @@ members = [
"crates/language",
"crates/language2",
"crates/language_selector",
"crates/language_selector2",
"crates/language_tools",
"crates/live_kit_client",
"crates/live_kit_server",
@@ -70,6 +77,7 @@ members = [
"crates/notifications",
"crates/notifications2",
"crates/outline",
"crates/outline2",
"crates/picker",
"crates/picker2",
"crates/plugin",
@@ -82,11 +90,17 @@ members = [
"crates/project_panel",
"crates/project_panel2",
"crates/project_symbols",
"crates/project_symbols2",
"crates/quick_action_bar2",
"crates/recent_projects",
"crates/recent_projects2",
"crates/rope",
"crates/rpc",
"crates/rpc2",
"crates/search",
"crates/search2",
"crates/semantic_index",
"crates/semantic_index2",
"crates/settings",
"crates/settings2",
"crates/snippet",
@@ -97,18 +111,22 @@ members = [
"crates/sum_tree",
"crates/terminal",
"crates/terminal2",
"crates/terminal_view2",
"crates/text",
"crates/theme",
"crates/theme2",
"crates/theme_importer",
"crates/theme_selector",
"crates/theme_selector2",
"crates/ui2",
"crates/util",
"crates/semantic_index",
"crates/story",
"crates/vim",
"crates/vcs_menu",
"crates/vcs_menu2",
"crates/workspace2",
"crates/welcome",
"crates/welcome2",
"crates/xtask",
"crates/zed",
"crates/zed2",
@@ -121,6 +139,7 @@ resolver = "2"
[workspace.dependencies]
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
# TODO: Switch back to the published version of `ctor` once:
# 1. A new version of `ctor` is published with this change: https://github.com/mmastrac/rust-ctor/pull/295
# 2. We've confirmed it's fine to update to the latest version of `ctor` (we're currently on v0.1.20).
@@ -188,9 +207,11 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42"}
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "b5f461a69bf3df7298b1903574d506179e6390b0" }
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
@@ -202,8 +223,12 @@ core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "07
[profile.dev]
split-debuginfo = "unpacked"
debug = "limited"
[profile.dev.package.taffy]
opt-level = 3
[profile.release]
debug = true
debug = "limited"
lto = "thin"
codegen-units = 1

4
Procfile.zed2 Normal file
View File

@@ -0,0 +1,4 @@
web: cd ../zed.dev && PORT=3000 npm run dev
collab: cd crates/collab2 && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve
livekit: livekit-server --dev
postgrest: postgrest crates/collab2/admin_api.conf

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00001 12L3.5 7.50001M8.00001 12L12.5 7.50001M8.00001 12L8.00001 3.00001" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.125 6.99344L6.35938 3.63281M3.125 6.99344L6.35938 10.3672M3.125 6.99344H11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 248 B

View File

@@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8906 7.00125L7.64062 3.64062M10.8906 7.00125L7.64062 10.375M10.8906 7.00125H3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 7.5L8 12M12.5 7.5L8 3M12.5 7.5L3.5 7.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 242 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99999 3.00001L12.5 7.50001M7.99999 3.00001L3.49999 7.50001M7.99999 3.00001L7.99999 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 286 B

3
assets/icons/command.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 3.625C11 2.86561 11.6156 2.25 12.375 2.25C13.1344 2.25 13.75 2.86561 13.75 3.625C13.75 4.38401 13.135 4.99939 12.3761 5C12.3758 5 12.3754 5 12.375 5H11V3.625ZM9.75 5V3.625C9.75 2.17525 10.9253 1 12.375 1C13.8247 1 15 2.17525 15 3.625C15 4.98872 13.9601 6.10955 12.63 6.23777V6.25H12.3766C12.376 6.25 12.3755 6.25 12.375 6.25H11V9.75H12.375C13.8247 9.75 15 10.9253 15 12.375C15 13.8247 13.8247 15 12.375 15C11.0113 15 9.89045 13.9601 9.76223 12.63H9.75V12.3773L9.75 12.375V11H6.25V12.375C6.25 13.8247 5.07475 15 3.625 15C2.17525 15 1 13.8247 1 12.375C1 11.0113 2.03991 9.89045 3.37 9.76223V9.75H3.62274L3.625 9.75H5L5 6.25H3.625C2.17525 6.25 1 5.07475 1 3.625C1 2.17525 2.17525 1 3.625 1C4.98872 1 6.10955 2.03991 6.23777 3.37H6.25L6.25 5L9.75 5ZM9.75 6.25L6.25 6.25L6.25 9.75H9.75V6.25ZM3.625 11H5V12.375C5 13.1344 4.38439 13.75 3.625 13.75C2.86561 13.75 2.25 13.1344 2.25 12.375C2.25 11.6162 2.86472 11.0009 3.62336 11L3.625 11ZM11 12.3766C11.0009 13.1353 11.6162 13.75 12.375 13.75C13.1344 13.75 13.75 13.1344 13.75 12.375C13.75 11.6156 13.1344 11 12.375 11H11V12.375L11 12.3766ZM3.625 5C2.86561 5 2.25 4.38439 2.25 3.625C2.25 2.86561 2.86561 2.25 3.625 2.25C4.38439 2.25 5 2.86561 5 3.625V5H3.625Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3
assets/icons/control.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 6.12488L7.64656 1.97853C7.84183 1.78328 8.1584 1.78329 8.35366 1.97854L12.5 6.12488" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 262 B

1
assets/icons/copy.svg Normal file
View File

@@ -0,0 +1 @@
<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-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>

After

Width:  |  Height:  |  Size: 338 B

3
assets/icons/option.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.35606 1.005H1.62545C1.28002 1.005 1 1.28502 1 1.63044C1 1.97587 1.28002 2.25589 1.62545 2.25589L5.35606 2.25589C5.62311 2.25589 5.8607 2.42545 5.94752 2.67799L9.75029 13.7387C10.0108 14.4963 10.7235 15.005 11.5247 15.005H14.3746C14.72 15.005 15 14.725 15 14.3796C15 14.0341 14.72 13.7541 14.3746 13.7541H11.5247C11.2576 13.7541 11.02 13.5845 10.9332 13.332L7.13046 2.27128C6.86998 1.51366 6.15721 1.005 5.35606 1.005ZM14.3745 1.005H9.75125C9.40582 1.005 9.1258 1.28502 9.1258 1.63044C9.1258 1.97587 9.40582 2.25589 9.75125 2.25589L14.3745 2.25589C14.72 2.25589 15 1.97587 15 1.63044C15 1.28502 14.72 1.005 14.3745 1.005Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 792 B

3
assets/icons/return.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.375 1.63C8.375 1.28482 8.65482 1.005 9 1.005H12.375C13.8247 1.005 15 2.18025 15 3.63V7.625C15 9.07474 13.8247 10.25 12.375 10.25H3.13388L6.07194 13.1881C6.31602 13.4321 6.31602 13.8279 6.07194 14.0719C5.82786 14.316 5.43214 14.316 5.18806 14.0719L1.18306 10.0669C0.938981 9.82286 0.938981 9.42714 1.18306 9.18306L5.18306 5.18306C5.42714 4.93898 5.82286 4.93898 6.06694 5.18306C6.31102 5.42714 6.31102 5.82286 6.06694 6.06694L3.13388 9H12.375C13.1344 9 13.75 8.38439 13.75 7.625V3.63C13.75 2.87061 13.1344 2.255 12.375 2.255H9C8.65482 2.255 8.375 1.97518 8.375 1.63Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

3
assets/icons/shift.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.46475 7.99652L7.85304 2.15921C7.93223 2.07342 8.06777 2.07341 8.14696 2.15921L13.5352 7.99652C13.7126 8.18869 13.5763 8.5 13.3148 8.5H10.5V13.7C10.5 13.8657 10.3657 14 10.2 14H5.8C5.63431 14 5.5 13.8657 5.5 13.7V8.5H2.6852C2.42367 8.5 2.28737 8.18869 2.46475 7.99652Z" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -1,6 +1 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.45563 12.3438H11.5444C11.9137 12.3438 12.1556 11.9571 11.994 11.625L10.2346 8.00952C9.77174 7.05841 8.89104 6.37821 7.85383 6.17077C7.29019 6.05804 6.70981 6.05804 6.14617 6.17077C5.10896 6.37821 4.22826 7.05841 3.76542 8.00952L2.00603 11.625C1.84442 11.9571 2.08628 12.3438 2.45563 12.3438Z" fill="#001A33" fill-opacity="0.157"/>
<path d="M9.5 6.5L11.994 11.625C12.1556 11.9571 11.9137 12.3438 11.5444 12.3438H2.45563C2.08628 12.3438 1.84442 11.9571 2.00603 11.625L4.5 6.5" stroke="#11181C" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 7L7 2" stroke="#11181C" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="7" cy="9.24219" r="0.75" fill="#11181C"/>
</svg>
<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-alert-triangle"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>

Before

Width:  |  Height:  |  Size: 835 B

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -17,18 +17,9 @@
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem",
"alt-cmd-left": "pane::ActivatePrevItem",
"alt-cmd-right": "pane::ActivateNextItem",
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": "pane::CloseInactiveItems",
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k u": "pane::CloseCleanItems",
"cmd-k cmd-w": "pane::CloseAllItems",
"cmd-shift-w": "workspace::CloseWindow",
"cmd-s": "workspace::Save",
"cmd-shift-s": "workspace::SaveAs",
"shift-escape": "workspace::ToggleZoom",
"cmd-o": "workspace::Open",
"cmd-=": "zed::IncreaseBufferFontSize",
"cmd-+": "zed::IncreaseBufferFontSize",
"cmd--": "zed::DecreaseBufferFontSize",
@@ -38,15 +29,7 @@
"cmd-h": "zed::Hide",
"alt-cmd-h": "zed::HideOthers",
"cmd-m": "zed::Minimize",
"ctrl-cmd-f": "zed::ToggleFullScreen",
"cmd-n": "workspace::NewFile",
"cmd-shift-n": "workspace::NewWindow",
"cmd-o": "workspace::Open",
"alt-cmd-o": "projects::OpenRecent",
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"ctrl-`": "terminal_panel::ToggleFocus",
"shift-escape": "workspace::ToggleZoom"
"ctrl-cmd-f": "zed::ToggleFullScreen"
}
},
{
@@ -284,6 +267,15 @@
{
"context": "Pane",
"bindings": {
"cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem",
"alt-cmd-left": "pane::ActivatePrevItem",
"alt-cmd-right": "pane::ActivateNextItem",
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": "pane::CloseInactiveItems",
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k u": "pane::CloseCleanItems",
"cmd-k cmd-w": "pane::CloseAllItems",
"cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch",
@@ -389,6 +381,14 @@
{
"context": "Workspace",
"bindings": {
"alt-cmd-o": "projects::OpenRecent",
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"cmd-s": "workspace::Save",
"cmd-shift-s": "workspace::SaveAs",
"cmd-n": "workspace::NewFile",
"cmd-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"cmd-1": ["workspace::ActivatePane", 0],
"cmd-2": ["workspace::ActivatePane", 1],
"cmd-3": ["workspace::ActivatePane", 2],
@@ -530,12 +530,17 @@
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"space": "project_panel::Open",
"backspace": "project_panel::Delete",
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
{
"context": "ProjectPanel && not_editing",
"bindings": {
"space": "project_panel::Open"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {

View File

@@ -35,6 +35,15 @@
// "custom": 2
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
"ui_font_family": "Zed Mono",
// The OpenType features to enable for text in the UI
"ui_font_features": {
// Disable ligatures:
"calt": false
},
// The default font size for text in the UI
"ui_font_size": 16,
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
@@ -134,7 +143,11 @@
// Whether to show the git status in the project panel.
"git_status": true,
// Amount of indentation for nested items.
"indent_size": 20
"indent_size": 20,
// Whether to reveal it in the project panel automatically,
// when a corresponding project entry becomes active.
// Gitignored entries are never auto revealed.
"auto_reveal_entries": true
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
@@ -259,6 +272,19 @@
// Whether to show warnings or not by default.
"include_warnings": true
},
// Add files or globs of files that will be excluded by Zed entirely:
// they will be skipped during FS scan(s), file tree and file search
// will lack the corresponding file entries.
"file_scan_exclusions": [
"**/.git",
"**/.svn",
"**/.hg",
"**/CVS",
"**/.DS_Store",
"**/Thumbs.db",
"**/.classpath",
"**/.settings"
],
// Git gutter behavior configuration.
"git": {
// Control whether the git gutter is shown. May take 2 values:

View File

@@ -7,5 +7,6 @@
// custom settings, run the `open default settings` command
// from the command palette or from `Zed` application menu.
{
"buffer_font_size": 15
"ui_font_size": 16,
"buffer_font_size": 16
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -17,5 +17,8 @@
"file_name": "rose-pine-dawn.json",
"appearance": "light"
}
]
],
"syntax": {
"function": ["entity.name"]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
[package]
name = "activity_indicator2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/activity_indicator.rs"
doctest = false
[dependencies]
auto_update = { path = "../auto_update2", package = "auto_update2" }
editor = { path = "../editor2", package = "editor2" }
language = { path = "../language2", package = "language2" }
gpui = { path = "../gpui2", package = "gpui2" }
project = { path = "../project2", package = "project2" }
settings = { path = "../settings2", package = "settings2" }
ui = { path = "../ui2", package = "ui2" }
util = { path = "../util" }
theme = { path = "../theme2", package = "theme2" }
workspace = { path = "../workspace2", package = "workspace2" }
anyhow.workspace = true
futures.workspace = true
smallvec.workspace = true
[dev-dependencies]
editor = { path = "../editor2", package = "editor2", features = ["test-support"] }

View File

@@ -0,0 +1,333 @@
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor;
use futures::StreamExt;
use gpui::{
actions, svg, AppContext, CursorStyle, Div, EventEmitter, InteractiveElement as _, Model,
ParentElement as _, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, View,
ViewContext, VisualContext as _,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus};
use project::{LanguageServerProgress, Project};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc};
use ui::h_stack;
use util::ResultExt;
use workspace::{item::ItemHandle, StatusItemView, Workspace};
actions!(activity_indicator, [ShowErrorMessage]);
const DOWNLOAD_ICON: &str = "icons/download.svg";
const WARNING_ICON: &str = "icons/warning.svg";
pub enum Event {
ShowError { lsp_name: Arc<str>, error: String },
}
pub struct ActivityIndicator {
statuses: Vec<LspStatus>,
project: Model<Project>,
auto_updater: Option<Model<AutoUpdater>>,
}
struct LspStatus {
name: Arc<str>,
status: LanguageServerBinaryStatus,
}
struct PendingWork<'a> {
language_server_name: &'a str,
progress_token: &'a str,
progress: &'a LanguageServerProgress,
}
#[derive(Default)]
struct Content {
icon: Option<&'static str>,
message: String,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
}
impl ActivityIndicator {
pub fn new(
workspace: &mut Workspace,
languages: Arc<LanguageRegistry>,
cx: &mut ViewContext<Workspace>,
) -> View<ActivityIndicator> {
let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx);
let this = cx.build_view(|cx: &mut ViewContext<Self>| {
let mut status_events = languages.language_server_binary_statuses();
cx.spawn(|this, mut cx| async move {
while let Some((language, event)) = status_events.next().await {
this.update(&mut cx, |this, cx| {
this.statuses.retain(|s| s.name != language.name());
this.statuses.push(LspStatus {
name: language.name(),
status: event,
});
cx.notify();
})?;
}
anyhow::Ok(())
})
.detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach();
if let Some(auto_updater) = auto_updater.as_ref() {
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
}
// cx.observe_active_labeled_tasks(|_, cx| cx.notify())
// .detach();
Self {
statuses: Default::default(),
project: project.clone(),
auto_updater,
}
});
cx.subscribe(&this, move |workspace, _, event, cx| match event {
Event::ShowError { lsp_name, error } => {
if let Some(buffer) = project
.update(cx, |project, cx| project.create_buffer(error, None, cx))
.log_err()
{
buffer.update(cx, |buffer, cx| {
buffer.edit(
[(0..0, format!("Language server error: {}\n\n", lsp_name))],
None,
cx,
);
});
workspace.add_item(
Box::new(cx.build_view(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), cx)
})),
cx,
);
}
}
})
.detach();
this
}
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 {
lsp_name: status.name.clone(),
error: error.clone(),
});
false
} else {
true
}
});
cx.notify();
}
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);
});
}
cx.notify();
}
fn pending_language_server_work<'a>(
&self,
cx: &'a AppContext,
) -> impl Iterator<Item = PendingWork<'a>> {
self.project
.read(cx)
.language_server_statuses()
.rev()
.filter_map(|status| {
if status.pending_work.is_empty() {
None
} else {
let mut pending_work = status
.pending_work
.iter()
.map(|(token, progress)| PendingWork {
language_server_name: status.name.as_str(),
progress_token: token.as_str(),
progress,
})
.collect::<SmallVec<[_; 4]>>();
pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));
Some(pending_work)
}
})
.flatten()
}
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
// Show any language server has pending activity.
let mut pending_work = self.pending_language_server_work(cx);
if let Some(PendingWork {
language_server_name,
progress_token,
progress,
}) = pending_work.next()
{
let mut message = language_server_name.to_string();
message.push_str(": ");
if let Some(progress_message) = progress.message.as_ref() {
message.push_str(progress_message);
} else {
message.push_str(progress_token);
}
if let Some(percentage) = progress.percentage {
write!(&mut message, " ({}%)", percentage).unwrap();
}
let additional_work_count = pending_work.count();
if additional_work_count > 0 {
write!(&mut message, " + {} more", additional_work_count).unwrap();
}
return Content {
icon: None,
message,
on_click: None,
};
}
// Show any language server installation info.
let mut downloading = SmallVec::<[_; 3]>::new();
let mut checking_for_update = SmallVec::<[_; 3]>::new();
let mut failed = SmallVec::<[_; 3]>::new();
for status in &self.statuses {
let name = status.name.clone();
match status.status {
LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name),
LanguageServerBinaryStatus::Downloading => downloading.push(name),
LanguageServerBinaryStatus::Failed { .. } => failed.push(name),
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
}
}
if !downloading.is_empty() {
return Content {
icon: Some(DOWNLOAD_ICON),
message: format!(
"Downloading {} language server{}...",
downloading.join(", "),
if downloading.len() > 1 { "s" } else { "" }
),
on_click: None,
};
} else if !checking_for_update.is_empty() {
return Content {
icon: Some(DOWNLOAD_ICON),
message: format!(
"Checking for updates to {} language server{}...",
checking_for_update.join(", "),
if checking_for_update.len() > 1 {
"s"
} else {
""
}
),
on_click: None,
};
} else if !failed.is_empty() {
return Content {
icon: Some(WARNING_ICON),
message: format!(
"Failed to download {} language server{}. Click to show error.",
failed.join(", "),
if failed.len() > 1 { "s" } else { "" }
),
on_click: Some(Arc::new(|this, cx| {
this.show_error_message(&Default::default(), cx)
})),
};
}
// Show any application auto-update info.
if let Some(updater) = &self.auto_updater {
return match &updater.read(cx).status() {
AutoUpdateStatus::Checking => Content {
icon: Some(DOWNLOAD_ICON),
message: "Checking for Zed updates…".to_string(),
on_click: None,
},
AutoUpdateStatus::Downloading => Content {
icon: Some(DOWNLOAD_ICON),
message: "Downloading Zed update…".to_string(),
on_click: None,
},
AutoUpdateStatus::Installing => Content {
icon: Some(DOWNLOAD_ICON),
message: "Installing Zed update…".to_string(),
on_click: None,
},
AutoUpdateStatus::Updated => Content {
icon: None,
message: "Click to restart and update Zed".to_string(),
on_click: Some(Arc::new(|_, cx| {
workspace::restart(&Default::default(), cx)
})),
},
AutoUpdateStatus::Errored => Content {
icon: Some(WARNING_ICON),
message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&Default::default(), cx)
})),
},
AutoUpdateStatus::Idle => Default::default(),
};
}
// todo!(show active tasks)
// if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
// return Content {
// icon: None,
// message: most_recent_active_task.to_string(),
// on_click: None,
// };
// }
Default::default()
}
}
impl EventEmitter<Event> for ActivityIndicator {}
impl Render for ActivityIndicator {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let content = self.content_to_render(cx);
let mut result = h_stack()
.id("activity-indicator")
.on_action(cx.listener(Self::show_error_message))
.on_action(cx.listener(Self::dismiss_error_message));
if let Some(on_click) = content.on_click {
result = result
.cursor(CursorStyle::PointingHand)
.on_click(cx.listener(move |this, _, cx| {
on_click(this, cx);
}))
}
result
.children(content.icon.map(|icon| svg().path(icon)))
.child(SharedString::from(content.message))
}
}
impl StatusItemView for ActivityIndicator {
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
}

View File

@@ -7,7 +7,7 @@ pub enum ProviderCredential {
NotNeeded,
}
pub trait CredentialProvider {
pub trait CredentialProvider: Send + Sync {
fn has_credentials(&self) -> bool;
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);

View File

@@ -104,7 +104,7 @@ pub struct OpenAIResponseStreamEvent {
pub async fn stream_completion(
credential: ProviderCredential,
executor: Arc<BackgroundExecutor>,
executor: BackgroundExecutor,
request: Box<dyn CompletionRequest>,
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
let api_key = match credential {
@@ -197,11 +197,11 @@ pub async fn stream_completion(
pub struct OpenAICompletionProvider {
model: OpenAILanguageModel,
credential: Arc<RwLock<ProviderCredential>>,
executor: Arc<BackgroundExecutor>,
executor: BackgroundExecutor,
}
impl OpenAICompletionProvider {
pub fn new(model_name: &str, executor: Arc<BackgroundExecutor>) -> Self {
pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self {
let model = OpenAILanguageModel::load(model_name);
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
Self {

View File

@@ -35,7 +35,7 @@ pub struct OpenAIEmbeddingProvider {
model: OpenAILanguageModel,
credential: Arc<RwLock<ProviderCredential>>,
pub client: Arc<dyn HttpClient>,
pub executor: Arc<BackgroundExecutor>,
pub executor: BackgroundExecutor,
rate_limit_count_rx: watch::Receiver<Option<Instant>>,
rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
}
@@ -66,7 +66,7 @@ struct OpenAIEmbeddingUsage {
}
impl OpenAIEmbeddingProvider {
pub fn new(client: Arc<dyn HttpClient>, executor: Arc<BackgroundExecutor>) -> Self {
pub fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));

View File

@@ -15,7 +15,7 @@ use ai::{
use ai::prompts::repository_context::PromptCodeSnippet;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Local};
use client::{telemetry::AssistantKind, ClickhouseEvent, TelemetrySettings};
use client::{telemetry::AssistantKind, TelemetrySettings};
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
display_map::{
@@ -1218,6 +1218,31 @@ impl View for AssistantPanel {
let style = &theme.assistant;
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
Flex::column()
.with_child(
Text::new(
"To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
style.api_key_prompt.text.clone(),
),
)
.with_child(
Text::new(
" - Having a subscription for another service like GitHub Copilot won't work.",
style.api_key_prompt.text.clone(),
),
)
.with_child(
Text::new(
" - You can create a api key at: platform.openai.com/api-keys",
style.api_key_prompt.text.clone(),
),
)
.with_child(
Text::new(
" ",
style.api_key_prompt.text.clone(),
)
.aligned(),
)
.with_child(
Text::new(
"Paste your OpenAI API key and press Enter to use the assistant",
@@ -1231,6 +1256,20 @@ impl View for AssistantPanel {
.with_style(style.api_key_editor.container)
.aligned(),
)
.with_child(
Text::new(
" ",
style.api_key_prompt.text.clone(),
)
.aligned(),
)
.with_child(
Text::new(
"Click on the Z button in the status bar to close this panel.",
style.api_key_prompt.text.clone(),
)
.aligned(),
)
.contained()
.with_style(style.api_key_prompt.container)
.aligned()
@@ -3803,12 +3842,12 @@ fn report_assistant_event(
.default_open_ai_model
.clone();
let event = ClickhouseEvent::Assistant {
conversation_id,
kind: assistant_kind,
model: model.full_name(),
};
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
telemetry.report_clickhouse_event(event, telemetry_settings)
telemetry.report_assistant_event(
telemetry_settings,
conversation_id,
assistant_kind,
model.full_name(),
)
}

View File

@@ -0,0 +1,54 @@
[package]
name = "assistant2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/assistant.rs"
doctest = false
[dependencies]
ai = { package = "ai2", path = "../ai2" }
client = { package = "client2", path = "../client2" }
collections = { path = "../collections"}
editor = { package = "editor2", path = "../editor2" }
fs = { package = "fs2", path = "../fs2" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
menu = { package = "menu2", path = "../menu2" }
multi_buffer = { package = "multi_buffer2", path = "../multi_buffer2" }
project = { package = "project2", path = "../project2" }
search = { package = "search2", path = "../search2" }
semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
workspace = { package = "workspace2", path = "../workspace2" }
uuid.workspace = true
log.workspace = true
anyhow.workspace = true
chrono = { version = "0.4", features = ["serde"] }
futures.workspace = true
indoc.workspace = true
isahc.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true
regex.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
tiktoken-rs.workspace = true
[dev-dependencies]
ai = { package = "ai2", path = "../ai2", features = ["test-support"]}
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
project = { package = "project2", path = "../project2", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
log.workspace = true
rand.workspace = true

View File

@@ -0,0 +1,63 @@
> Hello, I'm Nathan Sobo.
I'm currently your sole creator and the leader of Zed Industries.
Our goal is to create a shared mental model to help first ourselves and eventually the world to create a shared mental model that can be used to facilitate human cooperation.
< Hello, Nathan! Welcome back to Zed. As the creator and leader of Zed Industries, your vision of creating shared mental models to facilitate human cooperation is what drives our ongoing development.
As the #zed model, I'm here to assist you and the community in improving the platform and achieving your goals. Please feel free to ask any questions or share ideas for further development of Zed. Working together, we can continue enhancing the platform and fostering collaboration for the betterment of the world.
> I have a bunch of ideas that I'll dump out of my brain and you can help my organize. Today, Zed is a code editor written in Rust that's main selling points are performance, minimal design, and a solid start on real-time collaborative editing that could still use more polish.
We've decided that our next milestone will be open-sourcing Zed on the Zed platform.
< Nathan, it's exciting to hear that you have more ideas to enhance Zed! Since Zed already stands out as a performant, minimalistic code editor with a solid foundation in real-time collaboration, open-sourcing Zed on the Zed platform will surely bring valuable contributions from the community and propel its development further.
Let's organize your ideas and create a plan for your next milestone. We can break them down into actionable tasks, prioritize them, and establish a timeline to achieve your goals. Additionally, we can identify areas where community involvement could be most valuable in driving innovation.
Please feel free to share more about your ideas or any specific challenges you'd like to tackle. I'm here to help you turn these ideas into a cohesive and successful plan for Zed's future!
@nathan> #zed, can you describe the conversation UI as you currently understand it.
< Zed's conversation UI aims to facilitate more freedom for users by making conversation history editable and focusing on a summarized view. Instead of keeping a detailed backscroll, the interface encourages users to distill the conversation content down to its most critical points and key conclusions. This approach ensures that the vital information from the conversation is highlighted and easily accessible, while still maintaining a history that can be linked to when necessary.
Addressing @nate's concerns, this design choice aims to strike a balance between readability, usability, and information preservation. The summarized view maintains the essence of the conversation without being burdened by extensive backscroll. Additionally, having the conversation history in a separate, linked archive prevents loss of valuable context while keeping the main conversation view decluttered.
@nathan> What details around the conversation view are worth exploring?
< @nathan, there are several aspects of the conversation view that are worth exploring and refining:
1. Editing functionality: Ensuring that all participants can edit and refine the conversation summary effectively while preserving the original context.
2. Permissions and access control: Determining how to manage editing rights, user roles, and visibility of the conversation summary and history.
3. Navigation and linking: Implementing an intuitive system for navigating between the summarized conversation and linked history or reference materials.
4. Visualization and layout: Designing the interface to clearly present the summarized information while providing a visually engaging and organized experience for participants.
5. Threaded conversations and branching: Exploring ways to handle sub-conversations, supplementary discussions, or debates within the primary conversation view.
Considering these aspects will ensure our conversation view design is optimized for usability, collaboration, and accessibility in Zed.
@nathan> @nate, which of these would you like to explore?
@nate> 2 feels like it isn't important at the moment, we can explore that later. Let's start with 4, which I think will lead us to discussion 3 and 5.
#zed share your thoughts on the points we need to consider to design a layout and visualization for a conversation view between you (#zed) and multuple peoople, or between multiple people and multiple bots (you and other bots).
@nathan> Agreed. I'm interested in threading I think more than anything. Or 4 yeah. I think we need to scope the threading conversation. Also, asking #zed to propose the solution... not sure it will be that effective but it's worth a try...
#zed describe a hypothetical conversation UI that meets the description.
< @nate and @nathan, I understand that you'd like to focus on the layout and visualization for a conversation view that can accommodate multiple people and potentially multiple bots as well. Here's a proposal for a conversation UI that meets the requirements:
1. Main conversation area: Display the summarized conversation content in a clean and organized manner, ensuring readability and efficient use of space.
2. Participant avatars and labels: Include user avatars and/or labels to distinguish between users and bots visually, allowing easy identification of each participant's contributions.
3. Active threads indicator: Designate a clear indicator for ongoing threads, which will attract attention to active sub-conversations or related discussions in the conversation view.
4. Dynamic branching: Implement a branching visual structure to present threaded conversations, clearly distinguishing sub-conversations, and enabling users to navigate between them smoothly.
5. Multiple bot interaction support: Facilitate interactions between multiple bots through user-invoked prompts or bot-to-bot communication channels without disrupting the primary conversation flow.
6. Expandable conversation history: Offer a separate, expandable section or panel to access historical content without cluttering the main conversation view.
7. Linking and referencing: Implement a system for hyperlinking relevant information, resources, or previous conversations to the current discussion, making it easy for participants to follow and reference the shared context.
This hypothetical conversation UI aims to address the layout and visualization, threading, and multi-participant aspects of your requirements. Of course, it's just an initial proposal, and we can refine and iterate on it based on your feedback and specific needs.

View File

@@ -0,0 +1,3 @@
Push content to a deeper layer.
A context can have multiple sublayers.
You can enable or disable arbitrary sublayers at arbitrary nesting depths when viewing the document.

View File

@@ -0,0 +1,129 @@
pub mod assistant_panel;
mod assistant_settings;
mod codegen;
mod prompts;
mod streaming_diff;
use ai::providers::open_ai::Role;
use anyhow::Result;
pub use assistant_panel::AssistantPanel;
use assistant_settings::OpenAIModel;
use chrono::{DateTime, Local};
use collections::HashMap;
use fs::Fs;
use futures::StreamExt;
use gpui::{actions, AppContext, SharedString};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
use util::paths::CONVERSATIONS_DIR;
actions!(
assistant,
[
NewConversation,
Assist,
Split,
CycleMessageRole,
QuoteSelection,
ToggleFocus,
ResetKey,
InlineAssist,
ToggleIncludeConversation,
ToggleRetrieveContext,
]
);
#[derive(
Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
struct MessageId(usize);
#[derive(Clone, Debug, Serialize, Deserialize)]
struct MessageMetadata {
role: Role,
sent_at: DateTime<Local>,
status: MessageStatus,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum MessageStatus {
Pending,
Done,
Error(SharedString),
}
#[derive(Serialize, Deserialize)]
struct SavedMessage {
id: MessageId,
start: usize,
}
#[derive(Serialize, Deserialize)]
struct SavedConversation {
id: Option<String>,
zed: String,
version: String,
text: String,
messages: Vec<SavedMessage>,
message_metadata: HashMap<MessageId, MessageMetadata>,
summary: String,
model: OpenAIModel,
}
impl SavedConversation {
const VERSION: &'static str = "0.1.0";
}
struct SavedConversationMetadata {
title: String,
path: PathBuf,
mtime: chrono::DateTime<chrono::Local>,
}
impl SavedConversationMetadata {
pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
fs.create_dir(&CONVERSATIONS_DIR).await?;
let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
let mut conversations = Vec::<SavedConversationMetadata>::new();
while let Some(path) = paths.next().await {
let path = path?;
if path.extension() != Some(OsStr::new("json")) {
continue;
}
let pattern = r" - \d+.zed.json$";
let re = Regex::new(pattern).unwrap();
let metadata = fs.metadata(&path).await?;
if let Some((file_name, metadata)) = path
.file_name()
.and_then(|name| name.to_str())
.zip(metadata)
{
let title = re.replace(file_name, "");
conversations.push(Self {
title: title.into_owned(),
path,
mtime: metadata.mtime.into(),
});
}
}
conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));
Ok(conversations)
}
}
pub fn init(cx: &mut AppContext) {
assistant_panel::init(cx);
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
use anyhow;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum OpenAIModel {
#[serde(rename = "gpt-3.5-turbo-0613")]
ThreePointFiveTurbo,
#[serde(rename = "gpt-4-0613")]
Four,
#[serde(rename = "gpt-4-1106-preview")]
FourTurbo,
}
impl OpenAIModel {
pub fn full_name(&self) -> &'static str {
match self {
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
OpenAIModel::Four => "gpt-4-0613",
OpenAIModel::FourTurbo => "gpt-4-1106-preview",
}
}
pub fn short_name(&self) -> &'static str {
match self {
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
OpenAIModel::Four => "gpt-4",
OpenAIModel::FourTurbo => "gpt-4-turbo",
}
}
pub fn cycle(&self) -> Self {
match self {
OpenAIModel::ThreePointFiveTurbo => OpenAIModel::Four,
OpenAIModel::Four => OpenAIModel::FourTurbo,
OpenAIModel::FourTurbo => OpenAIModel::ThreePointFiveTurbo,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AssistantDockPosition {
Left,
Right,
Bottom,
}
#[derive(Deserialize, Debug)]
pub struct AssistantSettings {
pub button: bool,
pub dock: AssistantDockPosition,
pub default_width: f32,
pub default_height: f32,
pub default_open_ai_model: OpenAIModel,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct AssistantSettingsContent {
pub button: Option<bool>,
pub dock: Option<AssistantDockPosition>,
pub default_width: Option<f32>,
pub default_height: Option<f32>,
pub default_open_ai_model: Option<OpenAIModel>,
}
impl Settings for AssistantSettings {
const KEY: Option<&'static str> = Some("assistant");
type FileContent = AssistantSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
}
}

View File

@@ -0,0 +1,688 @@
use crate::streaming_diff::{Hunk, StreamingDiff};
use ai::completion::{CompletionProvider, CompletionRequest};
use anyhow::Result;
use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
use gpui::{EventEmitter, Model, ModelContext, Task};
use language::{Rope, TransactionId};
use multi_buffer;
use std::{cmp, future, ops::Range, sync::Arc};
pub enum Event {
Finished,
Undone,
}
#[derive(Clone)]
pub enum CodegenKind {
Transform { range: Range<Anchor> },
Generate { position: Anchor },
}
pub struct Codegen {
provider: Arc<dyn CompletionProvider>,
buffer: Model<MultiBuffer>,
snapshot: MultiBufferSnapshot,
kind: CodegenKind,
last_equal_ranges: Vec<Range<Anchor>>,
transaction_id: Option<TransactionId>,
error: Option<anyhow::Error>,
generation: Task<()>,
idle: bool,
_subscription: gpui::Subscription,
}
impl EventEmitter<Event> for Codegen {}
impl Codegen {
pub fn new(
buffer: Model<MultiBuffer>,
kind: CodegenKind,
provider: Arc<dyn CompletionProvider>,
cx: &mut ModelContext<Self>,
) -> Self {
let snapshot = buffer.read(cx).snapshot(cx);
Self {
provider,
buffer: buffer.clone(),
snapshot,
kind,
last_equal_ranges: Default::default(),
transaction_id: Default::default(),
error: Default::default(),
idle: true,
generation: Task::ready(()),
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
}
}
fn handle_buffer_event(
&mut self,
_buffer: Model<MultiBuffer>,
event: &multi_buffer::Event,
cx: &mut ModelContext<Self>,
) {
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
if self.transaction_id == Some(*transaction_id) {
self.transaction_id = None;
self.generation = Task::ready(());
cx.emit(Event::Undone);
}
}
}
pub fn range(&self) -> Range<Anchor> {
match &self.kind {
CodegenKind::Transform { range } => range.clone(),
CodegenKind::Generate { position } => position.bias_left(&self.snapshot)..*position,
}
}
pub fn kind(&self) -> &CodegenKind {
&self.kind
}
pub fn last_equal_ranges(&self) -> &[Range<Anchor>] {
&self.last_equal_ranges
}
pub fn idle(&self) -> bool {
self.idle
}
pub fn error(&self) -> Option<&anyhow::Error> {
self.error.as_ref()
}
pub fn start(&mut self, prompt: Box<dyn CompletionRequest>, cx: &mut ModelContext<Self>) {
let range = self.range();
let snapshot = self.snapshot.clone();
let selected_text = snapshot
.text_for_range(range.start..range.end)
.collect::<Rope>();
let selection_start = range.start.to_point(&snapshot);
let suggested_line_indent = snapshot
.suggested_indents(selection_start.row..selection_start.row + 1, cx)
.into_values()
.next()
.unwrap_or_else(|| snapshot.indent_size_for_line(selection_start.row));
let response = self.provider.complete(prompt);
self.generation = cx.spawn(|this, mut cx| {
async move {
let generate = async {
let mut edit_start = range.start.to_offset(&snapshot);
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
let diff = cx.background_executor().spawn(async move {
let chunks = strip_invalid_spans_from_codeblock(response.await?);
futures::pin_mut!(chunks);
let mut diff = StreamingDiff::new(selected_text.to_string());
let mut new_text = String::new();
let mut base_indent = None;
let mut line_indent = None;
let mut first_line = true;
while let Some(chunk) = chunks.next().await {
let chunk = chunk?;
let mut lines = chunk.split('\n').peekable();
while let Some(line) = lines.next() {
new_text.push_str(line);
if line_indent.is_none() {
if let Some(non_whitespace_ch_ix) =
new_text.find(|ch: char| !ch.is_whitespace())
{
line_indent = Some(non_whitespace_ch_ix);
base_indent = base_indent.or(line_indent);
let line_indent = line_indent.unwrap();
let base_indent = base_indent.unwrap();
let indent_delta = line_indent as i32 - base_indent as i32;
let mut corrected_indent_len = cmp::max(
0,
suggested_line_indent.len as i32 + indent_delta,
)
as usize;
if first_line {
corrected_indent_len = corrected_indent_len
.saturating_sub(selection_start.column as usize);
}
let indent_char = suggested_line_indent.char();
let mut indent_buffer = [0; 4];
let indent_str =
indent_char.encode_utf8(&mut indent_buffer);
new_text.replace_range(
..line_indent,
&indent_str.repeat(corrected_indent_len),
);
}
}
if line_indent.is_some() {
hunks_tx.send(diff.push_new(&new_text)).await?;
new_text.clear();
}
if lines.peek().is_some() {
hunks_tx.send(diff.push_new("\n")).await?;
line_indent = None;
first_line = false;
}
}
}
hunks_tx.send(diff.push_new(&new_text)).await?;
hunks_tx.send(diff.finish()).await?;
anyhow::Ok(())
});
while let Some(hunks) = hunks_rx.next().await {
this.update(&mut cx, |this, cx| {
this.last_equal_ranges.clear();
let transaction = this.buffer.update(cx, |buffer, cx| {
// Avoid grouping assistant edits with user edits.
buffer.finalize_last_transaction(cx);
buffer.start_transaction(cx);
buffer.edit(
hunks.into_iter().filter_map(|hunk| match hunk {
Hunk::Insert { text } => {
let edit_start = snapshot.anchor_after(edit_start);
Some((edit_start..edit_start, text))
}
Hunk::Remove { len } => {
let edit_end = edit_start + len;
let edit_range = snapshot.anchor_after(edit_start)
..snapshot.anchor_before(edit_end);
edit_start = edit_end;
Some((edit_range, String::new()))
}
Hunk::Keep { len } => {
let edit_end = edit_start + len;
let edit_range = snapshot.anchor_after(edit_start)
..snapshot.anchor_before(edit_end);
edit_start = edit_end;
this.last_equal_ranges.push(edit_range);
None
}
}),
None,
cx,
);
buffer.end_transaction(cx)
});
if let Some(transaction) = transaction {
if let Some(first_transaction) = this.transaction_id {
// Group all assistant edits into the first transaction.
this.buffer.update(cx, |buffer, cx| {
buffer.merge_transactions(
transaction,
first_transaction,
cx,
)
});
} else {
this.transaction_id = Some(transaction);
this.buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction(cx)
});
}
}
cx.notify();
})?;
}
diff.await?;
anyhow::Ok(())
};
let result = generate.await;
this.update(&mut cx, |this, cx| {
this.last_equal_ranges.clear();
this.idle = true;
if let Err(error) = result {
this.error = Some(error);
}
cx.emit(Event::Finished);
cx.notify();
})
.ok();
}
});
self.error.take();
self.idle = false;
cx.notify();
}
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction_id) = self.transaction_id {
self.buffer
.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
}
}
}
fn strip_invalid_spans_from_codeblock(
stream: impl Stream<Item = Result<String>>,
) -> impl Stream<Item = Result<String>> {
let mut first_line = true;
let mut buffer = String::new();
let mut starts_with_markdown_codeblock = false;
let mut includes_start_or_end_span = false;
stream.filter_map(move |chunk| {
let chunk = match chunk {
Ok(chunk) => chunk,
Err(err) => return future::ready(Some(Err(err))),
};
buffer.push_str(&chunk);
if buffer.len() > "<|S|".len() && buffer.starts_with("<|S|") {
includes_start_or_end_span = true;
buffer = buffer
.strip_prefix("<|S|>")
.or_else(|| buffer.strip_prefix("<|S|"))
.unwrap_or(&buffer)
.to_string();
} else if buffer.ends_with("|E|>") {
includes_start_or_end_span = true;
} else if buffer.starts_with("<|")
|| buffer.starts_with("<|S")
|| buffer.starts_with("<|S|")
|| buffer.ends_with("|")
|| buffer.ends_with("|E")
|| buffer.ends_with("|E|")
{
return future::ready(None);
}
if first_line {
if buffer == "" || buffer == "`" || buffer == "``" {
return future::ready(None);
} else if buffer.starts_with("```") {
starts_with_markdown_codeblock = true;
if let Some(newline_ix) = buffer.find('\n') {
buffer.replace_range(..newline_ix + 1, "");
first_line = false;
} else {
return future::ready(None);
}
}
}
let mut text = buffer.to_string();
if starts_with_markdown_codeblock {
text = text
.strip_suffix("\n```\n")
.or_else(|| text.strip_suffix("\n```"))
.or_else(|| text.strip_suffix("\n``"))
.or_else(|| text.strip_suffix("\n`"))
.or_else(|| text.strip_suffix('\n'))
.unwrap_or(&text)
.to_string();
}
if includes_start_or_end_span {
text = text
.strip_suffix("|E|>")
.or_else(|| text.strip_suffix("E|>"))
.or_else(|| text.strip_prefix("|>"))
.or_else(|| text.strip_prefix(">"))
.unwrap_or(&text)
.to_string();
};
if text.contains('\n') {
first_line = false;
}
let remainder = buffer.split_off(text.len());
let result = if buffer.is_empty() {
None
} else {
Some(Ok(buffer.clone()))
};
buffer = remainder;
future::ready(result)
})
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use ai::test::FakeCompletionProvider;
use futures::stream::{self};
use gpui::{Context, TestAppContext};
use indoc::indoc;
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
use rand::prelude::*;
use serde::Serialize;
use settings::SettingsStore;
#[derive(Serialize)]
pub struct DummyCompletionRequest {
pub name: String,
}
impl CompletionRequest for DummyCompletionRequest {
fn data(&self) -> serde_json::Result<String> {
serde_json::to_string(self)
}
}
#[gpui::test(iterations = 10)]
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = indoc! {"
fn main() {
let x = 0;
for _ in 0..10 {
x += 1;
}
}
"};
let buffer =
cx.build_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
});
let provider = Arc::new(FakeCompletionProvider::new());
let codegen = cx.build_model(|cx| {
Codegen::new(
buffer.clone(),
CodegenKind::Transform { range },
provider.clone(),
cx,
)
});
let request = Box::new(DummyCompletionRequest {
name: "test".to_string(),
});
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
let mut new_text = concat!(
" let mut x = 0;\n",
" while x < 10 {\n",
" x += 1;\n",
" }",
);
while !new_text.is_empty() {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
println!("CHUNK: {:?}", &chunk);
provider.send_completion(chunk);
new_text = suffix;
cx.background_executor.run_until_parked();
}
provider.finish_completion();
cx.background_executor.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
indoc! {"
fn main() {
let mut x = 0;
while x < 10 {
x += 1;
}
}
"}
);
}
#[gpui::test(iterations = 10)]
async fn test_autoindent_when_generating_past_indentation(
cx: &mut TestAppContext,
mut rng: StdRng,
) {
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = indoc! {"
fn main() {
le
}
"};
let buffer =
cx.build_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
let position = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 6))
});
let provider = Arc::new(FakeCompletionProvider::new());
let codegen = cx.build_model(|cx| {
Codegen::new(
buffer.clone(),
CodegenKind::Generate { position },
provider.clone(),
cx,
)
});
let request = Box::new(DummyCompletionRequest {
name: "test".to_string(),
});
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
let mut new_text = concat!(
"t mut x = 0;\n",
"while x < 10 {\n",
" x += 1;\n",
"}", //
);
while !new_text.is_empty() {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
provider.send_completion(chunk);
new_text = suffix;
cx.background_executor.run_until_parked();
}
provider.finish_completion();
cx.background_executor.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
indoc! {"
fn main() {
let mut x = 0;
while x < 10 {
x += 1;
}
}
"}
);
}
#[gpui::test(iterations = 10)]
async fn test_autoindent_when_generating_before_indentation(
cx: &mut TestAppContext,
mut rng: StdRng,
) {
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
let text = concat!(
"fn main() {\n",
" \n",
"}\n" //
);
let buffer =
cx.build_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
let position = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 2))
});
let provider = Arc::new(FakeCompletionProvider::new());
let codegen = cx.build_model(|cx| {
Codegen::new(
buffer.clone(),
CodegenKind::Generate { position },
provider.clone(),
cx,
)
});
let request = Box::new(DummyCompletionRequest {
name: "test".to_string(),
});
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
let mut new_text = concat!(
"let mut x = 0;\n",
"while x < 10 {\n",
" x += 1;\n",
"}", //
);
while !new_text.is_empty() {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
println!("{:?}", &chunk);
provider.send_completion(chunk);
new_text = suffix;
cx.background_executor.run_until_parked();
}
provider.finish_completion();
cx.background_executor.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
indoc! {"
fn main() {
let mut x = 0;
while x < 10 {
x += 1;
}
}
"}
);
}
#[gpui::test]
async fn test_strip_invalid_spans_from_codeblock() {
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("Lorem ipsum dolor", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum dolor"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum dolor"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum dolor"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum dolor"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks(
"```html\n```js\nLorem ipsum dolor\n```\n```",
2
))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"```js\nLorem ipsum dolor\n```"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"``\nLorem ipsum dolor\n```"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("<|S|Lorem ipsum|E|>", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("<|S|>Lorem ipsum", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("```\n<|S|>Lorem ipsum\n```", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum"
);
assert_eq!(
strip_invalid_spans_from_codeblock(chunks("```\n<|S|Lorem ipsum|E|>\n```", 2))
.map(|chunk| chunk.unwrap())
.collect::<String>()
.await,
"Lorem ipsum"
);
fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
stream::iter(
text.chars()
.collect::<Vec<_>>()
.chunks(size)
.map(|chunk| Ok(chunk.iter().collect::<String>()))
.collect::<Vec<_>>(),
)
}
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {
name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
Some(tree_sitter_rust::language()),
)
.with_indents_query(
r#"
(call_expression) @indent
(field_expression) @indent
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
"#,
)
.unwrap()
}
}

View File

@@ -0,0 +1,389 @@
use ai::models::LanguageModel;
use ai::prompts::base::{PromptArguments, PromptChain, PromptPriority, PromptTemplate};
use ai::prompts::file_context::FileContext;
use ai::prompts::generate::GenerateInlineContent;
use ai::prompts::preamble::EngineerPreamble;
use ai::prompts::repository_context::{PromptCodeSnippet, RepositoryContext};
use ai::providers::open_ai::OpenAILanguageModel;
use language::{BufferSnapshot, OffsetRangeExt, ToOffset};
use std::cmp::{self, Reverse};
use std::ops::Range;
use std::sync::Arc;
#[allow(dead_code)]
fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> String {
#[derive(Debug)]
struct Match {
collapse: Range<usize>,
keep: Vec<Range<usize>>,
}
let selected_range = selected_range.to_offset(buffer);
let mut ts_matches = buffer.matches(0..buffer.len(), |grammar| {
Some(&grammar.embedding_config.as_ref()?.query)
});
let configs = ts_matches
.grammars()
.iter()
.map(|g| g.embedding_config.as_ref().unwrap())
.collect::<Vec<_>>();
let mut matches = Vec::new();
while let Some(mat) = ts_matches.peek() {
let config = &configs[mat.grammar_index];
if let Some(collapse) = mat.captures.iter().find_map(|cap| {
if Some(cap.index) == config.collapse_capture_ix {
Some(cap.node.byte_range())
} else {
None
}
}) {
let mut keep = Vec::new();
for capture in mat.captures.iter() {
if Some(capture.index) == config.keep_capture_ix {
keep.push(capture.node.byte_range());
} else {
continue;
}
}
ts_matches.advance();
matches.push(Match { collapse, keep });
} else {
ts_matches.advance();
}
}
matches.sort_unstable_by_key(|mat| (mat.collapse.start, Reverse(mat.collapse.end)));
let mut matches = matches.into_iter().peekable();
let mut summary = String::new();
let mut offset = 0;
let mut flushed_selection = false;
while let Some(mat) = matches.next() {
// Keep extending the collapsed range if the next match surrounds
// the current one.
while let Some(next_mat) = matches.peek() {
if mat.collapse.start <= next_mat.collapse.start
&& mat.collapse.end >= next_mat.collapse.end
{
matches.next().unwrap();
} else {
break;
}
}
if offset > mat.collapse.start {
// Skip collapsed nodes that have already been summarized.
offset = cmp::max(offset, mat.collapse.end);
continue;
}
if offset <= selected_range.start && selected_range.start <= mat.collapse.end {
if !flushed_selection {
// The collapsed node ends after the selection starts, so we'll flush the selection first.
summary.extend(buffer.text_for_range(offset..selected_range.start));
summary.push_str("<|S|");
if selected_range.end == selected_range.start {
summary.push_str(">");
} else {
summary.extend(buffer.text_for_range(selected_range.clone()));
summary.push_str("|E|>");
}
offset = selected_range.end;
flushed_selection = true;
}
// If the selection intersects the collapsed node, we won't collapse it.
if selected_range.end >= mat.collapse.start {
continue;
}
}
summary.extend(buffer.text_for_range(offset..mat.collapse.start));
for keep in mat.keep {
summary.extend(buffer.text_for_range(keep));
}
offset = mat.collapse.end;
}
// Flush selection if we haven't already done so.
if !flushed_selection && offset <= selected_range.start {
summary.extend(buffer.text_for_range(offset..selected_range.start));
summary.push_str("<|S|");
if selected_range.end == selected_range.start {
summary.push_str(">");
} else {
summary.extend(buffer.text_for_range(selected_range.clone()));
summary.push_str("|E|>");
}
offset = selected_range.end;
}
summary.extend(buffer.text_for_range(offset..buffer.len()));
summary
}
pub fn generate_content_prompt(
user_prompt: String,
language_name: Option<&str>,
buffer: BufferSnapshot,
range: Range<usize>,
search_results: Vec<PromptCodeSnippet>,
model: &str,
project_name: Option<String>,
) -> anyhow::Result<String> {
// Using new Prompt Templates
let openai_model: Arc<dyn LanguageModel> = Arc::new(OpenAILanguageModel::load(model));
let lang_name = if let Some(language_name) = language_name {
Some(language_name.to_string())
} else {
None
};
let args = PromptArguments {
model: openai_model,
language_name: lang_name.clone(),
project_name,
snippets: search_results.clone(),
reserved_tokens: 1000,
buffer: Some(buffer),
selected_range: Some(range),
user_prompt: Some(user_prompt.clone()),
};
let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
(PromptPriority::Mandatory, Box::new(EngineerPreamble {})),
(
PromptPriority::Ordered { order: 1 },
Box::new(RepositoryContext {}),
),
(
PromptPriority::Ordered { order: 0 },
Box::new(FileContext {}),
),
(
PromptPriority::Mandatory,
Box::new(GenerateInlineContent {}),
),
];
let chain = PromptChain::new(args, templates);
let (prompt, _) = chain.generate(true)?;
anyhow::Ok(prompt)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use std::sync::Arc;
use gpui::{AppContext, Context};
use indoc::indoc;
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
use settings::SettingsStore;
pub(crate) fn rust_lang() -> Language {
Language::new(
LanguageConfig {
name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
Some(tree_sitter_rust::language()),
)
.with_embedding_query(
r#"
(
[(line_comment) (attribute_item)]* @context
.
[
(struct_item
name: (_) @name)
(enum_item
name: (_) @name)
(impl_item
trait: (_)? @name
"for"? @name
type: (_) @name)
(trait_item
name: (_) @name)
(function_item
name: (_) @name
body: (block
"{" @keep
"}" @keep) @collapse)
(macro_definition
name: (_) @name)
] @item
)
"#,
)
.unwrap()
}
#[gpui::test]
fn test_outline_for_prompt(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language_settings::init(cx);
let text = indoc! {"
struct X {
a: usize,
b: usize,
}
impl X {
fn new() -> Self {
let a = 1;
let b = 2;
Self { a, b }
}
pub fn a(&self, param: bool) -> usize {
self.a
}
pub fn b(&self) -> usize {
self.b
}
}
"};
let buffer =
cx.build_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.read(cx).snapshot();
assert_eq!(
summarize(&snapshot, Point::new(1, 4)..Point::new(1, 4)),
indoc! {"
struct X {
<|S|>a: usize,
b: usize,
}
impl X {
fn new() -> Self {}
pub fn a(&self, param: bool) -> usize {}
pub fn b(&self) -> usize {}
}
"}
);
assert_eq!(
summarize(&snapshot, Point::new(8, 12)..Point::new(8, 14)),
indoc! {"
struct X {
a: usize,
b: usize,
}
impl X {
fn new() -> Self {
let <|S|a |E|>= 1;
let b = 2;
Self { a, b }
}
pub fn a(&self, param: bool) -> usize {}
pub fn b(&self) -> usize {}
}
"}
);
assert_eq!(
summarize(&snapshot, Point::new(6, 0)..Point::new(6, 0)),
indoc! {"
struct X {
a: usize,
b: usize,
}
impl X {
<|S|>
fn new() -> Self {}
pub fn a(&self, param: bool) -> usize {}
pub fn b(&self) -> usize {}
}
"}
);
assert_eq!(
summarize(&snapshot, Point::new(21, 0)..Point::new(21, 0)),
indoc! {"
struct X {
a: usize,
b: usize,
}
impl X {
fn new() -> Self {}
pub fn a(&self, param: bool) -> usize {}
pub fn b(&self) -> usize {}
}
<|S|>"}
);
// Ensure nested functions get collapsed properly.
let text = indoc! {"
struct X {
a: usize,
b: usize,
}
impl X {
fn new() -> Self {
let a = 1;
let b = 2;
Self { a, b }
}
pub fn a(&self, param: bool) -> usize {
let a = 30;
fn nested() -> usize {
3
}
self.a + nested()
}
pub fn b(&self) -> usize {
self.b
}
}
"};
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let snapshot = buffer.read(cx).snapshot();
assert_eq!(
summarize(&snapshot, Point::new(0, 0)..Point::new(0, 0)),
indoc! {"
<|S|>struct X {
a: usize,
b: usize,
}
impl X {
fn new() -> Self {}
pub fn a(&self, param: bool) -> usize {}
pub fn b(&self) -> usize {}
}
"}
);
}
}

View File

@@ -0,0 +1,293 @@
use collections::HashMap;
use ordered_float::OrderedFloat;
use std::{
cmp,
fmt::{self, Debug},
ops::Range,
};
struct Matrix {
cells: Vec<f64>,
rows: usize,
cols: usize,
}
impl Matrix {
fn new() -> Self {
Self {
cells: Vec::new(),
rows: 0,
cols: 0,
}
}
fn resize(&mut self, rows: usize, cols: usize) {
self.cells.resize(rows * cols, 0.);
self.rows = rows;
self.cols = cols;
}
fn get(&self, row: usize, col: usize) -> f64 {
if row >= self.rows {
panic!("row out of bounds")
}
if col >= self.cols {
panic!("col out of bounds")
}
self.cells[col * self.rows + row]
}
fn set(&mut self, row: usize, col: usize, value: f64) {
if row >= self.rows {
panic!("row out of bounds")
}
if col >= self.cols {
panic!("col out of bounds")
}
self.cells[col * self.rows + row] = value;
}
}
impl Debug for Matrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
for i in 0..self.rows {
for j in 0..self.cols {
write!(f, "{:5}", self.get(i, j))?;
}
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug)]
pub enum Hunk {
Insert { text: String },
Remove { len: usize },
Keep { len: usize },
}
pub struct StreamingDiff {
old: Vec<char>,
new: Vec<char>,
scores: Matrix,
old_text_ix: usize,
new_text_ix: usize,
equal_runs: HashMap<(usize, usize), u32>,
}
impl StreamingDiff {
const INSERTION_SCORE: f64 = -1.;
const DELETION_SCORE: f64 = -20.;
const EQUALITY_BASE: f64 = 1.8;
const MAX_EQUALITY_EXPONENT: i32 = 16;
pub fn new(old: String) -> Self {
let old = old.chars().collect::<Vec<_>>();
let mut scores = Matrix::new();
scores.resize(old.len() + 1, 1);
for i in 0..=old.len() {
scores.set(i, 0, i as f64 * Self::DELETION_SCORE);
}
Self {
old,
new: Vec::new(),
scores,
old_text_ix: 0,
new_text_ix: 0,
equal_runs: Default::default(),
}
}
pub fn push_new(&mut self, text: &str) -> Vec<Hunk> {
self.new.extend(text.chars());
self.scores.resize(self.old.len() + 1, self.new.len() + 1);
for j in self.new_text_ix + 1..=self.new.len() {
self.scores.set(0, j, j as f64 * Self::INSERTION_SCORE);
for i in 1..=self.old.len() {
let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_SCORE;
let equality_score = if self.old[i - 1] == self.new[j - 1] {
let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
equal_run += 1;
self.equal_runs.insert((i, j), equal_run);
let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
self.scores.get(i - 1, j - 1) + Self::EQUALITY_BASE.powi(exponent)
} else {
f64::NEG_INFINITY
};
let score = insertion_score.max(deletion_score).max(equality_score);
self.scores.set(i, j, score);
}
}
let mut max_score = f64::NEG_INFINITY;
let mut next_old_text_ix = self.old_text_ix;
let next_new_text_ix = self.new.len();
for i in self.old_text_ix..=self.old.len() {
let score = self.scores.get(i, next_new_text_ix);
if score > max_score {
max_score = score;
next_old_text_ix = i;
}
}
let hunks = self.backtrack(next_old_text_ix, next_new_text_ix);
self.old_text_ix = next_old_text_ix;
self.new_text_ix = next_new_text_ix;
hunks
}
fn backtrack(&self, old_text_ix: usize, new_text_ix: usize) -> Vec<Hunk> {
let mut pending_insert: Option<Range<usize>> = None;
let mut hunks = Vec::new();
let mut i = old_text_ix;
let mut j = new_text_ix;
while (i, j) != (self.old_text_ix, self.new_text_ix) {
let insertion_score = if j > self.new_text_ix {
Some((i, j - 1))
} else {
None
};
let deletion_score = if i > self.old_text_ix {
Some((i - 1, j))
} else {
None
};
let equality_score = if i > self.old_text_ix && j > self.new_text_ix {
if self.old[i - 1] == self.new[j - 1] {
Some((i - 1, j - 1))
} else {
None
}
} else {
None
};
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
.iter()
.max_by_key(|cell| cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j))))
.unwrap()
.unwrap();
if prev_i == i && prev_j == j - 1 {
if let Some(pending_insert) = pending_insert.as_mut() {
pending_insert.start = prev_j;
} else {
pending_insert = Some(prev_j..j);
}
} else {
if let Some(range) = pending_insert.take() {
hunks.push(Hunk::Insert {
text: self.new[range].iter().collect(),
});
}
let char_len = self.old[i - 1].len_utf8();
if prev_i == i - 1 && prev_j == j {
if let Some(Hunk::Remove { len }) = hunks.last_mut() {
*len += char_len;
} else {
hunks.push(Hunk::Remove { len: char_len })
}
} else {
if let Some(Hunk::Keep { len }) = hunks.last_mut() {
*len += char_len;
} else {
hunks.push(Hunk::Keep { len: char_len })
}
}
}
i = prev_i;
j = prev_j;
}
if let Some(range) = pending_insert.take() {
hunks.push(Hunk::Insert {
text: self.new[range].iter().collect(),
});
}
hunks.reverse();
hunks
}
pub fn finish(self) -> Vec<Hunk> {
self.backtrack(self.old.len(), self.new.len())
}
}
#[cfg(test)]
mod tests {
use std::env;
use super::*;
use rand::prelude::*;
#[gpui::test(iterations = 100)]
fn test_random_diffs(mut rng: StdRng) {
let old_text_len = env::var("OLD_TEXT_LEN")
.map(|i| i.parse().expect("invalid `OLD_TEXT_LEN` variable"))
.unwrap_or(10);
let new_text_len = env::var("NEW_TEXT_LEN")
.map(|i| i.parse().expect("invalid `NEW_TEXT_LEN` variable"))
.unwrap_or(10);
let old = util::RandomCharIter::new(&mut rng)
.take(old_text_len)
.collect::<String>();
log::info!("old text: {:?}", old);
let mut diff = StreamingDiff::new(old.clone());
let mut hunks = Vec::new();
let mut new_len = 0;
let mut new = String::new();
while new_len < new_text_len {
let new_chunk_len = rng.gen_range(1..=new_text_len - new_len);
let new_chunk = util::RandomCharIter::new(&mut rng)
.take(new_len)
.collect::<String>();
log::info!("new chunk: {:?}", new_chunk);
new_len += new_chunk_len;
new.push_str(&new_chunk);
let new_hunks = diff.push_new(&new_chunk);
log::info!("hunks: {:?}", new_hunks);
hunks.extend(new_hunks);
}
let final_hunks = diff.finish();
log::info!("final hunks: {:?}", final_hunks);
hunks.extend(final_hunks);
log::info!("new text: {:?}", new);
let mut old_ix = 0;
let mut new_ix = 0;
let mut patched = String::new();
for hunk in hunks {
match hunk {
Hunk::Keep { len } => {
assert_eq!(&old[old_ix..old_ix + len], &new[new_ix..new_ix + len]);
patched.push_str(&old[old_ix..old_ix + len]);
old_ix += len;
new_ix += len;
}
Hunk::Remove { len } => {
old_ix += len;
}
Hunk::Insert { text } => {
assert_eq!(text, &new[new_ix..new_ix + text.len()]);
patched.push_str(&text);
new_ix += text.len();
}
}
}
assert_eq!(patched, new);
}
}

View File

@@ -118,14 +118,18 @@ fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
let auto_updater = auto_updater.read(cx);
let server_url = &auto_updater.server_url;
let current_version = auto_updater.current_version;
let latest_release_url = if cx.has_global::<ReleaseChannel>()
&& *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
{
format!("{server_url}/releases/preview/{current_version}")
} else {
format!("{server_url}/releases/stable/{current_version}")
};
cx.platform().open_url(&latest_release_url);
if cx.has_global::<ReleaseChannel>() {
match cx.global::<ReleaseChannel>() {
ReleaseChannel::Dev => {}
ReleaseChannel::Nightly => {}
ReleaseChannel::Preview => cx
.platform()
.open_url(&format!("{server_url}/releases/preview/{current_version}")),
ReleaseChannel::Stable => cx
.platform()
.open_url(&format!("{server_url}/releases/stable/{current_version}")),
}
}
}
}
@@ -224,22 +228,19 @@ impl AutoUpdater {
)
});
let preview_param = cx.read(|cx| {
let mut url_string = format!(
"{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
);
cx.read(|cx| {
if cx.has_global::<ReleaseChannel>() {
if *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview {
return "&preview=1";
if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
url_string += "&";
url_string += param;
}
}
""
});
let mut response = client
.get(
&format!("{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg{preview_param}"),
Default::default(),
true,
)
.await?;
let mut response = client.get(&url_string, Default::default(), true).await?;
let mut body = Vec::new();
response

View File

@@ -0,0 +1,29 @@
[package]
name = "auto_update2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/auto_update.rs"
doctest = false
[dependencies]
db = { package = "db2", path = "../db2" }
client = { package = "client2", path = "../client2" }
gpui = { package = "gpui2", path = "../gpui2" }
menu = { package = "menu2", path = "../menu2" }
project = { package = "project2", path = "../project2" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
workspace = { package = "workspace2", path = "../workspace2" }
util = { path = "../util" }
anyhow.workspace = true
isahc.workspace = true
lazy_static.workspace = true
log.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
smol.workspace = true
tempdir.workspace = true

View File

@@ -0,0 +1,405 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
ViewContext, VisualContext, WindowContext,
};
use isahc::AsyncBody;
use serde::Deserialize;
use serde_derive::Serialize;
use smol::io::AsyncReadExt;
use settings::{Settings, SettingsStore};
use smol::{fs::File, process::Command};
use std::{ffi::OsString, sync::Arc, time::Duration};
use update_notification::UpdateNotification;
use util::channel::{AppCommitSha, ReleaseChannel};
use util::http::HttpClient;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]);
#[derive(Serialize)]
struct UpdateRequestBody {
installation_id: Option<Arc<str>>,
release_channel: Option<&'static str>,
telemetry: bool,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AutoUpdateStatus {
Idle,
Checking,
Downloading,
Installing,
Updated,
Errored,
}
pub struct AutoUpdater {
status: AutoUpdateStatus,
current_version: SemanticVersion,
http_client: Arc<dyn HttpClient>,
pending_poll: Option<Task<Option<()>>>,
server_url: String,
}
#[derive(Deserialize)]
struct JsonRelease {
version: String,
url: String,
}
struct AutoUpdateSetting(bool);
impl Settings for AutoUpdateSetting {
const KEY: Option<&'static str> = Some("auto_update");
type FileContent = Option<bool>;
fn load(
default_value: &Option<bool>,
user_values: &[&Option<bool>],
_: &mut AppContext,
) -> Result<Self> {
Ok(Self(
Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
))
}
}
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|_, action: &Check, cx| check(action, cx));
workspace.register_action(|_, action, cx| view_release_notes(action, cx));
// @nate - code to trigger update notification on launch
// todo!("remove this when Nate is done")
// workspace.show_notification(0, _cx, |cx| {
// cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
// });
})
.detach();
if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
let auto_updater = cx.build_model(|cx| {
let updater = AutoUpdater::new(version, http_client, server_url);
let mut update_subscription = AutoUpdateSetting::get_global(cx)
.0
.then(|| updater.start_polling(cx));
cx.observe_global::<SettingsStore>(move |updater, cx| {
if AutoUpdateSetting::get_global(cx).0 {
if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx))
}
} else {
update_subscription.take();
}
})
.detach();
updater
});
cx.set_global(Some(auto_updater));
}
}
pub fn check(_: &Check, cx: &mut WindowContext) {
if let Some(updater) = AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx));
} else {
drop(cx.prompt(
gpui::PromptLevel::Info,
"Auto-updates disabled for non-bundled app.",
&["Ok"],
));
}
}
pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
if let Some(auto_updater) = AutoUpdater::get(cx) {
let auto_updater = auto_updater.read(cx);
let server_url = &auto_updater.server_url;
let current_version = auto_updater.current_version;
if cx.has_global::<ReleaseChannel>() {
match cx.global::<ReleaseChannel>() {
ReleaseChannel::Dev => {}
ReleaseChannel::Nightly => {}
ReleaseChannel::Preview => {
cx.open_url(&format!("{server_url}/releases/preview/{current_version}"))
}
ReleaseChannel::Stable => {
cx.open_url(&format!("{server_url}/releases/stable/{current_version}"))
}
}
}
}
}
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(|workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
workspace.show_notification(0, cx, |cx| {
cx.build_view(|_| UpdateNotification::new(version))
});
updater
.read(cx)
.set_should_show_update_notification(false, cx)
.detach_and_log_err(cx);
})?;
}
anyhow::Ok(())
})
.detach();
None
}
impl AutoUpdater {
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
cx.default_global::<Option<Model<Self>>>().clone()
}
fn new(
current_version: SemanticVersion,
http_client: Arc<dyn HttpClient>,
server_url: String,
) -> Self {
Self {
status: AutoUpdateStatus::Idle,
current_version,
http_client,
server_url,
pending_poll: None,
}
}
pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.spawn(|this, mut cx| async move {
loop {
this.update(&mut cx, |this, cx| this.poll(cx))?;
cx.background_executor().timer(POLL_INTERVAL).await;
}
})
}
pub fn poll(&mut self, cx: &mut ModelContext<Self>) {
if self.pending_poll.is_some() || self.status == AutoUpdateStatus::Updated {
return;
}
self.status = AutoUpdateStatus::Checking;
cx.notify();
self.pending_poll = Some(cx.spawn(|this, mut cx| async move {
let result = Self::update(this.upgrade()?, cx.clone()).await;
this.update(&mut cx, |this, cx| {
this.pending_poll = None;
if let Err(error) = result {
log::error!("auto-update failed: error:{:?}", error);
this.status = AutoUpdateStatus::Errored;
cx.notify();
}
})
.ok()
}));
}
pub fn status(&self) -> AutoUpdateStatus {
self.status
}
pub fn dismiss_error(&mut self, cx: &mut ModelContext<Self>) {
self.status = AutoUpdateStatus::Idle;
cx.notify();
}
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
(
this.http_client.clone(),
this.server_url.clone(),
this.current_version,
)
})?;
let mut url_string = format!(
"{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
);
cx.update(|cx| {
if cx.has_global::<ReleaseChannel>() {
if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
url_string += "&";
url_string += param;
}
}
})?;
let mut response = client.get(&url_string, Default::default(), true).await?;
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading release")?;
let release: JsonRelease =
serde_json::from_slice(body.as_slice()).context("error deserializing release")?;
let should_download = match *RELEASE_CHANNEL {
ReleaseChannel::Nightly => cx
.try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
.unwrap_or(true),
_ => release.version.parse::<SemanticVersion>()? <= current_version,
};
if !should_download {
this.update(&mut cx, |this, cx| {
this.status = AutoUpdateStatus::Idle;
cx.notify();
})?;
return Ok(());
}
this.update(&mut cx, |this, cx| {
this.status = AutoUpdateStatus::Downloading;
cx.notify();
})?;
let temp_dir = tempdir::TempDir::new("zed-auto-update")?;
let dmg_path = temp_dir.path().join("Zed.dmg");
let mount_path = temp_dir.path().join("Zed");
let running_app_path = ZED_APP_PATH
.clone()
.map_or_else(|| cx.update(|cx| cx.app_path())?, Ok)?;
let running_app_filename = running_app_path
.file_name()
.ok_or_else(|| anyhow!("invalid running app path"))?;
let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into();
mounted_app_path.push("/");
let mut dmg_file = File::create(&dmg_path).await?;
let (installation_id, release_channel, telemetry) = cx.update(|cx| {
let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
let release_channel = cx
.has_global::<ReleaseChannel>()
.then(|| cx.global::<ReleaseChannel>().display_name());
let telemetry = TelemetrySettings::get_global(cx).metrics;
(installation_id, release_channel, telemetry)
})?;
let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
installation_id,
release_channel,
telemetry,
})?);
let mut response = client.get(&release.url, request_body, true).await?;
smol::io::copy(response.body_mut(), &mut dmg_file).await?;
log::info!("downloaded update. path:{:?}", dmg_path);
this.update(&mut cx, |this, cx| {
this.status = AutoUpdateStatus::Installing;
cx.notify();
})?;
let output = Command::new("hdiutil")
.args(&["attach", "-nobrowse"])
.arg(&dmg_path)
.arg("-mountroot")
.arg(&temp_dir.path())
.output()
.await?;
if !output.status.success() {
Err(anyhow!(
"failed to mount: {:?}",
String::from_utf8_lossy(&output.stderr)
))?;
}
let output = Command::new("rsync")
.args(&["-av", "--delete"])
.arg(&mounted_app_path)
.arg(&running_app_path)
.output()
.await?;
if !output.status.success() {
Err(anyhow!(
"failed to copy app: {:?}",
String::from_utf8_lossy(&output.stderr)
))?;
}
let output = Command::new("hdiutil")
.args(&["detach"])
.arg(&mount_path)
.output()
.await?;
if !output.status.success() {
Err(anyhow!(
"failed to unmount: {:?}",
String::from_utf8_lossy(&output.stderr)
))?;
}
this.update(&mut cx, |this, cx| {
this.set_should_show_update_notification(true, cx)
.detach_and_log_err(cx);
this.status = AutoUpdateStatus::Updated;
cx.notify();
})?;
Ok(())
}
fn set_should_show_update_notification(
&self,
should_show: bool,
cx: &AppContext,
) -> Task<Result<()>> {
cx.background_executor().spawn(async move {
if should_show {
KEY_VALUE_STORE
.write_kvp(
SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string(),
"".to_string(),
)
.await?;
} else {
KEY_VALUE_STORE
.delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string())
.await?;
}
Ok(())
})
}
fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
cx.background_executor().spawn(async move {
Ok(KEY_VALUE_STORE
.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?
.is_some())
})
}
}

View File

@@ -0,0 +1,58 @@
use gpui::{
div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
};
use menu::Cancel;
use util::channel::ReleaseChannel;
use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
pub struct UpdateNotification {
version: SemanticVersion,
}
impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
type Element = Div;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let app_name = cx.global::<ReleaseChannel>().display_name();
v_stack()
.on_action(cx.listener(UpdateNotification::dismiss))
.elevation_3(cx)
.p_4()
.child(
h_stack()
.justify_between()
.child(Label::new(format!(
"Updated to {app_name} {}",
self.version
)))
.child(
div()
.id("cancel")
.child(IconElement::new(Icon::Close))
.cursor_pointer()
.on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))),
),
)
.child(
div()
.id("notes")
.child(Label::new("View the release notes"))
.cursor_pointer()
.on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
)
}
}
impl UpdateNotification {
pub fn new(version: SemanticVersion) -> Self {
Self { version }
}
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
}
}

View File

@@ -0,0 +1,28 @@
[package]
name = "breadcrumbs2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/breadcrumbs.rs"
doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { package = "editor2", path = "../editor2" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
language = { package = "language2", path = "../language2" }
project = { package = "project2", path = "../project2" }
search = { package = "search2", path = "../search2" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
workspace = { package = "workspace2", path = "../workspace2" }
outline = { package = "outline2", path = "../outline2" }
itertools = "0.10"
[dev-dependencies]
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }

View File

@@ -0,0 +1,127 @@
use editor::Editor;
use gpui::{
Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
ViewContext,
};
use itertools::Itertools;
use theme::ActiveTheme;
use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
use workspace::{
item::{ItemEvent, ItemHandle},
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
};
pub enum Event {
UpdateLocation,
}
pub struct Breadcrumbs {
pane_focused: bool,
active_item: Option<Box<dyn ItemHandle>>,
subscription: Option<Subscription>,
}
impl Breadcrumbs {
pub fn new() -> Self {
Self {
pane_focused: false,
active_item: Default::default(),
subscription: Default::default(),
}
}
}
impl EventEmitter<Event> for Breadcrumbs {}
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let element = h_stack().text_ui();
let Some(active_item) = &self
.active_item
.as_ref()
.filter(|item| item.downcast::<editor::Editor>().is_some())
else {
return element;
};
let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
return element;
};
let highlighted_segments = segments.into_iter().map(|segment| {
StyledText::new(segment.text)
.with_highlights(&cx.text_style(), segment.highlights.unwrap_or_default())
.into_any()
});
let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
Label::new("").into_any_element()
});
let editor = active_item
.downcast::<Editor>()
.map(|editor| editor.downgrade());
element.child(
ButtonLike::new("toggle outline view")
.style(ButtonStyle::Subtle)
.child(h_stack().gap_1().children(breadcrumbs))
.on_click(move |_, cx| {
if let Some(editor) = editor.as_ref().and_then(|editor| editor.upgrade()) {
outline::toggle(editor, &outline::Toggle, cx)
}
})
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
)
}
}
impl ToolbarItemView for Breadcrumbs {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
cx.notify();
self.active_item = None;
if let Some(item) = active_pane_item {
let this = cx.view().downgrade();
self.subscription = Some(item.subscribe_to_item_events(
cx,
Box::new(move |event, cx| {
if let ItemEvent::UpdateBreadcrumbs = event {
this.update(cx, |_, cx| {
cx.emit(Event::UpdateLocation);
cx.notify();
})
.ok();
}
}),
));
self.active_item = Some(item.boxed_clone());
item.breadcrumb_location(cx)
} else {
ToolbarItemLocation::Hidden
}
}
// fn location_for_event(
// &self,
// _: &Event,
// current_location: ToolbarItemLocation,
// cx: &AppContext,
// ) -> ToolbarItemLocation {
// if let Some(active_item) = self.active_item.as_ref() {
// active_item.breadcrumb_location(cx)
// } else {
// current_location
// }
// }
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
self.pane_focused = pane_focused;
}
}

View File

@@ -5,10 +5,7 @@ pub mod room;
use anyhow::{anyhow, Result};
use audio::Audio;
use call_settings::CallSettings;
use client::{
proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore,
ZED_ALWAYS_ACTIVE,
};
use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
@@ -485,12 +482,8 @@ pub fn report_call_event_for_room(
) {
let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call {
operation,
room_id: Some(room_id),
channel_id,
};
telemetry.report_clickhouse_event(event, telemetry_settings);
telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
}
pub fn report_call_event_for_channel(
@@ -504,12 +497,12 @@ pub fn report_call_event_for_channel(
let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call {
telemetry.report_call_event(
telemetry_settings,
operation,
room_id: room.map(|r| r.read(cx).id()),
channel_id: Some(channel_id),
};
telemetry.report_clickhouse_event(event, telemetry_settings);
room.map(|r| r.read(cx).id()),
Some(channel_id),
)
}
#[cfg(test)]

View File

@@ -35,11 +35,13 @@ util = { path = "../util" }
anyhow.workspace = true
async-broadcast = "0.4"
futures.workspace = true
image = "0.23"
postage.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
[dev-dependencies]
client = { package = "client2", path = "../client2", features = ["test-support"] }

View File

@@ -5,10 +5,7 @@ pub mod room;
use anyhow::{anyhow, Result};
use audio::Audio;
use call_settings::CallSettings;
use client::{
proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore,
ZED_ALWAYS_ACTIVE,
};
use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
@@ -467,7 +464,7 @@ impl ActiveCall {
&self.pending_invites
}
pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
if let Some(room) = self.room() {
let room = room.read(cx);
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
@@ -480,16 +477,12 @@ pub fn report_call_event_for_room(
room_id: u64,
channel_id: Option<u64>,
client: &Arc<Client>,
cx: &AppContext,
cx: &mut AppContext,
) {
let telemetry = client.telemetry();
let telemetry_settings = *TelemetrySettings::get_global(cx);
let event = ClickhouseEvent::Call {
operation,
room_id: Some(room_id),
channel_id,
};
telemetry.report_clickhouse_event(event, telemetry_settings);
telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
}
pub fn report_call_event_for_channel(
@@ -504,12 +497,12 @@ pub fn report_call_event_for_channel(
let telemetry_settings = *TelemetrySettings::get_global(cx);
let event = ClickhouseEvent::Call {
telemetry.report_call_event(
telemetry_settings,
operation,
room_id: room.map(|r| r.read(cx).id()),
channel_id: Some(channel_id),
};
telemetry.report_clickhouse_event(event, telemetry_settings);
room.map(|r| r.read(cx).id()),
Some(channel_id),
)
}
#[cfg(test)]

View File

@@ -4,7 +4,7 @@ use client::{proto, User};
use collections::HashMap;
use gpui::WeakModel;
pub use live_kit_client::Frame;
use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
use project::Project;
use std::sync::Arc;

View File

@@ -21,7 +21,7 @@ use live_kit_client::{
};
use postage::{sink::Sink, stream::Stream, watch};
use project::Project;
use settings::Settings;
use settings::Settings as _;
use std::{future::Future, mem, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt};
@@ -1267,7 +1267,6 @@ impl Room {
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
.await
};
let publication = publish_track.await;
this.upgrade()
.ok_or_else(|| anyhow!("room was dropped"))?

View File

@@ -18,7 +18,7 @@ db = { package = "db2", path = "../db2" }
gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }
rpc = { package = "rpc2", path = "../rpc2" }
text = { path = "../text" }
text = { package = "text2", path = "../text2" }
language = { package = "language2", path = "../language2" }
settings = { package = "settings2", path = "../settings2" }
feature_flags = { package = "feature_flags2", path = "../feature_flags2" }

View File

@@ -8,7 +8,8 @@ use collections::{hash_map, HashMap, HashSet};
use db::RELEASE_CHANNEL;
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
WeakModel,
};
use rpc::{
proto::{self, ChannelVisibility},
@@ -46,7 +47,7 @@ pub struct ChannelStore {
#[derive(Clone, Debug, PartialEq)]
pub struct Channel {
pub id: ChannelId,
pub name: String,
pub name: SharedString,
pub visibility: proto::ChannelVisibility,
pub role: proto::ChannelRole,
pub unseen_note_version: Option<(u64, clock::Global)>,
@@ -895,14 +896,16 @@ impl ChannelStore {
.channel_invitations
.binary_search_by_key(&channel.id, |c| c.id)
{
Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
Ok(ix) => {
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
}
Err(ix) => self.channel_invitations.insert(
ix,
Arc::new(Channel {
id: channel.id,
visibility: channel.visibility(),
role: channel.role(),
name: channel.name,
name: channel.name.into(),
unseen_note_version: None,
unseen_message_id: None,
parent_path: channel.parent_path,

View File

@@ -104,7 +104,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
existing_channel.visibility = channel_proto.visibility();
existing_channel.role = channel_proto.role();
existing_channel.name = channel_proto.name;
existing_channel.name = channel_proto.name.into();
} else {
self.channels_by_id.insert(
channel_proto.id,
@@ -112,7 +112,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
id: channel_proto.id,
visibility: channel_proto.visibility(),
role: channel_proto.role(),
name: channel_proto.name,
name: channel_proto.name.into(),
unseen_note_version: None,
unseen_message_id: None,
parent_path: channel_proto.parent_path,
@@ -146,11 +146,11 @@ fn channel_path_sorting_key<'a>(
let (parent_path, name) = channels_by_id
.get(&id)
.map_or((&[] as &[_], None), |channel| {
(channel.parent_path.as_slice(), Some(channel.name.as_str()))
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
});
parent_path
.iter()
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_str()))
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
.chain(name)
}

View File

@@ -345,7 +345,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);

View File

@@ -12,6 +12,7 @@ doctest = false
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
collections = { path = "../collections" }
db = { path = "../db" }
gpui = { path = "../gpui" }

View File

@@ -346,7 +346,7 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
}
}
#[derive(Copy, Clone)]
#[derive(Debug, Copy, Clone)]
pub struct TelemetrySettings {
pub diagnostics: bool,
pub metrics: bool,
@@ -987,9 +987,17 @@ impl Client {
self.establish_websocket_connection(credentials, cx)
}
async fn get_rpc_url(http: Arc<dyn HttpClient>, is_preview: bool) -> Result<Url> {
let preview_param = if is_preview { "?preview=1" } else { "" };
let url = format!("{}/rpc{preview_param}", *ZED_SERVER_URL);
async fn get_rpc_url(
http: Arc<dyn HttpClient>,
release_channel: Option<ReleaseChannel>,
) -> Result<Url> {
let mut url = format!("{}/rpc", *ZED_SERVER_URL);
if let Some(preview_param) =
release_channel.and_then(|channel| channel.release_query_param())
{
url += "?";
url += preview_param;
}
let response = http.get(&url, Default::default(), false).await?;
// Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
@@ -1024,11 +1032,11 @@ impl Client {
credentials: &Credentials,
cx: &AsyncAppContext,
) -> Task<Result<Connection, EstablishConnectionError>> {
let use_preview_server = cx.read(|cx| {
let release_channel = cx.read(|cx| {
if cx.has_global::<ReleaseChannel>() {
*cx.global::<ReleaseChannel>() != ReleaseChannel::Stable
Some(*cx.global::<ReleaseChannel>())
} else {
false
None
}
});
@@ -1041,7 +1049,7 @@ impl Client {
let http = self.http.clone();
cx.background().spawn(async move {
let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?;
let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
let rpc_host = rpc_url
.host_str()
.zip(rpc_url.port_or_known_default())
@@ -1191,7 +1199,7 @@ impl Client {
// Use the collab server's admin API to retrieve the id
// of the impersonated user.
let mut url = Self::get_rpc_url(http.clone(), false).await?;
let mut url = Self::get_rpc_url(http.clone(), None).await?;
url.set_path("/user");
url.set_query(Some(&format!("github_login={login}")));
let request = Request::get(url.as_str())

View File

@@ -1,4 +1,5 @@
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use chrono::{DateTime, Utc};
use gpui::{executor::Background, serde_json, AppContext, Task};
use lazy_static::lazy_static;
use parking_lot::Mutex;
@@ -20,7 +21,7 @@ pub struct Telemetry {
#[derive(Default)]
struct TelemetryState {
metrics_id: Option<Arc<str>>, // Per logged-in user
installation_id: Option<Arc<str>>, // Per app installation (different for dev, preview, and stable)
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<Arc<str>>, // Per app launch
app_version: Option<Arc<str>>,
release_channel: Option<&'static str>,
@@ -31,6 +32,7 @@ struct TelemetryState {
flush_clickhouse_events_task: Option<Task<()>>,
log_file: Option<NamedTempFile>,
is_staff: Option<bool>,
first_event_datetime: Option<DateTime<Utc>>,
}
const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
@@ -77,29 +79,39 @@ pub enum ClickhouseEvent {
vim_mode: bool,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
milliseconds_since_first_event: i64,
},
Copilot {
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
milliseconds_since_first_event: i64,
},
Call {
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
milliseconds_since_first_event: i64,
},
Assistant {
conversation_id: Option<String>,
kind: AssistantKind,
model: &'static str,
milliseconds_since_first_event: i64,
},
Cpu {
usage_as_percentage: f32,
core_count: u32,
milliseconds_since_first_event: i64,
},
Memory {
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
milliseconds_since_first_event: i64,
},
App {
operation: &'static str,
milliseconds_since_first_event: i64,
},
}
@@ -140,6 +152,7 @@ impl Telemetry {
flush_clickhouse_events_task: Default::default(),
log_file: None,
is_staff: None,
first_event_datetime: None,
}),
});
@@ -159,13 +172,8 @@ impl Telemetry {
let mut state = self.state.lock();
state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id.into());
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
drop(state);
if has_clickhouse_events {
self.flush_clickhouse_events();
}
let this = self.clone();
cx.spawn(|mut cx| async move {
// Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -195,20 +203,18 @@ impl Telemetry {
return;
};
let memory_event = ClickhouseEvent::Memory {
memory_in_bytes: process.memory(),
virtual_memory_in_bytes: process.virtual_memory(),
};
let cpu_event = ClickhouseEvent::Cpu {
usage_as_percentage: process.cpu_usage(),
core_count: system.cpus().len() as u32,
};
let telemetry_settings = cx.update(|cx| *settings::get::<TelemetrySettings>(cx));
this.report_clickhouse_event(memory_event, telemetry_settings);
this.report_clickhouse_event(cpu_event, telemetry_settings);
this.report_memory_event(
telemetry_settings,
process.memory(),
process.virtual_memory(),
);
this.report_cpu_event(
telemetry_settings,
process.cpu_usage(),
system.cpus().len() as u32,
);
}
})
.detach();
@@ -231,10 +237,141 @@ impl Telemetry {
drop(state);
}
pub fn report_clickhouse_event(
pub fn report_editor_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
file_extension: Option<String>,
vim_mode: bool,
operation: &'static str,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
) {
let event = ClickhouseEvent::Editor {
file_extension,
vim_mode,
operation,
copilot_enabled,
copilot_enabled_for_language,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_copilot_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
) {
let event = ClickhouseEvent::Copilot {
suggestion_id,
suggestion_accepted,
file_extension,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_assistant_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
conversation_id: Option<String>,
kind: AssistantKind,
model: &'static str,
) {
let event = ClickhouseEvent::Assistant {
conversation_id,
kind,
model,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_call_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
) {
let event = ClickhouseEvent::Call {
operation,
room_id,
channel_id,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_cpu_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
usage_as_percentage: f32,
core_count: u32,
) {
let event = ClickhouseEvent::Cpu {
usage_as_percentage,
core_count,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_memory_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
) {
let event = ClickhouseEvent::Memory {
memory_in_bytes,
virtual_memory_in_bytes,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
// app_events are called at app open and app close, so flush is set to immediately send
pub fn report_app_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
operation: &'static str,
) {
let event = ClickhouseEvent::App {
operation,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, true)
}
fn milliseconds_since_first_event(&self) -> i64 {
let mut state = self.state.lock();
match state.first_event_datetime {
Some(first_event_datetime) => {
let now: DateTime<Utc> = Utc::now();
now.timestamp_millis() - first_event_datetime.timestamp_millis()
}
None => {
state.first_event_datetime = Some(Utc::now());
0
}
}
}
fn report_clickhouse_event(
self: &Arc<Self>,
event: ClickhouseEvent,
telemetry_settings: TelemetrySettings,
immediate_flush: bool,
) {
if !telemetry_settings.metrics {
return;
@@ -247,7 +384,7 @@ impl Telemetry {
.push(ClickhouseEventWrapper { signed_in, event });
if state.installation_id.is_some() {
if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
drop(state);
self.flush_clickhouse_events();
} else {
@@ -275,6 +412,7 @@ impl Telemetry {
fn flush_clickhouse_events(self: &Arc<Self>) {
let mut state = self.state.lock();
state.first_event_datetime = None;
let mut events = mem::take(&mut state.clickhouse_events_queue);
state.flush_clickhouse_events_task.take();
drop(state);

View File

@@ -12,6 +12,7 @@ doctest = false
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
collections = { path = "../collections" }
db = { package = "db2", path = "../db2" }
gpui = { package = "gpui2", path = "../gpui2" }

View File

@@ -11,8 +11,8 @@ use async_tungstenite::tungstenite::{
http::{Request, StatusCode},
};
use futures::{
future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _,
TryStreamExt,
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
TryFutureExt as _, TryStreamExt,
};
use gpui::{
actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model,
@@ -70,7 +70,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
actions!(SignIn, SignOut, Reconnect);
actions!(client, [SignIn, SignOut, Reconnect]);
pub fn init_settings(cx: &mut AppContext) {
TelemetrySettings::register(cx);
@@ -382,7 +382,7 @@ impl settings::Settings for TelemetrySettings {
}
impl Client {
pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
Arc::new(Self {
id: AtomicU64::new(0),
peer: Peer::new(0),
@@ -551,7 +551,6 @@ impl Client {
F: 'static + Future<Output = Result<()>>,
{
let message_type_id = TypeId::of::<M>();
let mut state = self.state.write();
state
.models_by_message_type
@@ -694,8 +693,8 @@ impl Client {
}
}
pub async fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
read_credentials_from_keychain(cx).await.is_some()
pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
read_credentials_from_keychain(cx).is_some()
}
#[async_recursion(?Send)]
@@ -726,7 +725,7 @@ impl Client {
let mut read_from_keychain = false;
let mut credentials = self.state.read().credentials.clone();
if credentials.is_none() && try_keychain {
credentials = read_credentials_from_keychain(cx).await;
credentials = read_credentials_from_keychain(cx);
read_from_keychain = credentials.is_some();
}
if credentials.is_none() {
@@ -923,9 +922,17 @@ impl Client {
self.establish_websocket_connection(credentials, cx)
}
async fn get_rpc_url(http: Arc<dyn HttpClient>, is_preview: bool) -> Result<Url> {
let preview_param = if is_preview { "?preview=1" } else { "" };
let url = format!("{}/rpc{preview_param}", *ZED_SERVER_URL);
async fn get_rpc_url(
http: Arc<dyn HttpClient>,
release_channel: Option<ReleaseChannel>,
) -> Result<Url> {
let mut url = format!("{}/rpc", *ZED_SERVER_URL);
if let Some(preview_param) =
release_channel.and_then(|channel| channel.release_query_param())
{
url += "?";
url += preview_param;
}
let response = http.get(&url, Default::default(), false).await?;
// Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
@@ -960,9 +967,7 @@ impl Client {
credentials: &Credentials,
cx: &AsyncAppContext,
) -> Task<Result<Connection, EstablishConnectionError>> {
let use_preview_server = cx
.try_read_global(|channel: &ReleaseChannel, _| *channel != ReleaseChannel::Stable)
.unwrap_or(false);
let release_channel = cx.try_read_global(|channel: &ReleaseChannel, _| *channel);
let request = Request::builder()
.header(
@@ -973,7 +978,7 @@ impl Client {
let http = self.http.clone();
cx.background_executor().spawn(async move {
let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?;
let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
let rpc_host = rpc_url
.host_str()
.zip(rpc_url.port_or_known_default())
@@ -1015,91 +1020,116 @@ impl Client {
) -> Task<Result<Credentials>> {
let http = self.http.clone();
cx.spawn(|cx| async move {
// Generate a pair of asymmetric encryption keys. The public key will be used by the
// zed server to encrypt the user's access token, so that it can'be intercepted by
// any other app running on the user's device.
let (public_key, private_key) =
rpc::auth::keypair().expect("failed to generate keypair for auth");
let public_key_string =
String::try_from(public_key).expect("failed to serialize public key for auth");
let background = cx.background_executor().clone();
if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) {
return Self::authenticate_as_admin(http, login.clone(), token.clone()).await;
}
let (open_url_tx, open_url_rx) = oneshot::channel::<String>();
cx.update(|cx| {
cx.spawn(move |cx| async move {
let url = open_url_rx.await?;
cx.update(|cx| cx.open_url(&url))
})
.detach_and_log_err(cx);
})
.log_err();
// Start an HTTP server to receive the redirect from Zed's sign-in page.
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
let port = server.server_addr().port();
let credentials = background
.clone()
.spawn(async move {
// Generate a pair of asymmetric encryption keys. The public key will be used by the
// zed server to encrypt the user's access token, so that it can'be intercepted by
// any other app running on the user's device.
let (public_key, private_key) =
rpc::auth::keypair().expect("failed to generate keypair for auth");
let public_key_string = String::try_from(public_key)
.expect("failed to serialize public key for auth");
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
// that the user is signing in from a Zed app running on the same device.
let mut url = format!(
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
*ZED_SERVER_URL, port, public_key_string
);
if let Some((login, token)) =
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
{
return Self::authenticate_as_admin(http, login.clone(), token.clone())
.await;
}
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
log::info!("impersonating user @{}", impersonate_login);
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
}
// Start an HTTP server to receive the redirect from Zed's sign-in page.
let server =
tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
let port = server.server_addr().port();
cx.update(|cx| cx.open_url(&url))?;
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
// that the user is signing in from a Zed app running on the same device.
let mut url = format!(
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
*ZED_SERVER_URL, port, public_key_string
);
// Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
// access token from the query params.
//
// TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
// custom URL scheme instead of this local HTTP server.
let (user_id, access_token) = cx
.spawn(|_| async move {
for _ in 0..100 {
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
let path = req.url();
let mut user_id = None;
let mut access_token = None;
let url = Url::parse(&format!("http://example.com{}", path))
.context("failed to parse login notification url")?;
for (key, value) in url.query_pairs() {
if key == "access_token" {
access_token = Some(value.to_string());
} else if key == "user_id" {
user_id = Some(value.to_string());
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
log::info!("impersonating user @{}", impersonate_login);
write!(&mut url, "&impersonate={}", impersonate_login).unwrap();
}
open_url_tx.send(url).log_err();
// Receive the HTTP request from the user's browser. Retrieve the user id and encrypted
// access token from the query params.
//
// TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a
// custom URL scheme instead of this local HTTP server.
let (user_id, access_token) = background
.spawn(async move {
for _ in 0..100 {
if let Some(req) = server.recv_timeout(Duration::from_secs(1))? {
let path = req.url();
let mut user_id = None;
let mut access_token = None;
let url = Url::parse(&format!("http://example.com{}", path))
.context("failed to parse login notification url")?;
for (key, value) in url.query_pairs() {
if key == "access_token" {
access_token = Some(value.to_string());
} else if key == "user_id" {
user_id = Some(value.to_string());
}
}
let post_auth_url =
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
req.respond(
tiny_http::Response::empty(302).with_header(
tiny_http::Header::from_bytes(
&b"Location"[..],
post_auth_url.as_bytes(),
)
.unwrap(),
),
)
.context("failed to respond to login http request")?;
return Ok((
user_id
.ok_or_else(|| anyhow!("missing user_id parameter"))?,
access_token.ok_or_else(|| {
anyhow!("missing access_token parameter")
})?,
));
}
}
let post_auth_url =
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
req.respond(
tiny_http::Response::empty(302).with_header(
tiny_http::Header::from_bytes(
&b"Location"[..],
post_auth_url.as_bytes(),
)
.unwrap(),
),
)
.context("failed to respond to login http request")?;
return Ok((
user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?,
access_token
.ok_or_else(|| anyhow!("missing access_token parameter"))?,
));
}
}
Err(anyhow!("didn't receive login redirect"))
})
.await?;
Err(anyhow!("didn't receive login redirect"))
let access_token = private_key
.decrypt_string(&access_token)
.context("failed to decrypt access token")?;
Ok(Credentials {
user_id: user_id.parse()?,
access_token,
})
})
.await?;
let access_token = private_key
.decrypt_string(&access_token)
.context("failed to decrypt access token")?;
cx.update(|cx| cx.activate(true))?;
Ok(Credentials {
user_id: user_id.parse()?,
access_token,
})
Ok(credentials)
})
}
@@ -1120,7 +1150,7 @@ impl Client {
// Use the collab server's admin API to retrieve the id
// of the impersonated user.
let mut url = Self::get_rpc_url(http.clone(), false).await?;
let mut url = Self::get_rpc_url(http.clone(), None).await?;
url.set_path("/user");
url.set_query(Some(&format!("github_login={login}")));
let request = Request::get(url.as_str())
@@ -1319,7 +1349,7 @@ impl Client {
}
}
async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
if IMPERSONATE_LOGIN.is_some() {
return None;
}

View File

@@ -1,4 +1,6 @@
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use chrono::{DateTime, Utc};
use futures::Future;
use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
use lazy_static::lazy_static;
use parking_lot::Mutex;
@@ -20,7 +22,7 @@ pub struct Telemetry {
struct TelemetryState {
metrics_id: Option<Arc<str>>, // Per logged-in user
installation_id: Option<Arc<str>>, // Per app installation (different for dev, preview, and stable)
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<Arc<str>>, // Per app launch
release_channel: Option<&'static str>,
app_metadata: AppMetadata,
@@ -29,6 +31,7 @@ struct TelemetryState {
flush_clickhouse_events_task: Option<Task<()>>,
log_file: Option<NamedTempFile>,
is_staff: Option<bool>,
first_event_datetime: Option<DateTime<Utc>>,
}
const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
@@ -75,29 +78,39 @@ pub enum ClickhouseEvent {
vim_mode: bool,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
milliseconds_since_first_event: i64,
},
Copilot {
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
milliseconds_since_first_event: i64,
},
Call {
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
milliseconds_since_first_event: i64,
},
Assistant {
conversation_id: Option<String>,
kind: AssistantKind,
model: &'static str,
milliseconds_since_first_event: i64,
},
Cpu {
usage_as_percentage: f32,
core_count: u32,
milliseconds_since_first_event: i64,
},
Memory {
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
milliseconds_since_first_event: i64,
},
App {
operation: &'static str,
milliseconds_since_first_event: i64,
},
}
@@ -114,12 +127,13 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
impl Telemetry {
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
let release_channel = if cx.has_global::<ReleaseChannel>() {
Some(cx.global::<ReleaseChannel>().display_name())
} else {
None
};
// TODO: Replace all hardware stuff with nested SystemSpecs json
let this = Arc::new(Self {
http_client: client,
@@ -135,12 +149,34 @@ impl Telemetry {
flush_clickhouse_events_task: Default::default(),
log_file: None,
is_staff: None,
first_event_datetime: None,
}),
});
// We should only ever have one instance of Telemetry, leak the subscription to keep it alive
// rather than store in TelemetryState, complicating spawn as subscriptions are not Send
std::mem::forget(cx.on_app_quit({
let this = this.clone();
move |cx| this.shutdown_telemetry(cx)
}));
this
}
#[cfg(any(test, feature = "test-support"))]
fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
Task::ready(())
}
// Skip calling this function in tests.
// TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
#[cfg(not(any(test, feature = "test-support")))]
fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
let telemetry_settings = TelemetrySettings::get_global(cx).clone();
self.report_app_event(telemetry_settings, "close");
Task::ready(())
}
pub fn log_file_path(&self) -> Option<PathBuf> {
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
}
@@ -154,13 +190,8 @@ impl Telemetry {
let mut state = self.state.lock();
state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id.into());
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
drop(state);
if has_clickhouse_events {
self.flush_clickhouse_events();
}
let this = self.clone();
cx.spawn(|cx| async move {
// Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -190,16 +221,6 @@ impl Telemetry {
return;
};
let memory_event = ClickhouseEvent::Memory {
memory_in_bytes: process.memory(),
virtual_memory_in_bytes: process.virtual_memory(),
};
let cpu_event = ClickhouseEvent::Cpu {
usage_as_percentage: process.cpu_usage(),
core_count: system.cpus().len() as u32,
};
let telemetry_settings = if let Ok(telemetry_settings) =
cx.update(|cx| *TelemetrySettings::get_global(cx))
{
@@ -208,8 +229,16 @@ impl Telemetry {
break;
};
this.report_clickhouse_event(memory_event, telemetry_settings);
this.report_clickhouse_event(cpu_event, telemetry_settings);
this.report_memory_event(
telemetry_settings,
process.memory(),
process.virtual_memory(),
);
this.report_cpu_event(
telemetry_settings,
process.cpu_usage(),
system.cpus().len() as u32,
);
}
})
.detach();
@@ -232,10 +261,141 @@ impl Telemetry {
drop(state);
}
pub fn report_clickhouse_event(
pub fn report_editor_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
file_extension: Option<String>,
vim_mode: bool,
operation: &'static str,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
) {
let event = ClickhouseEvent::Editor {
file_extension,
vim_mode,
operation,
copilot_enabled,
copilot_enabled_for_language,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_copilot_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
) {
let event = ClickhouseEvent::Copilot {
suggestion_id,
suggestion_accepted,
file_extension,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_assistant_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
conversation_id: Option<String>,
kind: AssistantKind,
model: &'static str,
) {
let event = ClickhouseEvent::Assistant {
conversation_id,
kind,
model,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_call_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
) {
let event = ClickhouseEvent::Call {
operation,
room_id,
channel_id,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_cpu_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
usage_as_percentage: f32,
core_count: u32,
) {
let event = ClickhouseEvent::Cpu {
usage_as_percentage,
core_count,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_memory_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
) {
let event = ClickhouseEvent::Memory {
memory_in_bytes,
virtual_memory_in_bytes,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, false)
}
// app_events are called at app open and app close, so flush is set to immediately send
pub fn report_app_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
operation: &'static str,
) {
let event = ClickhouseEvent::App {
operation,
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings, true)
}
fn milliseconds_since_first_event(&self) -> i64 {
let mut state = self.state.lock();
match state.first_event_datetime {
Some(first_event_datetime) => {
let now: DateTime<Utc> = Utc::now();
now.timestamp_millis() - first_event_datetime.timestamp_millis()
}
None => {
state.first_event_datetime = Some(Utc::now());
0
}
}
}
fn report_clickhouse_event(
self: &Arc<Self>,
event: ClickhouseEvent,
telemetry_settings: TelemetrySettings,
immediate_flush: bool,
) {
if !telemetry_settings.metrics {
return;
@@ -248,7 +408,7 @@ impl Telemetry {
.push(ClickhouseEventWrapper { signed_in, event });
if state.installation_id.is_some() {
if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
drop(state);
self.flush_clickhouse_events();
} else {
@@ -276,6 +436,7 @@ impl Telemetry {
fn flush_clickhouse_events(self: &Arc<Self>) {
let mut state = self.state.lock();
state.first_event_datetime = None;
let mut events = mem::take(&mut state.clickhouse_events_queue);
state.flush_clickhouse_events_task.take();
drop(state);

View File

@@ -8,7 +8,6 @@ use rpc::{
ConnectionId, Peer, Receipt, TypedEnvelope,
};
use std::sync::Arc;
use util::http::FakeHttpClient;
pub struct FakeServer {
peer: Arc<Peer>,
@@ -195,8 +194,7 @@ impl FakeServer {
client: Arc<Client>,
cx: &mut TestAppContext,
) -> Model<UserStore> {
let http_client = FakeHttpClient::with_404_response();
let user_store = cx.build_model(|cx| UserStore::new(client, http_client, cx));
let user_store = cx.build_model(|cx| UserStore::new(client, cx));
assert_eq!(
self.receive::<proto::GetUsers>()
.await

View File

@@ -2,13 +2,12 @@ use super::{proto, Client, Status, TypedEnvelope};
use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
use gpui::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task};
use futures::{channel::mpsc, Future, StreamExt};
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use std::sync::{Arc, Weak};
use text::ReplicaId;
use util::http::HttpClient;
use util::TryFutureExt as _;
pub type UserId = u64;
@@ -20,7 +19,7 @@ pub struct ParticipantIndex(pub u32);
pub struct User {
pub id: UserId,
pub github_login: String,
pub avatar: Option<Arc<ImageData>>,
pub avatar_uri: SharedString,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -76,7 +75,6 @@ pub struct UserStore {
pending_contact_requests: HashMap<u64, usize>,
invite_info: Option<InviteInfo>,
client: Weak<Client>,
http: Arc<dyn HttpClient>,
_maintain_contacts: Task<()>,
_maintain_current_user: Task<Result<()>>,
}
@@ -112,11 +110,7 @@ enum UpdateContacts {
}
impl UserStore {
pub fn new(
client: Arc<Client>,
http: Arc<dyn HttpClient>,
cx: &mut ModelContext<Self>,
) -> Self {
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
let (mut current_user_tx, current_user_rx) = watch::channel();
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
let rpc_subscriptions = vec![
@@ -134,7 +128,6 @@ impl UserStore {
invite_info: None,
client: Arc::downgrade(&client),
update_contacts_tx,
http,
_maintain_contacts: cx.spawn(|this, mut cx| async move {
let _subscriptions = rpc_subscriptions;
while let Some(message) = update_contacts_rx.next().await {
@@ -445,6 +438,12 @@ impl UserStore {
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
}
pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
self.incoming_contact_requests
.iter()
.any(|user| user.id == user_id)
}
pub fn respond_to_contact_request(
&mut self,
requester_id: u64,
@@ -616,17 +615,14 @@ impl UserStore {
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Arc<User>>>> {
let client = self.client.clone();
let http = self.http.clone();
cx.spawn(|this, mut cx| async move {
if let Some(rpc) = client.upgrade() {
let response = rpc.request(request).await.context("error loading users")?;
let users = future::join_all(
response
.users
.into_iter()
.map(|user| User::new(user, http.as_ref())),
)
.await;
let users = response
.users
.into_iter()
.map(|user| User::new(user))
.collect::<Vec<_>>();
this.update(&mut cx, |this, _| {
for user in &users {
@@ -659,11 +655,11 @@ impl UserStore {
}
impl User {
async fn new(message: proto::User, http: &dyn HttpClient) -> Arc<Self> {
fn new(message: proto::User) -> Arc<Self> {
Arc::new(User {
id: message.id,
github_login: message.github_login,
avatar: fetch_avatar(http, &message.avatar_url).warn_on_err().await,
avatar_uri: message.avatar_url.into(),
})
}
}
@@ -696,25 +692,3 @@ impl Collaborator {
})
}
}
// todo!("we probably don't need this now that we fetch")
async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
let mut response = http
.get(url, Default::default(), true)
.await
.map_err(|e| anyhow!("failed to send user avatar request: {}", e))?;
if !response.status().is_success() {
return Err(anyhow!("avatar request failed {:?}", response.status()));
}
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.map_err(|e| anyhow!("failed to read user avatar response body: {}", e))?;
let format = image::guess_format(&body)?;
let image = image::load_from_memory_with_format(&body, format)?.into_bgra8();
Ok(Arc::new(ImageData::new(image)))
}

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.28.0"
version = "0.31.0"
publish = false
[[bin]]

View File

@@ -116,12 +116,13 @@ struct CreateUserResponse {
#[derive(Debug, Deserialize)]
struct Panic {
version: String,
release_channel: String,
text: String,
}
#[instrument(skip(panic))]
async fn trace_panic(panic: Json<Panic>) -> Result<()> {
tracing::error!(version = %panic.version, text = %panic.text, "panic report");
tracing::error!(version = %panic.version, release_channel = %panic.release_channel, text = %panic.text, "panic report");
Ok(())
}

View File

@@ -1220,6 +1220,13 @@ impl Database {
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
.await?;
if new_parent
.ancestors_including_self()
.any(|id| id == channel.id)
{
Err(anyhow!("cannot move a channel into one of its descendants"))?;
}
new_parent_path = new_parent.path();
new_parent_channel = Some(new_parent);
} else {

View File

@@ -450,6 +450,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
(livestreaming_id, &[projects_id]),
],
);
// Can't move a channel into its ancestor
db.move_channel(projects_id, Some(livestreaming_id), user_id)
.await
.unwrap_err();
let result = db.get_channels_for_user(user_id).await.unwrap();
assert_channel_tree(
result.channels,
&[
(zed_id, &[]),
(projects_id, &[]),
(livestreaming_id, &[projects_id]),
],
);
}
test_both_dbs!(

View File

@@ -4,8 +4,10 @@ use collab_ui::notifications::project_shared_notification::ProjectSharedNotifica
use editor::{Editor, ExcerptRange, MultiBuffer};
use gpui::{executor::Deterministic, geometry::vector::vec2f, TestAppContext, ViewHandle};
use live_kit_client::MacOSDisplay;
use project::project_settings::ProjectSettings;
use rpc::proto::PeerId;
use serde_json::json;
use settings::SettingsStore;
use std::{borrow::Cow, sync::Arc};
use workspace::{
dock::{test::TestPanel, DockPosition},
@@ -1602,6 +1604,141 @@ async fn test_following_across_workspaces(
});
}
#[gpui::test]
async fn test_following_into_excluded_file(
deterministic: Arc<Deterministic>,
mut cx_a: &mut TestAppContext,
mut cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
for cx in [&mut cx_a, &mut cx_b] {
cx.update(|cx| {
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
});
});
});
}
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
client_a
.fs()
.insert_tree(
"/a",
json!({
".git": {
"COMMIT_EDITMSG": "write your commit message here",
},
"1.txt": "one\none\none",
"2.txt": "two\ntwo\ntwo",
"3.txt": "three\nthree\nthree",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
active_call_a
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
.await
.unwrap();
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
.unwrap();
let window_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = window_a.root(cx_a);
let peer_id_a = client_a.peer_id().unwrap();
let window_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = window_b.root(cx_b);
// Client A opens editors for a regular file and an excluded file.
let editor_for_regular = workspace_a
.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(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
// Client A updates their selections in those editors
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(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(cx_b, |workspace, cx| {
workspace.follow(peer_id_a, cx).unwrap()
})
.await
.unwrap();
let editor_for_excluded_b = workspace_b.read_with(cx_b, |workspace, cx| {
workspace
.active_item(cx)
.unwrap()
.downcast::<Editor>()
.unwrap()
});
assert_eq!(
cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
Some((worktree_id, ".git/COMMIT_EDITMSG").into())
);
assert_eq!(
editor_for_excluded_b.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
vec![18..17]
);
// Changes from B to the excluded file are replicated in A's editor
editor_for_excluded_b.update(cx_b, |editor, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
});
deterministic.run_until_parked();
editor_for_excluded_a.update(cx_a, |editor, cx| {
assert_eq!(
editor.text(cx),
"new commit messag\nCo-Authored-By: B <b@b.b>"
);
});
}
fn visible_push_notifications(
cx: &mut TestAppContext,
) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {

View File

@@ -2981,11 +2981,10 @@ async fn test_fs_operations(
let entry = project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "c.txt"), false, cx)
.unwrap()
project.create_entry((worktree_id, "c.txt"), false, cx)
})
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
assert_eq!(
@@ -3010,7 +3009,6 @@ async fn test_fs_operations(
.update(cx_b, |project, cx| {
project.rename_entry(entry.id, Path::new("d.txt"), cx)
})
.unwrap()
.await
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
@@ -3034,11 +3032,10 @@ async fn test_fs_operations(
let dir_entry = project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR"), true, cx)
.unwrap()
project.create_entry((worktree_id, "DIR"), true, cx)
})
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
assert_eq!(
@@ -3061,25 +3058,19 @@ async fn test_fs_operations(
project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR/e.txt"), false, cx)
.unwrap()
project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
})
.await
.unwrap();
project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
.unwrap()
project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
})
.await
.unwrap();
project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
.unwrap()
project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
})
.await
.unwrap();
@@ -3120,9 +3111,7 @@ async fn test_fs_operations(
project_b
.update(cx_b, |project, cx| {
project
.copy_entry(entry.id, Path::new("f.txt"), cx)
.unwrap()
project.copy_entry(entry.id, Path::new("f.txt"), cx)
})
.await
.unwrap();
@@ -3941,7 +3930,7 @@ async fn test_collaborating_with_diagnostics(
// Ensure client B observes the new diagnostics.
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
&[(
ProjectPath {
worktree_id,
@@ -3961,14 +3950,14 @@ async fn test_collaborating_with_diagnostics(
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
project.diagnostic_summaries(cx).collect::<Vec<_>>()
project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
})));
project_c.update(cx_c, |_, cx| {
let summaries = project_c_diagnostic_summaries.clone();
cx.subscribe(&project_c, {
move |p, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
*summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
*summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
}
}
})
@@ -4018,7 +4007,7 @@ async fn test_collaborating_with_diagnostics(
deterministic.run_until_parked();
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -4034,7 +4023,7 @@ async fn test_collaborating_with_diagnostics(
});
project_c.read_with(cx_c, |project, cx| {
assert_eq!(
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -4097,13 +4086,22 @@ async fn test_collaborating_with_diagnostics(
);
deterministic.run_until_parked();
project_a.read_with(cx_a, |project, cx| {
assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
assert_eq!(
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[]
)
});
project_b.read_with(cx_b, |project, cx| {
assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
assert_eq!(
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[]
)
});
project_c.read_with(cx_c, |project, cx| {
assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
assert_eq!(
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[]
)
});
}
@@ -5052,7 +5050,7 @@ async fn test_project_search(
let mut results = HashMap::default();
let mut search_rx = project_b.update(cx_b, |project, cx| {
project.search(
SearchQuery::text("world", false, false, Vec::new(), Vec::new()).unwrap(),
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
cx,
)
});

View File

@@ -665,7 +665,6 @@ impl RandomizedTest for ProjectCollaborationTest {
ensure_project_shared(&project, client, cx).await;
project
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
.unwrap()
.await?;
}
@@ -869,7 +868,8 @@ impl RandomizedTest for ProjectCollaborationTest {
let mut search = project.update(cx, |project, cx| {
project.search(
SearchQuery::text(query, false, false, Vec::new(), Vec::new()).unwrap(),
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
.unwrap(),
cx,
)
});

View File

@@ -10,7 +10,7 @@ publish = false
name = "collab2"
[[bin]]
name = "seed"
name = "seed2"
required-features = ["seed-support"]
[dependencies]
@@ -81,7 +81,7 @@ settings = { package = "settings2", path = "../settings2", features = ["test-sup
theme = { package = "theme2", path = "../theme2" }
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
collab_ui = { path = "../collab_ui", features = ["test-support"] }
collab_ui = { path = "../collab_ui2", package = "collab_ui2", features = ["test-support"] }
async-trait.workspace = true
pretty_assertions.workspace = true

View File

@@ -1220,6 +1220,13 @@ impl Database {
self.check_user_is_channel_admin(&new_parent, admin_id, &*tx)
.await?;
if new_parent
.ancestors_including_self()
.any(|id| id == channel.id)
{
Err(anyhow!("cannot move a channel into one of its descendants"))?;
}
new_parent_path = new_parent.path();
new_parent_channel = Some(new_parent);
} else {

View File

@@ -420,8 +420,6 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
.await
.unwrap();
// Dag is: zed - projects - livestreaming
// Move to same parent should be a no-op
assert!(db
.move_channel(projects_id, Some(zed_id), user_id)
@@ -450,6 +448,20 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
(livestreaming_id, &[projects_id]),
],
);
// Can't move a channel into its ancestor
db.move_channel(projects_id, Some(livestreaming_id), user_id)
.await
.unwrap_err();
let result = db.get_channels_for_user(user_id).await.unwrap();
assert_channel_tree(
result.channels,
&[
(zed_id, &[]),
(projects_id, &[]),
(livestreaming_id, &[projects_id]),
],
);
}
test_both_dbs!(

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ use call::ActiveCall;
use channel::{ChannelId, ChannelMembership, ChannelStore};
use client::User;
use futures::future::try_join_all;
use gpui::{BackgroundExecutor, Model, TestAppContext};
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
use rpc::{
proto::{self, ChannelRole},
RECEIVE_TIMEOUT,
@@ -46,13 +46,13 @@ async fn test_core_channels(
&[
ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
depth: 0,
role: ChannelRole::Admin,
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
name: "channel-b".into(),
depth: 1,
role: ChannelRole::Admin,
},
@@ -92,7 +92,7 @@ async fn test_core_channels(
cx_b,
&[ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
depth: 0,
role: ChannelRole::Member,
}],
@@ -140,13 +140,13 @@ async fn test_core_channels(
&[
ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
role: ChannelRole::Member,
depth: 0,
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
name: "channel-b".into(),
role: ChannelRole::Member,
depth: 1,
},
@@ -168,19 +168,19 @@ async fn test_core_channels(
&[
ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
role: ChannelRole::Member,
depth: 0,
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
name: "channel-b".into(),
role: ChannelRole::Member,
depth: 1,
},
ExpectedChannel {
id: channel_c_id,
name: "channel-c".to_string(),
name: "channel-c".into(),
role: ChannelRole::Member,
depth: 2,
},
@@ -211,19 +211,19 @@ async fn test_core_channels(
&[
ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
depth: 0,
role: ChannelRole::Admin,
},
ExpectedChannel {
id: channel_b_id,
name: "channel-b".to_string(),
name: "channel-b".into(),
depth: 1,
role: ChannelRole::Admin,
},
ExpectedChannel {
id: channel_c_id,
name: "channel-c".to_string(),
name: "channel-c".into(),
depth: 2,
role: ChannelRole::Admin,
},
@@ -245,7 +245,7 @@ async fn test_core_channels(
cx_a,
&[ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
depth: 0,
role: ChannelRole::Admin,
}],
@@ -255,7 +255,7 @@ async fn test_core_channels(
cx_b,
&[ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
depth: 0,
role: ChannelRole::Admin,
}],
@@ -278,7 +278,7 @@ async fn test_core_channels(
cx_a,
&[ExpectedChannel {
id: channel_a_id,
name: "channel-a".to_string(),
name: "channel-a".into(),
depth: 0,
role: ChannelRole::Admin,
}],
@@ -309,7 +309,7 @@ async fn test_core_channels(
cx_a,
&[ExpectedChannel {
id: channel_a_id,
name: "channel-a-renamed".to_string(),
name: "channel-a-renamed".into(),
depth: 0,
role: ChannelRole::Admin,
}],
@@ -418,7 +418,7 @@ async fn test_channel_room(
cx_b,
&[ExpectedChannel {
id: zed_id,
name: "zed".to_string(),
name: "zed".into(),
depth: 0,
role: ChannelRole::Member,
}],
@@ -680,7 +680,7 @@ async fn test_permissions_update_while_invited(
&[ExpectedChannel {
depth: 0,
id: rust_id,
name: "rust".to_string(),
name: "rust".into(),
role: ChannelRole::Member,
}],
);
@@ -708,7 +708,7 @@ async fn test_permissions_update_while_invited(
&[ExpectedChannel {
depth: 0,
id: rust_id,
name: "rust".to_string(),
name: "rust".into(),
role: ChannelRole::Member,
}],
);
@@ -747,7 +747,7 @@ async fn test_channel_rename(
&[ExpectedChannel {
depth: 0,
id: rust_id,
name: "rust-archive".to_string(),
name: "rust-archive".into(),
role: ChannelRole::Admin,
}],
);
@@ -759,7 +759,7 @@ async fn test_channel_rename(
&[ExpectedChannel {
depth: 0,
id: rust_id,
name: "rust-archive".to_string(),
name: "rust-archive".into(),
role: ChannelRole::Member,
}],
);
@@ -888,7 +888,7 @@ async fn test_lost_channel_creation(
&[ExpectedChannel {
depth: 0,
id: channel_id,
name: "x".to_string(),
name: "x".into(),
role: ChannelRole::Member,
}],
);
@@ -912,13 +912,13 @@ async fn test_lost_channel_creation(
ExpectedChannel {
depth: 0,
id: channel_id,
name: "x".to_string(),
name: "x".into(),
role: ChannelRole::Admin,
},
ExpectedChannel {
depth: 1,
id: subchannel_id,
name: "subchannel".to_string(),
name: "subchannel".into(),
role: ChannelRole::Admin,
},
],
@@ -943,13 +943,13 @@ async fn test_lost_channel_creation(
ExpectedChannel {
depth: 0,
id: channel_id,
name: "x".to_string(),
name: "x".into(),
role: ChannelRole::Member,
},
ExpectedChannel {
depth: 1,
id: subchannel_id,
name: "subchannel".to_string(),
name: "subchannel".into(),
role: ChannelRole::Member,
},
],
@@ -1221,13 +1221,13 @@ async fn test_channel_membership_notifications(
ExpectedChannel {
depth: 0,
id: zed_channel,
name: "zed".to_string(),
name: "zed".into(),
role: ChannelRole::Guest,
},
ExpectedChannel {
depth: 1,
id: vim_channel,
name: "vim".to_string(),
name: "vim".into(),
role: ChannelRole::Member,
},
],
@@ -1250,13 +1250,13 @@ async fn test_channel_membership_notifications(
ExpectedChannel {
depth: 0,
id: zed_channel,
name: "zed".to_string(),
name: "zed".into(),
role: ChannelRole::Guest,
},
ExpectedChannel {
depth: 1,
id: vim_channel,
name: "vim".to_string(),
name: "vim".into(),
role: ChannelRole::Guest,
},
],
@@ -1476,7 +1476,7 @@ async fn test_channel_moving(
struct ExpectedChannel {
depth: usize,
id: ChannelId,
name: String,
name: SharedString,
role: ChannelRole,
}
@@ -1515,7 +1515,7 @@ fn assert_channels(
.ordered_channels()
.map(|(depth, channel)| ExpectedChannel {
depth,
name: channel.name.clone(),
name: channel.name.clone().into(),
id: channel.id,
role: channel.role,
})

View File

@@ -4,10 +4,12 @@
// use call::ActiveCall;
// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
// use editor::{Editor, ExcerptRange, MultiBuffer};
// use gpui::{BackgroundExecutor, TestAppContext, View};
// use gpui::{point, BackgroundExecutor, TestAppContext, View, VisualTestContext, WindowContext};
// use live_kit_client::MacOSDisplay;
// use project::project_settings::ProjectSettings;
// use rpc::proto::PeerId;
// use serde_json::json;
// use settings::SettingsStore;
// use std::borrow::Cow;
// use workspace::{
// dock::{test::TestPanel, DockPosition},
@@ -24,7 +26,7 @@
// cx_c: &mut TestAppContext,
// cx_d: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// let client_c = server.create_client(cx_c, "user_c").await;
@@ -71,12 +73,22 @@
// .unwrap();
// let window_a = client_a.build_workspace(&project_a, cx_a);
// let workspace_a = window_a.root(cx_a);
// let workspace_a = window_a.root(cx_a).unwrap();
// let window_b = client_b.build_workspace(&project_b, cx_b);
// let workspace_b = window_b.root(cx_b);
// let workspace_b = window_b.root(cx_b).unwrap();
// todo!("could be wrong")
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
// let cx_a = &mut cx_a;
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
// let cx_b = &mut cx_b;
// let mut cx_c = VisualTestContext::from_window(*window_c, cx_c);
// let cx_c = &mut cx_c;
// let mut cx_d = VisualTestContext::from_window(*window_d, cx_d);
// let cx_d = &mut cx_d;
// // Client A opens some editors.
// let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
// let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
// let editor_a1 = workspace_a
// .update(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -132,8 +144,8 @@
// .await
// .unwrap();
// cx_c.foreground().run_until_parked();
// let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
// cx_c.executor().run_until_parked();
// let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
// workspace
// .active_item(cx)
// .unwrap()
@@ -145,19 +157,19 @@
// Some((worktree_id, "2.txt").into())
// );
// assert_eq!(
// editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
// editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
// vec![2..1]
// );
// assert_eq!(
// editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
// editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
// vec![3..2]
// );
// cx_c.foreground().run_until_parked();
// cx_c.executor().run_until_parked();
// let active_call_c = cx_c.read(ActiveCall::global);
// let project_c = client_c.build_remote_project(project_id, cx_c).await;
// let window_c = client_c.build_workspace(&project_c, cx_c);
// let workspace_c = window_c.root(cx_c);
// let workspace_c = window_c.root(cx_c).unwrap();
// active_call_c
// .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
// .await
@@ -172,10 +184,13 @@
// .await
// .unwrap();
// cx_d.foreground().run_until_parked();
// cx_d.executor().run_until_parked();
// let active_call_d = cx_d.read(ActiveCall::global);
// let project_d = client_d.build_remote_project(project_id, cx_d).await;
// let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
// let workspace_d = client_d
// .build_workspace(&project_d, cx_d)
// .root(cx_d)
// .unwrap();
// active_call_d
// .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
// .await
@@ -183,7 +198,7 @@
// drop(project_d);
// // All clients see that clients B and C are following client A.
// cx_c.foreground().run_until_parked();
// cx_c.executor().run_until_parked();
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
// assert_eq!(
// followers_by_leader(project_id, cx),
@@ -198,7 +213,7 @@
// });
// // All clients see that clients B is following client A.
// cx_c.foreground().run_until_parked();
// cx_c.executor().run_until_parked();
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
// assert_eq!(
// followers_by_leader(project_id, cx),
@@ -216,7 +231,7 @@
// .unwrap();
// // All clients see that clients B and C are following client A.
// cx_c.foreground().run_until_parked();
// cx_c.executor().run_until_parked();
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
// assert_eq!(
// followers_by_leader(project_id, cx),
@@ -240,7 +255,7 @@
// .unwrap();
// // All clients see that D is following C
// cx_d.foreground().run_until_parked();
// cx_d.executor().run_until_parked();
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
// assert_eq!(
// followers_by_leader(project_id, cx),
@@ -257,7 +272,7 @@
// cx_c.drop_last(workspace_c);
// // Clients A and B see that client B is following A, and client C is not present in the followers.
// cx_c.foreground().run_until_parked();
// cx_c.executor().run_until_parked();
// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
// assert_eq!(
// followers_by_leader(project_id, cx),
@@ -271,12 +286,15 @@
// workspace.activate_item(&editor_a1, cx)
// });
// executor.run_until_parked();
// workspace_b.read_with(cx_b, |workspace, cx| {
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
// workspace_b.update(cx_b, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().item_id(),
// editor_b1.item_id()
// );
// });
// // When client A opens a multibuffer, client B does so as well.
// let multibuffer_a = cx_a.add_model(|cx| {
// let multibuffer_a = cx_a.build_model(|cx| {
// let buffer_a1 = project_a.update(cx, |project, cx| {
// project
// .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
@@ -308,12 +326,12 @@
// });
// let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
// let editor =
// cx.add_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
// cx.build_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
// workspace.add_item(Box::new(editor.clone()), cx);
// editor
// });
// executor.run_until_parked();
// let multibuffer_editor_b = workspace_b.read_with(cx_b, |workspace, cx| {
// let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {
// workspace
// .active_item(cx)
// .unwrap()
@@ -321,8 +339,8 @@
// .unwrap()
// });
// assert_eq!(
// multibuffer_editor_a.read_with(cx_a, |editor, cx| editor.text(cx)),
// multibuffer_editor_b.read_with(cx_b, |editor, cx| editor.text(cx)),
// multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),
// multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),
// );
// // When client A navigates back and forth, client B does so as well.
@@ -333,8 +351,11 @@
// .await
// .unwrap();
// executor.run_until_parked();
// workspace_b.read_with(cx_b, |workspace, cx| {
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
// workspace_b.update(cx_b, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().item_id(),
// editor_b1.item_id()
// );
// });
// workspace_a
@@ -344,8 +365,11 @@
// .await
// .unwrap();
// executor.run_until_parked();
// workspace_b.read_with(cx_b, |workspace, cx| {
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
// workspace_b.update(cx_b, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().item_id(),
// editor_b2.item_id()
// );
// });
// workspace_a
@@ -355,8 +379,11 @@
// .await
// .unwrap();
// executor.run_until_parked();
// workspace_b.read_with(cx_b, |workspace, cx| {
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
// workspace_b.update(cx_b, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().item_id(),
// editor_b1.item_id()
// );
// });
// // Changes to client A's editor are reflected on client B.
@@ -364,20 +391,20 @@
// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
// });
// executor.run_until_parked();
// editor_b1.read_with(cx_b, |editor, cx| {
// editor_b1.update(cx_b, |editor, cx| {
// assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
// });
// editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
// executor.run_until_parked();
// editor_b1.read_with(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
// editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
// editor_a1.update(cx_a, |editor, cx| {
// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
// editor.set_scroll_position(vec2f(0., 100.), cx);
// editor.set_scroll_position(point(0., 100.), cx);
// });
// executor.run_until_parked();
// editor_b1.read_with(cx_b, |editor, cx| {
// editor_b1.update(cx_b, |editor, cx| {
// assert_eq!(editor.selections.ranges(cx), &[3..3]);
// });
@@ -390,11 +417,11 @@
// });
// executor.run_until_parked();
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, cx| workspace
// workspace_b.update(cx_b, |workspace, cx| workspace
// .active_item(cx)
// .unwrap()
// .id()),
// editor_b1.id()
// .item_id()),
// editor_b1.item_id()
// );
// // Client A starts following client B.
@@ -405,15 +432,15 @@
// .await
// .unwrap();
// assert_eq!(
// workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
// workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
// Some(peer_id_b)
// );
// assert_eq!(
// workspace_a.read_with(cx_a, |workspace, cx| workspace
// workspace_a.update(cx_a, |workspace, cx| workspace
// .active_item(cx)
// .unwrap()
// .id()),
// editor_a1.id()
// .item_id()),
// editor_a1.item_id()
// );
// // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
@@ -432,7 +459,7 @@
// .await
// .unwrap();
// executor.run_until_parked();
// let shared_screen = workspace_a.read_with(cx_a, |workspace, cx| {
// let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
// workspace
// .active_item(cx)
// .expect("no active item")
@@ -446,8 +473,11 @@
// .await
// .unwrap();
// executor.run_until_parked();
// workspace_a.read_with(cx_a, |workspace, cx| {
// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_a1.id())
// workspace_a.update(cx_a, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().item_id(),
// editor_a1.item_id()
// )
// });
// // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
@@ -455,26 +485,26 @@
// workspace.activate_item(&multibuffer_editor_b, cx)
// });
// executor.run_until_parked();
// workspace_a.read_with(cx_a, |workspace, cx| {
// workspace_a.update(cx_a, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().id(),
// multibuffer_editor_a.id()
// workspace.active_item(cx).unwrap().item_id(),
// multibuffer_editor_a.item_id()
// )
// });
// // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
// let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
// let panel = window_b.build_view(cx_b, |_| TestPanel::new(DockPosition::Left));
// workspace_b.update(cx_b, |workspace, cx| {
// workspace.add_panel(panel, cx);
// workspace.toggle_panel_focus::<TestPanel>(cx);
// });
// executor.run_until_parked();
// assert_eq!(
// workspace_a.read_with(cx_a, |workspace, cx| workspace
// workspace_a.update(cx_a, |workspace, cx| workspace
// .active_item(cx)
// .unwrap()
// .id()),
// shared_screen.id()
// .item_id()),
// shared_screen.item_id()
// );
// // Toggling the focus back to the pane causes client A to return to the multibuffer.
@@ -482,16 +512,16 @@
// workspace.toggle_panel_focus::<TestPanel>(cx);
// });
// executor.run_until_parked();
// workspace_a.read_with(cx_a, |workspace, cx| {
// workspace_a.update(cx_a, |workspace, cx| {
// assert_eq!(
// workspace.active_item(cx).unwrap().id(),
// multibuffer_editor_a.id()
// workspace.active_item(cx).unwrap().item_id(),
// multibuffer_editor_a.item_id()
// )
// });
// // Client B activates an item that doesn't implement following,
// // so the previously-opened screen-sharing item gets activated.
// let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
// let unfollowable_item = window_b.build_view(cx_b, |_| TestItem::new());
// workspace_b.update(cx_b, |workspace, cx| {
// workspace.active_pane().update(cx, |pane, cx| {
// pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
@@ -499,18 +529,18 @@
// });
// executor.run_until_parked();
// assert_eq!(
// workspace_a.read_with(cx_a, |workspace, cx| workspace
// workspace_a.update(cx_a, |workspace, cx| workspace
// .active_item(cx)
// .unwrap()
// .id()),
// shared_screen.id()
// .item_id()),
// shared_screen.item_id()
// );
// // Following interrupts when client B disconnects.
// client_b.disconnect(&cx_b.to_async());
// executor.advance_clock(RECONNECT_TIMEOUT);
// assert_eq!(
// workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
// workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
// None
// );
// }
@@ -521,7 +551,7 @@
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
@@ -560,13 +590,19 @@
// .await
// .unwrap();
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
// let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
// let workspace_a = client_a
// .build_workspace(&project_a, cx_a)
// .root(cx_a)
// .unwrap();
// let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
// let workspace_b = client_b
// .build_workspace(&project_b, cx_b)
// .root(cx_b)
// .unwrap();
// let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
// let client_b_id = project_a.read_with(cx_a, |project, _| {
// let client_b_id = project_a.update(cx_a, |project, _| {
// project.collaborators().values().next().unwrap().peer_id
// });
@@ -584,7 +620,7 @@
// .await
// .unwrap();
// let pane_paths = |pane: &ViewHandle<workspace::Pane>, cx: &mut TestAppContext| {
// let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
// pane.update(cx, |pane, cx| {
// pane.items()
// .map(|item| {
@@ -642,7 +678,7 @@
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
@@ -685,7 +721,10 @@
// .unwrap();
// // Client A opens a file.
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
// let workspace_a = client_a
// .build_workspace(&project_a, cx_a)
// .root(cx_a)
// .unwrap();
// workspace_a
// .update(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -696,7 +735,10 @@
// .unwrap();
// // Client B opens a different file.
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// let workspace_b = client_b
// .build_workspace(&project_b, cx_b)
// .root(cx_b)
// .unwrap();
// workspace_b
// .update(cx_b, |workspace, cx| {
// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
@@ -1167,7 +1209,7 @@
// cx_b: &mut TestAppContext,
// ) {
// // 2 clients connect to a server.
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
@@ -1207,8 +1249,17 @@
// .await
// .unwrap();
// todo!("could be wrong")
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
// let cx_a = &mut cx_a;
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
// let cx_b = &mut cx_b;
// // Client A opens some editors.
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
// let workspace_a = client_a
// .build_workspace(&project_a, cx_a)
// .root(cx_a)
// .unwrap();
// let _editor_a1 = workspace_a
// .update(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -1219,9 +1270,12 @@
// .unwrap();
// // Client B starts following client A.
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
// let leader_id = project_b.read_with(cx_b, |project, _| {
// let workspace_b = client_b
// .build_workspace(&project_b, cx_b)
// .root(cx_b)
// .unwrap();
// let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
// let leader_id = project_b.update(cx_b, |project, _| {
// project.collaborators().values().next().unwrap().peer_id
// });
// workspace_b
@@ -1231,10 +1285,10 @@
// .await
// .unwrap();
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// Some(leader_id)
// );
// let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
// let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
// workspace
// .active_item(cx)
// .unwrap()
@@ -1245,7 +1299,7 @@
// // When client B moves, it automatically stops following client A.
// editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// None
// );
@@ -1256,14 +1310,14 @@
// .await
// .unwrap();
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// Some(leader_id)
// );
// // When client B edits, it automatically stops following client A.
// editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// None
// );
@@ -1274,16 +1328,16 @@
// .await
// .unwrap();
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// Some(leader_id)
// );
// // When client B scrolls, it automatically stops following client A.
// editor_b2.update(cx_b, |editor, cx| {
// editor.set_scroll_position(vec2f(0., 3.), cx)
// editor.set_scroll_position(point(0., 3.), cx)
// });
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// None
// );
@@ -1294,7 +1348,7 @@
// .await
// .unwrap();
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// Some(leader_id)
// );
@@ -1303,13 +1357,13 @@
// workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
// });
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// Some(leader_id)
// );
// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// Some(leader_id)
// );
@@ -1321,7 +1375,7 @@
// .await
// .unwrap();
// assert_eq!(
// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
// None
// );
// }
@@ -1332,7 +1386,7 @@
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
@@ -1345,20 +1399,26 @@
// client_a.fs().insert_tree("/a", json!({})).await;
// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
// let workspace_a = client_a
// .build_workspace(&project_a, cx_a)
// .root(cx_a)
// .unwrap();
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
// .unwrap();
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// let workspace_b = client_b
// .build_workspace(&project_b, cx_b)
// .root(cx_b)
// .unwrap();
// executor.run_until_parked();
// let client_a_id = project_b.read_with(cx_b, |project, _| {
// let client_a_id = project_b.update(cx_b, |project, _| {
// project.collaborators().values().next().unwrap().peer_id
// });
// let client_b_id = project_a.read_with(cx_a, |project, _| {
// let client_b_id = project_a.update(cx_a, |project, _| {
// project.collaborators().values().next().unwrap().peer_id
// });
@@ -1370,13 +1430,13 @@
// });
// futures::try_join!(a_follow_b, b_follow_a).unwrap();
// workspace_a.read_with(cx_a, |workspace, _| {
// workspace_a.update(cx_a, |workspace, _| {
// assert_eq!(
// workspace.leader_for_pane(workspace.active_pane()),
// Some(client_b_id)
// );
// });
// workspace_b.read_with(cx_b, |workspace, _| {
// workspace_b.update(cx_b, |workspace, _| {
// assert_eq!(
// workspace.leader_for_pane(workspace.active_pane()),
// Some(client_a_id)
@@ -1398,7 +1458,7 @@
// // b opens a different file in project 2, a follows b
// // b opens a different file in project 1, a cannot follow b
// // b shares the project, a joins the project and follows b
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// cx_a.update(editor::init);
@@ -1435,8 +1495,14 @@
// let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
// let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// let workspace_a = client_a
// .build_workspace(&project_a, cx_a)
// .root(cx_a)
// .unwrap();
// let workspace_b = client_b
// .build_workspace(&project_b, cx_b)
// .root(cx_b)
// .unwrap();
// cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
// cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
@@ -1455,6 +1521,12 @@
// .await
// .unwrap();
// todo!("could be wrong")
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
// let cx_a = &mut cx_a;
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
// let cx_b = &mut cx_b;
// workspace_a
// .update(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
@@ -1476,11 +1548,12 @@
// let workspace_b_project_a = cx_b
// .windows()
// .iter()
// .max_by_key(|window| window.id())
// .max_by_key(|window| window.item_id())
// .unwrap()
// .downcast::<Workspace>()
// .unwrap()
// .root(cx_b);
// .root(cx_b)
// .unwrap();
// // assert that b is following a in project a in w.rs
// workspace_b_project_a.update(cx_b, |workspace, cx| {
@@ -1534,7 +1607,7 @@
// workspace.leader_for_pane(workspace.active_pane())
// );
// let item = workspace.active_pane().read(cx).active_item().unwrap();
// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
// assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
// });
// // b moves to y.rs in b's project, a is still following but can't yet see
@@ -1578,11 +1651,12 @@
// let workspace_a_project_b = cx_a
// .windows()
// .iter()
// .max_by_key(|window| window.id())
// .max_by_key(|window| window.item_id())
// .unwrap()
// .downcast::<Workspace>()
// .unwrap()
// .root(cx_a);
// .root(cx_a)
// .unwrap();
// workspace_a_project_b.update(cx_a, |workspace, cx| {
// assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
@@ -1596,12 +1670,151 @@
// });
// }
// #[gpui::test]
// async fn test_following_into_excluded_file(
// executor: BackgroundExecutor,
// mut cx_a: &mut TestAppContext,
// mut cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// for cx in [&mut cx_a, &mut cx_b] {
// cx.update(|cx| {
// cx.update_global::<SettingsStore, _>(|store, cx| {
// store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
// project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
// });
// });
// });
// }
// server
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
// .await;
// let active_call_a = cx_a.read(ActiveCall::global);
// let active_call_b = cx_b.read(ActiveCall::global);
// cx_a.update(editor::init);
// cx_b.update(editor::init);
// client_a
// .fs()
// .insert_tree(
// "/a",
// json!({
// ".git": {
// "COMMIT_EDITMSG": "write your commit message here",
// },
// "1.txt": "one\none\none",
// "2.txt": "two\ntwo\ntwo",
// "3.txt": "three\nthree\nthree",
// }),
// )
// .await;
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
// active_call_a
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
// .await
// .unwrap();
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
// .unwrap();
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
// active_call_b
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
// .await
// .unwrap();
// let window_a = client_a.build_workspace(&project_a, cx_a);
// let workspace_a = window_a.root(cx_a).unwrap();
// let peer_id_a = client_a.peer_id().unwrap();
// let window_b = client_b.build_workspace(&project_b, cx_b);
// let workspace_b = window_b.root(cx_b).unwrap();
// todo!("could be wrong")
// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
// let cx_a = &mut cx_a;
// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
// let cx_b = &mut cx_b;
// // Client A opens editors for a regular file and an excluded file.
// let editor_for_regular = workspace_a
// .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(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
// })
// .await
// .unwrap()
// .downcast::<Editor>()
// .unwrap();
// // Client A updates their selections in those editors
// 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(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(cx_b, |workspace, cx| {
// workspace.follow(peer_id_a, cx).unwrap()
// })
// .await
// .unwrap();
// let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
// workspace
// .active_item(cx)
// .unwrap()
// .downcast::<Editor>()
// .unwrap()
// });
// assert_eq!(
// cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
// Some((worktree_id, ".git/COMMIT_EDITMSG").into())
// );
// assert_eq!(
// editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
// vec![18..17]
// );
// // Changes from B to the excluded file are replicated in A's editor
// 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| {
// assert_eq!(
// editor.text(cx),
// "new commit messag\nCo-Authored-By: B <b@b.b>"
// );
// });
// }
// fn visible_push_notifications(
// cx: &mut TestAppContext,
// ) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
// ) -> Vec<gpui::View<ProjectSharedNotification>> {
// let mut ret = Vec::new();
// for window in cx.windows() {
// window.read_with(cx, |window| {
// window.update(cx, |window| {
// if let Some(handle) = window
// .root_view()
// .clone()
@@ -1645,8 +1858,8 @@
// })
// }
// fn pane_summaries(workspace: &ViewHandle<Workspace>, cx: &mut TestAppContext) -> Vec<PaneSummary> {
// workspace.read_with(cx, |workspace, cx| {
// fn pane_summaries(workspace: &View<Workspace>, cx: &mut WindowContext<'_>) -> Vec<PaneSummary> {
// workspace.update(cx, |workspace, cx| {
// let active_pane = workspace.active_pane();
// workspace
// .panes()

View File

@@ -1823,7 +1823,7 @@ async fn test_active_call_events(
owner: Arc::new(User {
id: client_a.user_id().unwrap(),
github_login: "user_a".to_string(),
avatar: None,
avatar_uri: "avatar_a".into(),
}),
project_id: project_a_id,
worktree_root_names: vec!["a".to_string()],
@@ -1841,7 +1841,7 @@ async fn test_active_call_events(
owner: Arc::new(User {
id: client_b.user_id().unwrap(),
github_login: "user_b".to_string(),
avatar: None,
avatar_uri: "avatar_b".into(),
}),
project_id: project_b_id,
worktree_root_names: vec!["b".to_string()]
@@ -2781,11 +2781,10 @@ async fn test_fs_operations(
let entry = project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "c.txt"), false, cx)
.unwrap()
project.create_entry((worktree_id, "c.txt"), false, cx)
})
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
@@ -2812,8 +2811,8 @@ async fn test_fs_operations(
.update(cx_b, |project, cx| {
project.rename_entry(entry.id, Path::new("d.txt"), cx)
})
.unwrap()
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
@@ -2838,11 +2837,10 @@ async fn test_fs_operations(
let dir_entry = project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR"), true, cx)
.unwrap()
project.create_entry((worktree_id, "DIR"), true, cx)
})
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
@@ -2867,27 +2865,24 @@ async fn test_fs_operations(
project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR/e.txt"), false, cx)
.unwrap()
project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
})
.await
.unwrap()
.unwrap();
project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
.unwrap()
project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
})
.await
.unwrap()
.unwrap();
project_b
.update(cx_b, |project, cx| {
project
.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
.unwrap()
project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
})
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
@@ -2928,11 +2923,10 @@ async fn test_fs_operations(
project_b
.update(cx_b, |project, cx| {
project
.copy_entry(entry.id, Path::new("f.txt"), cx)
.unwrap()
project.copy_entry(entry.id, Path::new("f.txt"), cx)
})
.await
.unwrap()
.unwrap();
worktree_a.read_with(cx_a, |worktree, _| {
@@ -3685,7 +3679,7 @@ async fn test_collaborating_with_diagnostics(
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
&[(
ProjectPath {
worktree_id,
@@ -3705,14 +3699,14 @@ async fn test_collaborating_with_diagnostics(
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
project.diagnostic_summaries(cx).collect::<Vec<_>>()
project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
})));
project_c.update(cx_c, |_, cx| {
let summaries = project_c_diagnostic_summaries.clone();
cx.subscribe(&project_c, {
move |p, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
*summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
*summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
}
}
})
@@ -3763,7 +3757,7 @@ async fn test_collaborating_with_diagnostics(
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -3780,7 +3774,7 @@ async fn test_collaborating_with_diagnostics(
project_c.read_with(cx_c, |project, cx| {
assert_eq!(
project.diagnostic_summaries(cx).collect::<Vec<_>>(),
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -3841,15 +3835,24 @@ async fn test_collaborating_with_diagnostics(
executor.run_until_parked();
project_a.read_with(cx_a, |project, cx| {
assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
assert_eq!(
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[]
)
});
project_b.read_with(cx_b, |project, cx| {
assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
assert_eq!(
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[]
)
});
project_c.read_with(cx_c, |project, cx| {
assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
assert_eq!(
project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[]
)
});
}
@@ -4599,7 +4602,7 @@ async fn test_project_search(
let mut results = HashMap::default();
let mut search_rx = project_b.update(cx_b, |project, cx| {
project.search(
SearchQuery::text("world", false, false, Vec::new(), Vec::new()).unwrap(),
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
cx,
)
});

View File

@@ -3,7 +3,7 @@ use crate::db::ChannelRole;
use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
use anyhow::Result;
use async_trait::async_trait;
use gpui::{BackgroundExecutor, TestAppContext};
use gpui::{BackgroundExecutor, SharedString, TestAppContext};
use rand::prelude::*;
use serde_derive::{Deserialize, Serialize};
use std::{
@@ -30,13 +30,13 @@ struct RandomChannelBufferTest;
#[derive(Clone, Serialize, Deserialize)]
enum ChannelBufferOperation {
JoinChannelNotes {
channel_name: String,
channel_name: SharedString,
},
LeaveChannelNotes {
channel_name: String,
channel_name: SharedString,
},
EditChannelNotes {
channel_name: String,
channel_name: SharedString,
edits: Vec<(Range<usize>, Arc<str>)>,
},
Noop,

View File

@@ -665,7 +665,6 @@ impl RandomizedTest for ProjectCollaborationTest {
ensure_project_shared(&project, client, cx).await;
project
.update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
.unwrap()
.await?;
}
@@ -870,7 +869,8 @@ impl RandomizedTest for ProjectCollaborationTest {
let mut search = project.update(cx, |project, cx| {
project.search(
SearchQuery::text(query, false, false, Vec::new(), Vec::new()).unwrap(),
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
.unwrap(),
cx,
)
});

View File

@@ -13,7 +13,7 @@ use client::{
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use gpui::{BackgroundExecutor, Context, Model, TestAppContext, WindowHandle};
use gpui::{BackgroundExecutor, Context, Model, TestAppContext, View, VisualTestContext};
use language::LanguageRegistry;
use node_runtime::FakeNodeRuntime;
@@ -149,7 +149,7 @@ impl TestServer {
.user_id
};
let client_name = name.to_string();
let mut client = cx.read(|cx| Client::new(http.clone(), cx));
let mut client = cx.update(|cx| Client::new(http.clone(), cx));
let server = self.server.clone();
let db = self.app_state.db.clone();
let connection_killers = self.connection_killers.clone();
@@ -209,7 +209,7 @@ impl TestServer {
});
let fs = FakeFs::new(cx.executor());
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
let mut language_registry = LanguageRegistry::test();
language_registry.set_executor(cx.executor());
@@ -220,12 +220,11 @@ impl TestServer {
languages: Arc::new(language_registry),
fs: fs.clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _, _| gpui::Task::ready(Ok(())),
node_runtime: FakeNodeRuntime::new(),
});
cx.update(|cx| {
theme::init(cx);
theme::init(theme::LoadThemes::JustBase, cx);
Project::init(&client, cx);
client::init(&client, cx);
language::init(cx);
@@ -603,14 +602,12 @@ impl TestClient {
.unwrap()
}
//todo(workspace)
#[allow(dead_code)]
pub fn build_workspace(
&self,
pub fn build_workspace<'a>(
&'a self,
project: &Model<Project>,
cx: &mut TestAppContext,
) -> WindowHandle<Workspace> {
cx.add_window(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx))
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx))
}
}

View File

@@ -14,14 +14,8 @@ use std::{sync::Arc, time::Duration};
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
lazy_static! {
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
"@[-_\\w]+",
false,
false,
Default::default(),
Default::default()
)
.unwrap();
static ref MENTIONS_SEARCH: SearchQuery =
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
}
pub struct MessageEditor {

View File

@@ -0,0 +1,81 @@
[package]
name = "collab_ui2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/collab_ui.rs"
doctest = false
[features]
test-support = [
"call/test-support",
"client/test-support",
"collections/test-support",
"editor/test-support",
"gpui/test-support",
"project/test-support",
"settings/test-support",
"util/test-support",
"workspace/test-support",
]
[dependencies]
auto_update = { package = "auto_update2", path = "../auto_update2" }
db = { package = "db2", path = "../db2" }
call = { package = "call2", path = "../call2" }
client = { package = "client2", path = "../client2" }
channel = { package = "channel2", path = "../channel2" }
clock = { path = "../clock" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
# drag_and_drop = { path = "../drag_and_drop" }
editor = { package="editor2", path = "../editor2" }
feedback = { package = "feedback2", path = "../feedback2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
menu = { package = "menu2", path = "../menu2" }
notifications = { package = "notifications2", path = "../notifications2" }
rich_text = { package = "rich_text2", path = "../rich_text2" }
picker = { package = "picker2", path = "../picker2" }
project = { package = "project2", path = "../project2" }
recent_projects = { package = "recent_projects2", path = "../recent_projects2" }
rpc = { package ="rpc2", path = "../rpc2" }
settings = { package = "settings2", path = "../settings2" }
feature_flags = { package = "feature_flags2", path = "../feature_flags2"}
theme = { package = "theme2", path = "../theme2" }
theme_selector = { package = "theme_selector2", path = "../theme_selector2" }
vcs_menu = { package = "vcs_menu2", path = "../vcs_menu2" }
ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
workspace = { package = "workspace2", path = "../workspace2" }
zed-actions = { package="zed_actions2", path = "../zed_actions2"}
anyhow.workspace = true
futures.workspace = true
lazy_static.workspace = true
log.workspace = true
schemars.workspace = true
postage.workspace = true
serde.workspace = true
serde_derive.workspace = true
time.workspace = true
smallvec.workspace = true
[dev-dependencies]
call = { package = "call2", path = "../call2", features = ["test-support"] }
client = { package = "client2", path = "../client2", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] }
project = { package = "project2", path = "../project2", features = ["test-support"] }
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
pretty_assertions.workspace = true
tree-sitter-markdown.workspace = true

View File

@@ -0,0 +1,450 @@
use anyhow::Result;
use call::report_call_event_for_channel;
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore};
use client::{
proto::{self, PeerId},
Collaborator, ParticipantIndex,
};
use collections::HashMap;
use editor::{CollaborationHub, Editor, EditorEvent};
use gpui::{
actions, AnyElement, AnyView, AppContext, Entity as _, EventEmitter, FocusableView,
IntoElement as _, Model, Pixels, Point, Render, Subscription, Task, View, ViewContext,
VisualContext as _, WindowContext,
};
use project::Project;
use std::{
any::{Any, TypeId},
sync::Arc,
};
use ui::{prelude::*, Label};
use util::ResultExt;
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle},
register_followable_item,
searchable::SearchableItemHandle,
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
};
actions!(collab, [Deploy]);
pub fn init(cx: &mut AppContext) {
register_followable_item::<ChannelView>(cx)
}
pub struct ChannelView {
pub editor: View<Editor>,
project: Model<Project>,
channel_store: Model<ChannelStore>,
channel_buffer: Model<ChannelBuffer>,
remote_id: Option<ViewId>,
_editor_event_subscription: Subscription,
}
impl ChannelView {
pub fn open(
channel_id: ChannelId,
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, pane.clone(), workspace.clone(), cx);
cx.spawn(|mut cx| async move {
let channel_view = channel_view.await?;
pane.update(&mut cx, |pane, cx| {
report_call_event_for_channel(
"open channel notes",
channel_id,
&workspace.read(cx).app_state().client,
cx,
);
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
})?;
anyhow::Ok(channel_view)
})
}
pub fn open_in_pane(
channel_id: ChannelId,
pane: View<Pane>,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let workspace = workspace.read(cx);
let project = workspace.project().to_owned();
let channel_store = ChannelStore::global(cx);
let language_registry = workspace.app_state().languages.clone();
let markdown = language_registry.language_for_name("Markdown");
let channel_buffer =
channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
cx.spawn(|mut cx| async move {
let channel_buffer = channel_buffer.await?;
let markdown = markdown.await.log_err();
channel_buffer.update(&mut cx, |buffer, cx| {
buffer.buffer().update(cx, |buffer, cx| {
buffer.set_language_registry(language_registry);
if let Some(markdown) = markdown {
buffer.set_language(Some(markdown), cx);
}
})
})?;
pane.update(&mut cx, |pane, cx| {
let buffer_id = channel_buffer.read(cx).remote_id(cx);
let existing_view = pane
.items_of_type::<Self>()
.find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
// If this channel buffer is already open in this pane, just return it.
if let Some(existing_view) = existing_view.clone() {
if existing_view.read(cx).channel_buffer == channel_buffer {
return existing_view;
}
}
let view = cx.build_view(|cx| {
let mut this = Self::new(project, channel_store, channel_buffer, cx);
this.acknowledge_buffer_version(cx);
this
});
// If the pane contained a disconnected view for this channel buffer,
// 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, cx)
.detach();
pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
}
}
view
})
})
}
pub fn new(
project: Model<Project>,
channel_store: Model<ChannelStore>,
channel_buffer: Model<ChannelBuffer>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = channel_buffer.read(cx).buffer();
let editor = cx.build_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx);
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
channel_buffer.clone(),
)));
editor.set_read_only(
!channel_buffer
.read(cx)
.channel(cx)
.is_some_and(|c| c.can_edit_notes()),
);
editor
});
let _editor_event_subscription =
cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
.detach();
Self {
editor,
project,
channel_store,
channel_buffer,
remote_id: None,
_editor_event_subscription,
}
}
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
self.channel_buffer.read(cx).channel(cx)
}
fn handle_channel_buffer_event(
&mut self,
_: Model<ChannelBuffer>,
event: &ChannelBufferEvent,
cx: &mut ViewContext<Self>,
) {
match event {
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
editor.set_read_only(true);
cx.notify();
}),
ChannelBufferEvent::ChannelChanged => {
self.editor.update(cx, |editor, cx| {
editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes()));
cx.emit(editor::EditorEvent::TitleChanged);
cx.notify()
});
}
ChannelBufferEvent::BufferEdited => {
if self.editor.read(cx).is_focused(cx) {
self.acknowledge_buffer_version(cx);
} else {
self.channel_store.update(cx, |store, cx| {
let channel_buffer = self.channel_buffer.read(cx);
store.notes_changed(
channel_buffer.channel_id,
channel_buffer.epoch(),
&channel_buffer.buffer().read(cx).version(),
cx,
)
});
}
}
ChannelBufferEvent::CollaboratorsChanged => {}
}
}
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(
channel_buffer.channel_id,
channel_buffer.epoch(),
&channel_buffer.buffer().read(cx).version(),
cx,
)
});
self.channel_buffer.update(cx, |buffer, cx| {
buffer.acknowledge_buffer_version(cx);
});
}
}
impl EventEmitter<EditorEvent> for ChannelView {}
impl Render for ChannelView {
type Element = AnyView;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
self.editor.clone().into()
}
}
impl FocusableView for ChannelView {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.read(cx).focus_handle(cx)
}
}
impl Item for ChannelView {
type Event = EditorEvent;
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a View<Self>,
_: &'a AppContext,
) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
Some(self_handle.to_any())
} else if type_id == TypeId::of::<Editor>() {
Some(self.editor.to_any())
} else {
None
}
}
fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
let label = if let Some(channel) = self.channel(cx) {
match (
channel.can_edit_notes(),
self.channel_buffer.read(cx).is_connected(),
) {
(true, true) => format!("#{}", channel.name),
(false, true) => format!("#{} (read-only)", channel.name),
(_, false) => format!("#{} (disconnected)", channel.name),
}
} else {
format!("channel notes (disconnected)")
};
Label::new(label)
.color(if selected {
Color::Default
} else {
Color::Muted
})
.into_any_element()
}
fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<View<Self>> {
Some(cx.build_view(|cx| {
Self::new(
self.project.clone(),
self.channel_store.clone(),
self.channel_buffer.clone(),
cx,
)
}))
}
fn is_singleton(&self, _cx: &AppContext) -> bool {
false
}
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, cx))
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| Item::deactivated(editor, cx))
}
fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
}
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
fn show_toolbar(&self) -> bool {
true
}
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
self.editor.read(cx).pixel_position_of_cursor(cx)
}
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
Editor::to_item_events(event, f)
}
}
impl FollowableItem for ChannelView {
fn remote_id(&self) -> Option<workspace::ViewId> {
self.remote_id
}
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;
}
Some(proto::view::Variant::ChannelView(
proto::view::ChannelView {
channel_id: channel_buffer.channel_id,
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx)
{
Some(proto)
} else {
None
},
},
))
}
fn from_state_proto(
pane: View<workspace::Pane>,
workspace: View<workspace::Workspace>,
remote_id: workspace::ViewId,
state: &mut Option<proto::view::Variant>,
cx: &mut WindowContext,
) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
let Some(proto::view::Variant::ChannelView(_)) = state else {
return None;
};
let Some(proto::view::Variant::ChannelView(state)) = state.take() else {
unreachable!()
};
let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
Some(cx.spawn(|mut cx| async move {
let this = open.await?;
let task = this.update(&mut cx, |this, cx| {
this.remote_id = Some(remote_id);
if let Some(state) = state.editor {
Some(this.editor.update(cx, |editor, cx| {
editor.apply_update_proto(
&this.project,
proto::update_view::Variant::Editor(proto::update_view::Editor {
selections: state.selections,
pending_selection: state.pending_selection,
scroll_top_anchor: state.scroll_top_anchor,
scroll_x: state.scroll_x,
scroll_y: state.scroll_y,
..Default::default()
}),
cx,
)
}))
} else {
None
}
})?;
if let Some(task) = task {
task.await?;
}
Ok(this)
}))
}
fn add_event_to_update_proto(
&self,
event: &EditorEvent,
update: &mut Option<proto::update_view::Variant>,
cx: &WindowContext,
) -> bool {
self.editor
.read(cx)
.add_event_to_update_proto(event, update, cx)
}
fn apply_update_proto(
&mut self,
project: &Model<Project>,
message: proto::update_view::Variant,
cx: &mut ViewContext<Self>,
) -> gpui::Task<anyhow::Result<()>> {
self.editor.update(cx, |editor, cx| {
editor.apply_update_proto(project, message, cx)
})
}
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, cx)
})
}
fn is_project_item(&self, _cx: &WindowContext) -> bool {
false
}
fn to_follow_event(event: &Self::Event) -> Option<workspace::item::FollowEvent> {
Editor::to_follow_event(event)
}
}
struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);
impl CollaborationHub for ChannelBufferCollaborationHub {
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
self.0.read(cx).collaborators()
}
fn user_participant_indices<'a>(
&self,
cx: &'a AppContext,
) -> &'a HashMap<u64, ParticipantIndex> {
self.0.read(cx).user_store().read(cx).participant_indices()
}
}

View File

@@ -0,0 +1,686 @@
use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings};
use anyhow::Result;
use call::ActiveCall;
use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
use client::Client;
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use gpui::{
actions, div, list, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext,
ClickEvent, Div, ElementId, EventEmitter, FocusableView, ListOffset, ListScrollEvent,
ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView,
};
use language::LanguageRegistry;
use menu::Confirm;
use message_editor::MessageEditor;
use project::Fs;
use rich_text::RichText;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use theme::ActiveTheme as _;
use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, Tooltip};
use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
Workspace,
};
mod message_editor;
const MESSAGE_LOADING_THRESHOLD: usize = 50;
const CHAT_PANEL_KEY: &'static str = "ChatPanel";
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<ChatPanel>(cx);
});
})
.detach();
}
pub struct ChatPanel {
client: Arc<Client>,
channel_store: Model<ChannelStore>,
languages: Arc<LanguageRegistry>,
message_list: ListState,
active_chat: Option<(Model<ChannelChat>, Subscription)>,
input_editor: View<MessageEditor>,
local_timezone: UtcOffset,
fs: Arc<dyn Fs>,
width: Option<f32>,
active: bool,
pending_serialization: Task<Option<()>>,
subscriptions: Vec<gpui::Subscription>,
workspace: WeakView<Workspace>,
is_scrolled_to_bottom: bool,
markdown_data: HashMap<ChannelMessageId, RichText>,
}
#[derive(Serialize, Deserialize)]
struct SerializedChatPanel {
width: Option<f32>,
}
#[derive(Debug)]
pub enum Event {
DockPositionChanged,
Focus,
Dismissed,
}
actions!(chat_panel, [ToggleFocus]);
impl ChatPanel {
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 languages = workspace.app_state().languages.clone();
let input_editor = cx.build_view(|cx| {
MessageEditor::new(
languages.clone(),
channel_store.clone(),
cx.build_view(|cx| Editor::auto_height(4, cx)),
cx,
)
});
let workspace_handle = workspace.weak_handle();
cx.build_view(|cx| {
let view: View<ChatPanel> = cx.view().clone();
let message_list =
ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
view.update(cx, |view, cx| view.render_message(ix, 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);
}
this.is_scrolled_to_bottom = event.visible_range.end == event.count;
}));
let mut this = Self {
fs,
client,
channel_store,
languages,
message_list,
active_chat: Default::default(),
pending_serialization: Task::ready(None),
input_editor,
local_timezone: cx.local_timezone(),
subscriptions: Vec::new(),
workspace: workspace_handle,
is_scrolled_to_bottom: true,
active: false,
width: None,
markdown_data: Default::default(),
};
let mut old_dock_position = this.position(cx);
this.subscriptions.push(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 is_scrolled_to_bottom(&self) -> bool {
self.is_scrolled_to_bottom
}
pub fn active_chat(&self) -> Option<Model<ChannelChat>> {
self.active_chat.as_ref().map(|(chat, _)| chat.clone())
}
pub fn load(
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let serialized_panel = if let Some(panel) = cx
.background_executor()
.spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) })
.await
.log_err()
.flatten()
{
Some(serde_json::from_str::<SerializedChatPanel>(&panel)?)
} else {
None
};
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;
cx.notify();
});
}
panel
})
})
}
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
async move {
KEY_VALUE_STORE
.write_kvp(
CHAT_PANEL_KEY.into(),
serde_json::to_string(&SerializedChatPanel { width })?,
)
.await?;
anyhow::Ok(())
}
.log_err(),
);
}
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) {
let channel_id = chat.read(cx).channel_id;
{
self.markdown_data.clear();
let chat = chat.read(cx);
self.message_list.reset(chat.message_count());
let channel_name = chat.channel(cx).map(|channel| channel.name.clone());
self.input_editor.update(cx, |editor, cx| {
editor.set_channel(channel_id, channel_name, cx);
});
};
let subscription = cx.subscribe(&chat, Self::channel_did_change);
self.active_chat = Some((chat, subscription));
self.acknowledge_last_message(cx);
cx.notify();
}
}
fn channel_did_change(
&mut self,
_: Model<ChannelChat>,
event: &ChannelChatEvent,
cx: &mut ViewContext<Self>,
) {
match event {
ChannelChatEvent::MessagesUpdated {
old_range,
new_count,
} => {
self.message_list.splice(old_range.clone(), *new_count);
if self.active {
self.acknowledge_last_message(cx);
}
}
ChannelChatEvent::NewMessage {
channel_id,
message_id,
} => {
if !self.active {
self.channel_store.update(cx, |store, cx| {
store.new_message(*channel_id, *message_id, cx)
})
}
}
}
cx.notify();
}
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 {
chat.update(cx, |chat, cx| {
chat.acknowledge_last_message(cx);
});
}
}
}
fn render_channel(&self, cx: &mut ViewContext<Self>) -> AnyElement {
v_stack()
.full()
.on_action(cx.listener(Self::send))
.child(
h_stack()
.w_full()
.h_7()
.justify_between()
.z_index(1)
.bg(cx.theme().colors().background)
.child(Label::new(
self.active_chat
.as_ref()
.and_then(|c| Some(format!("#{}", c.0.read(cx).channel(cx)?.name)))
.unwrap_or_default(),
))
.child(
h_stack()
.child(
IconButton::new("notes", Icon::File)
.on_click(cx.listener(Self::open_notes))
.tooltip(|cx| Tooltip::text("Open notes", cx)),
)
.child(
IconButton::new("call", Icon::AudioOn)
.on_click(cx.listener(Self::join_call))
.tooltip(|cx| Tooltip::text("Join call", cx)),
),
),
)
.child(
div()
.flex_grow()
.child(self.render_active_channel_messages(cx)),
)
.child(
div()
.z_index(1)
.p_2()
.bg(cx.theme().colors().background)
.child(self.input_editor.clone()),
)
.into_any()
}
fn render_active_channel_messages(&self, _cx: &mut ViewContext<Self>) -> AnyElement {
if self.active_chat.is_some() {
list(self.message_list.clone()).full().into_any_element()
} else {
div().into_any_element()
}
}
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let active_chat = &self.active_chat.as_ref().unwrap().0;
let (message, is_continuation, is_admin) = active_chat.update(cx, |active_chat, cx| {
let is_admin = self
.channel_store
.read(cx)
.is_channel_admin(active_chat.channel_id);
let last_message = active_chat.message(ix.saturating_sub(1));
let this_message = active_chat.message(ix).clone();
let is_continuation = last_message.id != this_message.id
&& this_message.sender.id == last_message.sender.id;
if let ChannelMessageId::Saved(id) = this_message.id {
if this_message
.mentions
.iter()
.any(|(_, user_id)| Some(*user_id) == self.client.user_id())
{
active_chat.acknowledge_message(id);
}
}
(this_message, is_continuation, is_admin)
});
let _is_pending = message.is_pending();
let text = self.markdown_data.entry(message.id).or_insert_with(|| {
Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
});
let now = OffsetDateTime::now_utc();
let belongs_to_user = Some(message.sender.id) == self.client.user_id();
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
(message.id, belongs_to_user || is_admin)
{
Some(id)
} else {
None
};
let element_id: ElementId = match message.id {
ChannelMessageId::Saved(id) => ("saved-message", id).into(),
ChannelMessageId::Pending(id) => ("pending-message", id).into(),
};
let mut result = v_stack()
.w_full()
.id(element_id)
.relative()
.group("")
.mb_1();
if !is_continuation {
result = result.child(
h_stack()
.child(Avatar::new(message.sender.avatar_uri.clone()))
.child(Label::new(message.sender.github_login.clone()))
.child(Label::new(format_timestamp(
message.timestamp,
now,
self.local_timezone,
))),
);
}
result
.child(text.element("body".into(), cx))
.child(
div()
.absolute()
.top_1()
.right_2()
.w_8()
.visible_on_hover("")
.children(message_id_to_remove.map(|message_id| {
IconButton::new(("remove", message_id), Icon::XCircle).on_click(
cx.listener(move |this, _, cx| {
this.remove_message(message_id, cx);
}),
)
})),
)
.into_any()
}
fn render_markdown_with_mentions(
language_registry: &Arc<LanguageRegistry>,
current_user_id: u64,
message: &channel::ChannelMessage,
) -> RichText {
let mentions = message
.mentions
.iter()
.map(|(range, user_id)| rich_text::Mention {
range: range.clone(),
is_self_mention: *user_id == current_user_id,
})
.collect::<Vec<_>>();
rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
}
fn render_sign_in_prompt(&self, cx: &mut ViewContext<Self>) -> AnyElement {
Button::new("sign-in", "Sign in to use chat")
.on_click(cx.listener(move |this, _, cx| {
let client = this.client.clone();
cx.spawn(|this, mut cx| async move {
if client
.authenticate_and_connect(true, &cx)
.log_err()
.await
.is_some()
{
this.update(&mut cx, |_, cx| {
cx.focus_self();
})
.ok();
}
})
.detach();
}))
.into_any_element()
}
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if let Some((chat, _)) = self.active_chat.as_ref() {
let message = self
.input_editor
.update(cx, |editor, cx| editor.take_message(cx));
if let Some(task) = chat
.update(cx, |chat, cx| chat.send_message(message, cx))
.log_err()
{
task.detach();
}
}
}
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 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) {
task.detach();
}
})
}
}
pub fn select_channel(
&mut self,
selected_channel_id: u64,
scroll_to_message_id: Option<u64>,
cx: &mut ViewContext<ChatPanel>,
) -> Task<Result<()>> {
let open_chat = self
.active_chat
.as_ref()
.and_then(|(chat, _)| {
(chat.read(cx).channel_id == selected_channel_id)
.then(|| Task::ready(anyhow::Ok(chat.clone())))
})
.unwrap_or_else(|| {
self.channel_store.update(cx, |store, cx| {
store.open_channel_chat(selected_channel_id, cx)
})
});
cx.spawn(|this, mut cx| async move {
let chat = open_chat.await?;
this.update(&mut cx, |this, cx| {
this.set_active_chat(chat.clone(), cx);
})?;
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())
.await
{
this.update(&mut cx, |this, cx| {
if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
this.message_list.scroll_to(ListOffset {
item_ix,
offset_in_item: px(0.0),
});
cx.notify();
}
})?;
}
}
Ok(())
})
}
fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
if let Some((chat, _)) = &self.active_chat {
let channel_id = chat.read(cx).channel_id;
if let Some(workspace) = self.workspace.upgrade() {
ChannelView::open(channel_id, workspace, cx).detach();
}
}
}
fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
if let Some((chat, _)) = &self.active_chat {
let channel_id = chat.read(cx).channel_id;
ActiveCall::global(cx)
.update(cx, |call, cx| call.join_channel(channel_id, cx))
.detach_and_log_err(cx);
}
}
}
impl EventEmitter<Event> for ChatPanel {}
impl Render for ChatPanel {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.full()
.child(if self.client.user_id().is_some() {
self.render_channel(cx)
} else {
self.render_sign_in_prompt(cx)
})
.min_w(px(150.))
}
}
impl FocusableView for ChatPanel {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.input_editor.read(cx).focus_handle(cx)
}
}
impl Panel for ChatPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
ChatPanelSettings::get_global(cx).dock
}
fn position_is_valid(&self, position: DockPosition) -> bool {
matches!(position, DockPosition::Left | DockPosition::Right)
}
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
settings::update_settings_file::<ChatPanelSettings>(self.fs.clone(), cx, move |settings| {
settings.dock = Some(position)
});
}
fn size(&self, cx: &gpui::WindowContext) -> f32 {
self.width
.unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
}
fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
self.active = active;
if active {
self.acknowledge_last_message(cx);
if !is_channels_feature_enabled(cx) {
cx.emit(Event::Dismissed);
}
}
}
fn persistent_name() -> &'static str {
"ChatPanel"
}
fn icon(&self, _cx: &WindowContext) -> Option<ui::Icon> {
Some(ui::Icon::MessageBubbles)
}
fn toggle_action(&self) -> Box<dyn gpui::Action> {
Box::new(ToggleFocus)
}
}
impl EventEmitter<PanelEvent> for ChatPanel {}
fn format_timestamp(
mut timestamp: OffsetDateTime,
mut now: OffsetDateTime,
local_timezone: UtcOffset,
) -> String {
timestamp = timestamp.to_offset(local_timezone);
now = now.to_offset(local_timezone);
let today = now.date();
let date = timestamp.date();
let mut hour = timestamp.hour();
let mut part = "am";
if hour > 12 {
hour -= 12;
part = "pm";
}
if date == today {
format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
} else if date.next_day() == Some(today) {
format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
} else {
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::HighlightStyle;
use pretty_assertions::assert_eq;
use rich_text::Highlight;
use util::test::marked_text_ranges;
#[gpui::test]
fn test_render_markdown_with_mentions() {
let language_registry = Arc::new(LanguageRegistry::test());
let (body, ranges) = marked_text_ranges("*hi*, «@abc», let's **call** «@fgh»", false);
let message = channel::ChannelMessage {
id: ChannelMessageId::Saved(0),
body,
timestamp: OffsetDateTime::now_utc(),
sender: Arc::new(client::User {
github_login: "fgh".into(),
avatar_uri: "avatar_fgh".into(),
id: 103,
}),
nonce: 5,
mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
};
let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message);
// Note that the "'" was replaced with due to smart punctuation.
let (body, ranges) = marked_text_ranges("«hi», «@abc», lets «call» «@fgh»", false);
assert_eq!(message.text, body);
assert_eq!(
message.highlights,
vec![
(
ranges[0].clone(),
HighlightStyle {
font_style: Some(gpui::FontStyle::Italic),
..Default::default()
}
.into()
),
(ranges[1].clone(), Highlight::Mention),
(
ranges[2].clone(),
HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
..Default::default()
}
.into()
),
(ranges[3].clone(), Highlight::SelfMention)
]
);
}
}

View File

@@ -0,0 +1,298 @@
use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
use client::UserId;
use collections::HashMap;
use editor::{AnchorRangeExt, Editor};
use gpui::{
AnyView, AsyncWindowContext, FocusableView, Model, Render, SharedString, Task, View,
ViewContext, WeakView,
};
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
use lazy_static::lazy_static;
use project::search::SearchQuery;
use std::{sync::Arc, time::Duration};
use workspace::item::ItemHandle;
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
lazy_static! {
static ref MENTIONS_SEARCH: SearchQuery =
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
}
pub struct MessageEditor {
pub editor: View<Editor>,
channel_store: Model<ChannelStore>,
users: HashMap<String, UserId>,
mentions: Vec<UserId>,
mentions_task: Option<Task<()>>,
channel_id: Option<ChannelId>,
}
impl MessageEditor {
pub fn new(
language_registry: Arc<LanguageRegistry>,
channel_store: Model<ChannelStore>,
editor: View<Editor>,
cx: &mut ViewContext<Self>,
) -> Self {
editor.update(cx, |editor, cx| {
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
});
let buffer = editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.expect("message editor must be singleton");
cx.subscribe(&buffer, Self::on_buffer_event).detach();
let markdown = language_registry.language_for_name("Markdown");
cx.spawn(|_, mut cx| async move {
let markdown = markdown.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(Some(markdown), cx)
})
})
.detach_and_log_err(cx);
Self {
editor,
channel_store,
users: HashMap::default(),
channel_id: None,
mentions: Vec::new(),
mentions_task: None,
}
}
pub fn set_channel(
&mut self,
channel_id: u64,
channel_name: Option<SharedString>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
if let Some(channel_name) = channel_name {
editor.set_placeholder_text(format!("Message #{}", channel_name), cx);
} else {
editor.set_placeholder_text(format!("Message Channel"), cx);
}
});
self.channel_id = Some(channel_id);
self.refresh_users(cx);
}
pub fn refresh_users(&mut self, cx: &mut ViewContext<Self>) {
if let Some(channel_id) = self.channel_id {
let members = self.channel_store.update(cx, |store, cx| {
store.get_channel_member_details(channel_id, cx)
});
cx.spawn(|this, mut cx| async move {
let members = members.await?;
this.update(&mut cx, |this, cx| this.set_members(members, cx))?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
pub fn set_members(&mut self, members: Vec<ChannelMembership>, _: &mut ViewContext<Self>) {
self.users.clear();
self.users.extend(
members
.into_iter()
.map(|member| (member.user.github_login.clone(), member.user.id)),
);
}
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);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let mentions = if let Some((_, ranges)) = highlights {
ranges
.iter()
.map(|range| range.to_offset(&snapshot))
.zip(self.mentions.iter().copied())
.collect()
} else {
Vec::new()
};
editor.clear(cx);
self.mentions.clear();
MessageParams { text, mentions }
})
}
fn on_buffer_event(
&mut self,
buffer: Model<Buffer>,
event: &language::Event,
cx: &mut ViewContext<Self>,
) {
if let language::Event::Reparsed | language::Event::Edited = event {
let buffer = buffer.read(cx).snapshot();
self.mentions_task = Some(cx.spawn(|this, cx| async move {
cx.background_executor()
.timer(MENTIONS_DEBOUNCE_INTERVAL)
.await;
Self::find_mentions(this, buffer, cx).await;
}));
}
}
async fn find_mentions(
this: WeakView<MessageEditor>,
buffer: BufferSnapshot,
mut cx: AsyncWindowContext,
) {
let (buffer, ranges) = cx
.background_executor()
.spawn(async move {
let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
(buffer, ranges)
})
.await;
this.update(&mut cx, |this, cx| {
let mut anchor_ranges = Vec::new();
let mut mentioned_user_ids = Vec::new();
let mut text = String::new();
this.editor.update(cx, |editor, cx| {
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
for range in ranges {
text.clear();
text.extend(buffer.text_for_range(range.clone()));
if let Some(username) = text.strip_prefix("@") {
if let Some(user_id) = this.users.get(username) {
let start = multi_buffer.anchor_after(range.start);
let end = multi_buffer.anchor_after(range.end);
mentioned_user_ids.push(*user_id);
anchor_ranges.push(start..end);
}
}
}
editor.clear_highlights::<Self>(cx);
editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
});
this.mentions = mentioned_user_ids;
this.mentions_task.take();
})
.ok();
}
pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
self.editor.read(cx).focus_handle(cx)
}
}
impl Render for MessageEditor {
type Element = AnyView;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
self.editor.to_any()
}
}
#[cfg(test)]
mod tests {
use super::*;
use client::{Client, User, UserStore};
use gpui::{Context as _, TestAppContext, VisualContext as _};
use language::{Language, LanguageConfig};
use rpc::proto;
use settings::SettingsStore;
use util::{http::FakeHttpClient, test::marked_text_ranges};
#[gpui::test]
async fn test_message_editor(cx: &mut TestAppContext) {
let language_registry = init_test(cx);
let (editor, cx) = cx.add_window_view(|cx| {
MessageEditor::new(
language_registry,
ChannelStore::global(cx),
cx.build_view(|cx| Editor::auto_height(4, cx)),
cx,
)
});
cx.executor().run_until_parked();
editor.update(cx, |editor, cx| {
editor.set_members(
vec![
ChannelMembership {
user: Arc::new(User {
github_login: "a-b".into(),
id: 101,
avatar_uri: "avatar_a-b".into(),
}),
kind: proto::channel_member::Kind::Member,
role: proto::ChannelRole::Member,
},
ChannelMembership {
user: Arc::new(User {
github_login: "C_D".into(),
id: 102,
avatar_uri: "avatar_C_D".into(),
}),
kind: proto::channel_member::Kind::Member,
role: proto::ChannelRole::Member,
},
],
cx,
);
editor.editor.update(cx, |editor, cx| {
editor.set_text("Hello, @a-b! Have you met @C_D?", cx)
});
});
cx.executor().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
editor.update(cx, |editor, cx| {
let (text, ranges) = marked_text_ranges("Hello, «@a-b»! Have you met «@C_D»?", false);
assert_eq!(
editor.take_message(cx),
MessageParams {
text,
mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
}
);
});
}
fn init_test(cx: &mut TestAppContext) -> Arc<LanguageRegistry> {
cx.update(|cx| {
let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), cx));
let settings = SettingsStore::test(cx);
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx);
editor::init(cx);
client::init(&client, cx);
channel::init(&client, user_store, cx);
});
let language_registry = Arc::new(LanguageRegistry::test());
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Markdown".into(),
..Default::default()
},
Some(tree_sitter_markdown::language()),
)));
language_registry
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,712 @@
use channel::{ChannelId, ChannelMembership, ChannelStore};
use client::{
proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore,
};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, div, AppContext, ClipboardItem, DismissEvent, Div, Entity, EventEmitter,
FocusableView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext,
WeakView,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::prelude::*;
use util::TryFutureExt;
use workspace::ModalView;
actions!(
channel_modal,
[
SelectNextControl,
ToggleMode,
ToggleMemberAdmin,
RemoveMember
]
);
// pub fn init(cx: &mut AppContext) {
// Picker::<ChannelModalDelegate>::init(cx);
// cx.add_action(ChannelModal::toggle_mode);
// cx.add_action(ChannelModal::toggle_member_admin);
// cx.add_action(ChannelModal::remove_member);
// cx.add_action(ChannelModal::dismiss);
// }
pub struct ChannelModal {
picker: View<Picker<ChannelModalDelegate>>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
has_focus: bool,
}
impl ChannelModal {
pub fn new(
user_store: Model<UserStore>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
mode: Mode,
members: Vec<ChannelMembership>,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
let channel_modal = cx.view().downgrade();
let picker = cx.build_view(|cx| {
Picker::new(
ChannelModalDelegate {
channel_modal,
matching_users: Vec::new(),
matching_member_indices: Vec::new(),
selected_index: 0,
user_store: user_store.clone(),
channel_store: channel_store.clone(),
channel_id,
match_candidates: Vec::new(),
members,
mode,
// context_menu: cx.add_view(|cx| {
// let mut menu = ContextMenu::new(cx.view_id(), cx);
// menu.set_position_mode(OverlayPositionMode::Local);
// menu
// }),
},
cx,
)
});
let has_focus = picker.focus_handle(cx).contains_focused(cx);
Self {
picker,
channel_store,
channel_id,
has_focus,
}
}
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, cx);
}
fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
let channel_store = self.channel_store.clone();
let channel_id = self.channel_id;
cx.spawn(|this, mut cx| async move {
if mode == Mode::ManageMembers {
let mut members = channel_store
.update(&mut cx, |channel_store, cx| {
channel_store.get_channel_member_details(channel_id, cx)
})?
.await?;
members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
this.update(&mut cx, |this, cx| {
this.picker
.update(cx, |picker, _| picker.delegate.members = members);
})?;
}
this.update(&mut cx, |this, cx| {
this.picker.update(cx, |picker, cx| {
let delegate = &mut picker.delegate;
delegate.mode = mode;
delegate.selected_index = 0;
picker.set_query("", cx);
picker.update_matches(picker.query(cx), cx);
cx.notify()
});
cx.notify()
})
})
.detach();
}
fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.delegate.toggle_selected_member_admin(cx);
})
}
fn remove_member(&mut self, _: &RemoveMember, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.delegate.remove_selected_member(cx);
});
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
}
}
impl EventEmitter<DismissEvent> for ChannelModal {}
impl ModalView for ChannelModal {}
impl FocusableView for ChannelModal {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ChannelModal {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w(rems(34.)).child(self.picker.clone())
// let theme = &theme::current(cx).collab_panel.tabbed_modal;
// let mode = self.picker.read(cx).delegate().mode;
// let Some(channel) = self.channel_store.read(cx).channel_for_id(self.channel_id) else {
// return Empty::new().into_any();
// };
// enum InviteMembers {}
// enum ManageMembers {}
// fn render_mode_button<T: 'static>(
// mode: Mode,
// text: &'static str,
// current_mode: Mode,
// theme: &theme::TabbedModal,
// cx: &mut ViewContext<ChannelModal>,
// ) -> AnyElement<ChannelModal> {
// let active = mode == current_mode;
// MouseEventHandler::new::<T, _>(0, cx, move |state, _| {
// let contained_text = theme.tab_button.style_for(active, state);
// Label::new(text, contained_text.text.clone())
// .contained()
// .with_style(contained_text.container.clone())
// })
// .on_click(MouseButton::Left, move |_, this, cx| {
// if !active {
// this.set_mode(mode, cx);
// }
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .into_any()
// }
// fn render_visibility(
// channel_id: ChannelId,
// visibility: ChannelVisibility,
// theme: &theme::TabbedModal,
// cx: &mut ViewContext<ChannelModal>,
// ) -> AnyElement<ChannelModal> {
// enum TogglePublic {}
// if visibility == ChannelVisibility::Members {
// return Flex::row()
// .with_child(
// MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
// let style = theme.visibility_toggle.style_for(state);
// Label::new(format!("{}", "Public access: OFF"), style.text.clone())
// .contained()
// .with_style(style.container.clone())
// })
// .on_click(MouseButton::Left, move |_, this, cx| {
// this.channel_store
// .update(cx, |channel_store, cx| {
// channel_store.set_channel_visibility(
// channel_id,
// ChannelVisibility::Public,
// cx,
// )
// })
// .detach_and_log_err(cx);
// })
// .with_cursor_style(CursorStyle::PointingHand),
// )
// .into_any();
// }
// Flex::row()
// .with_child(
// MouseEventHandler::new::<TogglePublic, _>(0, cx, move |state, _| {
// let style = theme.visibility_toggle.style_for(state);
// Label::new(format!("{}", "Public access: ON"), style.text.clone())
// .contained()
// .with_style(style.container.clone())
// })
// .on_click(MouseButton::Left, move |_, this, cx| {
// this.channel_store
// .update(cx, |channel_store, cx| {
// channel_store.set_channel_visibility(
// channel_id,
// ChannelVisibility::Members,
// cx,
// )
// })
// .detach_and_log_err(cx);
// })
// .with_cursor_style(CursorStyle::PointingHand),
// )
// .with_spacing(14.0)
// .with_child(
// MouseEventHandler::new::<TogglePublic, _>(1, cx, move |state, _| {
// let style = theme.channel_link.style_for(state);
// Label::new(format!("{}", "copy link"), style.text.clone())
// .contained()
// .with_style(style.container.clone())
// })
// .on_click(MouseButton::Left, move |_, this, cx| {
// if let Some(channel) =
// this.channel_store.read(cx).channel_for_id(channel_id)
// {
// let item = ClipboardItem::new(channel.link());
// cx.write_to_clipboard(item);
// }
// })
// .with_cursor_style(CursorStyle::PointingHand),
// )
// .into_any()
// }
// Flex::column()
// .with_child(
// Flex::column()
// .with_child(
// Label::new(format!("#{}", channel.name), theme.title.text.clone())
// .contained()
// .with_style(theme.title.container.clone()),
// )
// .with_child(render_visibility(channel.id, channel.visibility, theme, cx))
// .with_child(Flex::row().with_children([
// render_mode_button::<InviteMembers>(
// Mode::InviteMembers,
// "Invite members",
// mode,
// theme,
// cx,
// ),
// render_mode_button::<ManageMembers>(
// Mode::ManageMembers,
// "Manage members",
// mode,
// theme,
// cx,
// ),
// ]))
// .expanded()
// .contained()
// .with_style(theme.header),
// )
// .with_child(
// ChildView::new(&self.picker, cx)
// .contained()
// .with_style(theme.body),
// )
// .constrained()
// .with_max_height(theme.max_height)
// .with_max_width(theme.max_width)
// .contained()
// .with_style(theme.modal)
// .into_any()
}
// fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
// self.has_focus = true;
// if cx.is_self_focused() {
// cx.focus(&self.picker)
// }
// }
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
// self.has_focus = false;
// }
}
#[derive(Copy, Clone, PartialEq)]
pub enum Mode {
ManageMembers,
InviteMembers,
}
pub struct ChannelModalDelegate {
channel_modal: WeakView<ChannelModal>,
matching_users: Vec<Arc<User>>,
matching_member_indices: Vec<usize>,
user_store: Model<UserStore>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
selected_index: usize,
mode: Mode,
match_candidates: Vec<StringMatchCandidate>,
members: Vec<ChannelMembership>,
// context_menu: ViewHandle<ContextMenu>,
}
impl PickerDelegate for ChannelModalDelegate {
type ListItem = Div;
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
fn match_count(&self) -> usize {
match self.mode {
Mode::ManageMembers => self.matching_member_indices.len(),
Mode::InviteMembers => self.matching_users.len(),
}
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
match self.mode {
Mode::ManageMembers => {
self.match_candidates.clear();
self.match_candidates
.extend(self.members.iter().enumerate().map(|(id, member)| {
StringMatchCandidate {
id,
string: member.user.github_login.clone(),
char_bag: member.user.github_login.chars().collect(),
}
}));
let matches = cx.background_executor().block(match_strings(
&self.match_candidates,
&query,
true,
usize::MAX,
&Default::default(),
cx.background_executor().clone(),
));
cx.spawn(|picker, mut cx| async move {
picker
.update(&mut cx, |picker, cx| {
let delegate = &mut picker.delegate;
delegate.matching_member_indices.clear();
delegate
.matching_member_indices
.extend(matches.into_iter().map(|m| m.candidate_id));
cx.notify();
})
.ok();
})
}
Mode::InviteMembers => {
let search_users = self
.user_store
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
cx.spawn(|picker, mut cx| async move {
async {
let users = search_users.await?;
picker.update(&mut cx, |picker, cx| {
picker.delegate.matching_users = users;
cx.notify();
})?;
anyhow::Ok(())
}
.log_err()
.await;
})
}
}
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some((selected_user, role)) = self.user_at_index(self.selected_index) {
match self.mode {
Mode::ManageMembers => {
self.show_context_menu(role.unwrap_or(ChannelRole::Member), cx)
}
Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
Some(proto::channel_member::Kind::Invitee) => {
self.remove_selected_member(cx);
}
Some(proto::channel_member::Kind::AncestorMember) | None => {
self.invite_member(selected_user, cx)
}
Some(proto::channel_member::Kind::Member) => {}
},
}
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.channel_modal
.update(cx, |_, cx| {
cx.emit(DismissEvent);
})
.ok();
}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
None
// let full_theme = &theme::current(cx);
// let theme = &full_theme.collab_panel.channel_modal;
// let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
// let (user, role) = self.user_at_index(ix).unwrap();
// let request_status = self.member_status(user.id, cx);
// let style = tabbed_modal
// .picker
// .item
// .in_state(selected)
// .style_for(mouse_state);
// let in_manage = matches!(self.mode, Mode::ManageMembers);
// let mut result = Flex::row()
// .with_children(user.avatar.clone().map(|avatar| {
// Image::from_data(avatar)
// .with_style(theme.contact_avatar)
// .aligned()
// .left()
// }))
// .with_child(
// Label::new(user.github_login.clone(), style.label.clone())
// .contained()
// .with_style(theme.contact_username)
// .aligned()
// .left(),
// )
// .with_children({
// (in_manage && request_status == Some(proto::channel_member::Kind::Invitee)).then(
// || {
// Label::new("Invited", theme.member_tag.text.clone())
// .contained()
// .with_style(theme.member_tag.container)
// .aligned()
// .left()
// },
// )
// })
// .with_children(if in_manage && role == Some(ChannelRole::Admin) {
// Some(
// Label::new("Admin", theme.member_tag.text.clone())
// .contained()
// .with_style(theme.member_tag.container)
// .aligned()
// .left(),
// )
// } else if in_manage && role == Some(ChannelRole::Guest) {
// Some(
// Label::new("Guest", theme.member_tag.text.clone())
// .contained()
// .with_style(theme.member_tag.container)
// .aligned()
// .left(),
// )
// } else {
// None
// })
// .with_children({
// let svg = match self.mode {
// Mode::ManageMembers => Some(
// Svg::new("icons/ellipsis.svg")
// .with_color(theme.member_icon.color)
// .constrained()
// .with_width(theme.member_icon.icon_width)
// .aligned()
// .constrained()
// .with_width(theme.member_icon.button_width)
// .with_height(theme.member_icon.button_width)
// .contained()
// .with_style(theme.member_icon.container),
// ),
// Mode::InviteMembers => match request_status {
// Some(proto::channel_member::Kind::Member) => Some(
// Svg::new("icons/check.svg")
// .with_color(theme.member_icon.color)
// .constrained()
// .with_width(theme.member_icon.icon_width)
// .aligned()
// .constrained()
// .with_width(theme.member_icon.button_width)
// .with_height(theme.member_icon.button_width)
// .contained()
// .with_style(theme.member_icon.container),
// ),
// Some(proto::channel_member::Kind::Invitee) => Some(
// Svg::new("icons/check.svg")
// .with_color(theme.invitee_icon.color)
// .constrained()
// .with_width(theme.invitee_icon.icon_width)
// .aligned()
// .constrained()
// .with_width(theme.invitee_icon.button_width)
// .with_height(theme.invitee_icon.button_width)
// .contained()
// .with_style(theme.invitee_icon.container),
// ),
// Some(proto::channel_member::Kind::AncestorMember) | None => None,
// },
// };
// svg.map(|svg| svg.aligned().flex_float().into_any())
// })
// .contained()
// .with_style(style.container)
// .constrained()
// .with_height(tabbed_modal.row_height)
// .into_any();
// if selected {
// result = Stack::new()
// .with_child(result)
// .with_child(
// ChildView::new(&self.context_menu, cx)
// .aligned()
// .top()
// .right(),
// )
// .into_any();
// }
// result
}
}
impl ChannelModalDelegate {
fn member_status(
&self,
user_id: UserId,
cx: &AppContext,
) -> Option<proto::channel_member::Kind> {
self.members
.iter()
.find_map(|membership| (membership.user.id == user_id).then_some(membership.kind))
.or_else(|| {
self.channel_store
.read(cx)
.has_pending_channel_invite(self.channel_id, user_id)
.then_some(proto::channel_member::Kind::Invitee)
})
}
fn user_at_index(&self, ix: usize) -> Option<(Arc<User>, Option<ChannelRole>)> {
match self.mode {
Mode::ManageMembers => self.matching_member_indices.get(ix).and_then(|ix| {
let channel_membership = self.members.get(*ix)?;
Some((
channel_membership.user.clone(),
Some(channel_membership.role),
))
}),
Mode::InviteMembers => Some((self.matching_users.get(ix).cloned()?, None)),
}
}
fn toggle_selected_member_admin(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
let (user, role) = self.user_at_index(self.selected_index)?;
let new_role = if role == Some(ChannelRole::Admin) {
ChannelRole::Member
} else {
ChannelRole::Admin
};
let update = self.channel_store.update(cx, |store, cx| {
store.set_member_role(self.channel_id, user.id, new_role, cx)
});
cx.spawn(|picker, mut cx| async move {
update.await?;
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();
cx.notify();
})
})
.detach_and_log_err(cx);
Some(())
}
fn remove_selected_member(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
let (user, _) = self.user_at_index(self.selected_index)?;
let user_id = user.id;
let update = self.channel_store.update(cx, |store, cx| {
store.remove_member(self.channel_id, user_id, cx)
});
cx.spawn(|picker, mut cx| async move {
update.await?;
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);
this.matching_member_indices.retain_mut(|member_ix| {
if *member_ix == ix {
return false;
} else if *member_ix > ix {
*member_ix -= 1;
}
true
})
}
this.selected_index = this
.selected_index
.min(this.matching_member_indices.len().saturating_sub(1));
cx.focus_self();
cx.notify();
})
})
.detach_and_log_err(cx);
Some(())
}
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(|this, mut cx| async move {
invite_member.await?;
this.update(&mut cx, |this, cx| {
let new_member = ChannelMembership {
user,
kind: proto::channel_member::Kind::Invitee,
role: ChannelRole::Member,
};
let members = &mut this.delegate.members;
match members.binary_search_by_key(&new_member.sort_key(), |k| k.sort_key()) {
Ok(ix) | Err(ix) => members.insert(ix, new_member),
}
cx.notify();
})
})
.detach_and_log_err(cx);
}
fn show_context_menu(&mut self, role: ChannelRole, cx: &mut ViewContext<Picker<Self>>) {
// self.context_menu.update(cx, |context_menu, cx| {
// context_menu.show(
// Default::default(),
// AnchorCorner::TopRight,
// vec![
// ContextMenuItem::action("Remove", RemoveMember),
// ContextMenuItem::action(
// if role == ChannelRole::Admin {
// "Make non-admin"
// } else {
// "Make admin"
// },
// ToggleMemberAdmin,
// ),
// ],
// cx,
// )
// })
}
}

View File

@@ -0,0 +1,195 @@
use client::{ContactRequestStatus, User, UserStore};
use gpui::{
div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use theme::ActiveTheme as _;
use ui::{prelude::*, Avatar};
use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView;
pub struct ContactFinder {
picker: View<Picker<ContactFinderDelegate>>,
}
impl ContactFinder {
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let delegate = ContactFinderDelegate {
parent: cx.view().downgrade(),
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
};
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
Self { picker }
}
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.set_query(query, cx);
});
}
}
impl Render for ContactFinder {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
fn render_mode_button(text: &'static str) -> AnyElement {
Label::new(text).into_any_element()
}
v_stack()
.child(
v_stack()
.child(Label::new("Contacts"))
.child(h_stack().children([render_mode_button("Invite new contacts")]))
.bg(cx.theme().colors().element_background),
)
.child(self.picker.clone())
.w(rems(34.))
}
type Element = Div;
}
pub struct ContactFinderDelegate {
parent: WeakView<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: Model<UserStore>,
selected_index: usize,
}
impl EventEmitter<DismissEvent> for ContactFinder {}
impl ModalView for ContactFinder {}
impl FocusableView for ContactFinder {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl PickerDelegate for ContactFinderDelegate {
type ListItem = Div;
fn match_count(&self) -> usize {
self.potential_contacts.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
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(|picker, mut cx| async move {
async {
let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| {
picker.delegate.potential_contacts = potential_contacts.into();
cx.notify();
})?;
anyhow::Ok(())
}
.log_err()
.await;
})
}
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) {
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
self.user_store
.update(cx, |store, cx| store.request_contact(user.id, cx))
.detach();
}
ContactRequestStatus::RequestSent => {
self.user_store
.update(cx, |store, cx| store.remove_contact(user.id, cx))
.detach();
}
_ => {}
}
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.parent
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
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);
let icon_path = match request_status {
ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
Some("icons/check.svg")
}
ContactRequestStatus::RequestSent => Some("icons/x.svg"),
ContactRequestStatus::RequestAccepted => None,
};
Some(
div()
.flex_1()
.justify_between()
.child(Avatar::new(user.avatar_uri.clone()))
.child(Label::new(user.github_login.clone()))
.children(icon_path.map(|icon_path| svg().path(icon_path))),
)
// todo!()
// Flex::row()
// .with_children(user.avatar.clone().map(|avatar| {
// Image::from_data(avatar)
// .with_style(theme.contact_avatar)
// .aligned()
// .left()
// }))
// .with_child(
// Label::new(user.github_login.clone(), style.label.clone())
// .contained()
// .with_style(theme.contact_username)
// .aligned()
// .left(),
// )
// .with_children(icon_path.map(|icon_path| {
// Svg::new(icon_path)
// .with_color(button_style.color)
// .constrained()
// .with_width(button_style.icon_width)
// .aligned()
// .contained()
// .with_style(button_style.container)
// .constrained()
// .with_width(button_style.button_width)
// .with_height(button_style.button_width)
// .aligned()
// .flex_float()
// }))
// .contained()
// .with_style(style.container)
// .constrained()
// .with_height(tabbed_modal.row_height)
// .into_any()
}
}

View File

@@ -0,0 +1,636 @@
use crate::face_pile::FacePile;
use auto_update::AutoUpdateStatus;
use call::{ActiveCall, ParticipantLocation, Room};
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
use gpui::{
actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent,
Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path,
Render, Stateful, StatefulInteractiveElement, Styled, Subscription, View, ViewContext,
VisualContext, WeakView, WindowBounds,
};
use project::{Project, RepositoryEntry};
use recent_projects::RecentProjects;
use std::sync::Arc;
use theme::{ActiveTheme, PlayerColors};
use ui::{
h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
IconButton, IconElement, Tooltip,
};
use util::ResultExt;
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB};
const MAX_PROJECT_NAME_LENGTH: usize = 40;
const MAX_BRANCH_NAME_LENGTH: usize = 40;
actions!(
collab,
[
ShareProject,
UnshareProject,
ToggleUserMenu,
ToggleProjectMenu,
SwitchBranch
]
);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, cx| {
let titlebar_item = cx.build_view(|cx| CollabTitlebarItem::new(workspace, cx));
workspace.set_titlebar_item(titlebar_item.into(), cx)
})
.detach();
// cx.add_action(CollabTitlebarItem::share_project);
// cx.add_action(CollabTitlebarItem::unshare_project);
// cx.add_action(CollabTitlebarItem::toggle_user_menu);
// cx.add_action(CollabTitlebarItem::toggle_vcs_menu);
// cx.add_action(CollabTitlebarItem::toggle_project_menu);
}
pub struct CollabTitlebarItem {
project: Model<Project>,
user_store: Model<UserStore>,
client: Arc<Client>,
workspace: WeakView<Workspace>,
branch_popover: Option<View<BranchList>>,
project_popover: Option<recent_projects::RecentProjects>,
_subscriptions: Vec<Subscription>,
}
impl Render for CollabTitlebarItem {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let room = ActiveCall::global(cx).read(cx).room().cloned();
let current_user = self.user_store.read(cx).current_user();
let client = self.client.clone();
let project_id = self.project.read(cx).remote_id();
h_stack()
.id("titlebar")
.justify_between()
.w_full()
.h(rems(1.75))
// Set a non-scaling min-height here to ensure the titlebar is
// always at least the height of the traffic lights.
.min_h(px(32.))
.pl_2()
.map(|this| {
if matches!(cx.window_bounds(), WindowBounds::Fullscreen) {
this.pl_2()
} else {
// Use pixels here instead of a rem-based size because the macOS traffic
// lights are a static size, and don't scale with the rest of the UI.
this.pl(px(72.))
}
})
.bg(cx.theme().colors().title_bar_background)
.on_click(|event, cx| {
if event.up.click_count == 2 {
cx.zoom_window();
}
})
// left side
.child(
h_stack()
.gap_1()
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
.children(self.render_project_branch(cx))
.when_some(
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|this, ((current_user, peer_id), room)| {
let player_colors = cx.theme().players();
let room = room.read(cx);
let mut remote_participants =
room.remote_participants().values().collect::<Vec<_>>();
remote_participants.sort_by_key(|p| p.participant_index.0);
this.children(self.render_collaborator(
&current_user,
peer_id,
true,
room.is_speaking(),
room.is_muted(cx),
&room,
project_id,
&current_user,
))
.children(
remote_participants.iter().filter_map(|collaborator| {
let is_present = project_id.map_or(false, |project_id| {
collaborator.location
== ParticipantLocation::SharedProject { project_id }
});
let face_pile = self.render_collaborator(
&collaborator.user,
collaborator.peer_id,
is_present,
collaborator.speaking,
collaborator.muted,
&room,
project_id,
&current_user,
)?;
Some(
v_stack()
.id(("collaborator", collaborator.user.id))
.child(face_pile)
.child(render_color_ribbon(
collaborator.participant_index,
player_colors,
))
.cursor_pointer()
.on_click({
let peer_id = collaborator.peer_id;
cx.listener(move |this, _, cx| {
this.workspace
.update(cx, |workspace, cx| {
workspace.follow(peer_id, cx);
})
.ok();
})
})
.tooltip({
let login = collaborator.user.github_login.clone();
move |cx| {
Tooltip::text(format!("Follow {login}"), cx)
}
}),
)
}),
)
},
),
)
// right side
.child(
h_stack()
.gap_1()
.pr_1()
.when_some(room, |this, room| {
let room = room.read(cx);
let is_shared = self.project.read(cx).is_shared();
let is_muted = room.is_muted(cx);
let is_deafened = room.is_deafened().unwrap_or(false);
this.child(
Button::new(
"toggle_sharing",
if is_shared { "Unshare" } else { "Share" },
)
.style(ButtonStyle::Subtle)
.on_click(cx.listener(
move |this, _, cx| {
if is_shared {
this.unshare_project(&Default::default(), cx);
} else {
this.share_project(&Default::default(), cx);
}
},
)),
)
.child(
IconButton::new("leave-call", ui::Icon::Exit)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
ActiveCall::global(cx)
.update(cx, |call, cx| call.hang_up(cx))
.detach_and_log_err(cx);
}),
)
.child(
IconButton::new(
"mute-microphone",
if is_muted {
ui::Icon::MicMute
} else {
ui::Icon::Mic
},
)
.style(ButtonStyle::Subtle)
.selected(is_muted)
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
)
.child(
IconButton::new(
"mute-sound",
if is_deafened {
ui::Icon::AudioOff
} else {
ui::Icon::AudioOn
},
)
.style(ButtonStyle::Subtle)
.selected(is_deafened.clone())
.tooltip(move |cx| {
Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
})
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
)
.child(
IconButton::new("screen-share", ui::Icon::Screen)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
crate::toggle_screen_sharing(&Default::default(), cx)
}),
)
})
.map(|el| {
let status = self.client.status();
let status = &*status.borrow();
if matches!(status, client::Status::Connected { .. }) {
el.child(self.render_user_menu_button(cx))
} else {
el.children(self.render_connection_status(status, cx))
.child(self.render_sign_in_button(cx))
.child(self.render_user_menu_button(cx))
}
}),
)
}
}
fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas {
let color = colors.color_for_participant(participant_index.0).cursor;
canvas(move |bounds, cx| {
let mut path = Path::new(bounds.lower_left());
let height = bounds.size.height;
path.curve_to(bounds.origin + point(height, px(0.)), bounds.origin);
path.line_to(bounds.upper_right() - point(height, px(0.)));
path.curve_to(bounds.lower_right(), bounds.upper_right());
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
})
.h_1()
.w_full()
}
impl CollabTitlebarItem {
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let project = workspace.project().clone();
let user_store = workspace.app_state().user_store.clone();
let client = workspace.app_state().client.clone();
let active_call = ActiveCall::global(cx);
let mut subscriptions = Vec::new();
subscriptions.push(
cx.observe(&workspace.weak_handle().upgrade().unwrap(), |_, _, cx| {
cx.notify()
}),
);
subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
subscriptions.push(cx.observe_window_activation(Self::window_activation_changed));
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
Self {
workspace: workspace.weak_handle(),
project,
user_store,
client,
branch_popover: None,
project_popover: None,
_subscriptions: subscriptions,
}
}
// resolve if you are in a room -> render_project_owner
// render_project_owner -> resolve if you are in a room -> Option<foo>
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
let host = self.project.read(cx).host()?;
let host = self.user_store.read(cx).get_cached_user(host.user_id)?;
let participant_index = self
.user_store
.read(cx)
.participant_indices()
.get(&host.id)?;
Some(
div().border().border_color(gpui::red()).child(
Button::new("project_owner_trigger", host.github_login.clone())
.color(Color::Player(participant_index.0))
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
),
)
}
pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
let name = {
let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
let worktree = worktree.read(cx);
worktree.root_name()
});
names.next().unwrap_or("")
};
let name = util::truncate_and_trailoff(name, MAX_PROJECT_NAME_LENGTH);
div()
.child(
Button::new("project_name_trigger", name)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Recent Projects", cx))
.on_click(cx.listener(|this, _, cx| {
this.toggle_project_menu(&ToggleProjectMenu, cx);
})),
)
.children(self.project_popover.as_ref().map(|popover| {
overlay().child(
div()
.min_w_56()
.on_mouse_down_out(cx.listener_for(&popover.picker, |picker, _, cx| {
picker.cancel(&Default::default(), cx)
}))
.child(popover.picker.clone()),
)
}))
}
pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
let entry = {
let mut names_and_branches =
self.project.read(cx).visible_worktrees(cx).map(|worktree| {
let worktree = worktree.read(cx);
worktree.root_git_entry()
});
names_and_branches.next().flatten()
};
let branch_name = entry
.as_ref()
.and_then(RepositoryEntry::branch)
.map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
Some(
div()
.child(
Button::new("project_branch_trigger", branch_name)
.color(Color::Muted)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| {
Tooltip::with_meta(
"Recent Branches",
Some(&ToggleVcsMenu),
"Local branches only",
cx,
)
})
.on_click(
cx.listener(|this, _, cx| this.toggle_vcs_menu(&ToggleVcsMenu, cx)),
),
)
.children(self.render_branches_popover_host()),
)
}
fn render_collaborator(
&self,
user: &Arc<User>,
peer_id: PeerId,
is_present: bool,
is_speaking: bool,
is_muted: bool,
room: &Room,
project_id: Option<u64>,
current_user: &Arc<User>,
) -> Option<FacePile> {
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
let pile = FacePile::default()
.child(
Avatar::new(user.avatar_uri.clone())
.grayscale(!is_present)
.border_color(if is_speaking {
gpui::blue()
} else if is_muted {
gpui::red()
} else {
Hsla::default()
}),
)
.children(followers.iter().filter_map(|follower_peer_id| {
let follower = room
.remote_participants()
.values()
.find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
.or_else(|| {
(self.client.peer_id() == Some(*follower_peer_id)).then_some(current_user)
})?
.clone();
Some(Avatar::new(follower.avatar_uri.clone()))
}));
Some(pile)
}
fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
let project = if cx.is_window_active() {
Some(self.project.clone())
} else {
None
};
ActiveCall::global(cx)
.update(cx, |call, cx| call.set_location(project.as_ref(), cx))
.detach_and_log_err(cx);
}
fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
cx.notify();
}
fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
let active_call = ActiveCall::global(cx);
let project = self.project.clone();
active_call
.update(cx, |call, cx| call.share_project(project, cx))
.detach_and_log_err(cx);
}
fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
let active_call = ActiveCall::global(cx);
let project = self.project.clone();
active_call
.update(cx, |call, cx| call.unshare_project(project, cx))
.log_err();
}
fn render_branches_popover_host<'a>(&'a self) -> Option<AnyElement> {
self.branch_popover.as_ref().map(|child| {
overlay()
.child(div().min_w_64().child(child.clone()))
.into_any()
})
}
pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
if self.branch_popover.take().is_none() {
if let Some(workspace) = self.workspace.upgrade() {
let Some(view) = build_branch_list(workspace, cx).log_err() else {
return;
};
cx.subscribe(&view, |this, _, _, cx| {
this.branch_popover = None;
cx.notify();
})
.detach();
self.project_popover.take();
let focus_handle = view.focus_handle(cx);
cx.focus(&focus_handle);
self.branch_popover = Some(view);
}
}
cx.notify();
}
pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
let workspace = self.workspace.clone();
if self.project_popover.take().is_none() {
cx.spawn(|this, mut cx| async move {
let workspaces = WORKSPACE_DB
.recent_workspaces_on_disk()
.await
.unwrap_or_default()
.into_iter()
.map(|(_, location)| location)
.collect();
let workspace = workspace.clone();
this.update(&mut cx, move |this, cx| {
let view = RecentProjects::open_popover(workspace, workspaces, cx);
cx.subscribe(&view.picker, |this, _, _: &DismissEvent, cx| {
this.project_popover = None;
cx.notify();
})
.detach();
let focus_handle = view.focus_handle(cx);
cx.focus(&focus_handle);
// todo!()
//this.branch_popover.take();
this.project_popover = Some(view);
cx.notify();
})
.log_err();
})
.detach();
}
cx.notify();
}
fn render_connection_status(
&self,
status: &client::Status,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
match status {
client::Status::ConnectionError
| client::Status::ConnectionLost
| client::Status::Reauthenticating { .. }
| client::Status::Reconnecting { .. }
| client::Status::ReconnectionError { .. } => Some(
div()
.id("disconnected")
.bg(gpui::red()) // todo!() @nate
.child(IconElement::new(Icon::Disconnected))
.tooltip(|cx| Tooltip::text("Disconnected", cx))
.into_any_element(),
),
client::Status::UpgradeRequired => {
let auto_updater = auto_update::AutoUpdater::get(cx);
let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) {
Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate",
Some(AutoUpdateStatus::Installing)
| Some(AutoUpdateStatus::Downloading)
| Some(AutoUpdateStatus::Checking) => "Updating...",
Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => {
"Please update Zed to Collaborate"
}
};
Some(
div()
.bg(gpui::red()) // todo!() @nate
.child(Button::new("connection-status", label).on_click(|_, cx| {
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
workspace::restart(&Default::default(), cx);
return;
}
}
auto_update::check(&Default::default(), cx);
}))
.into_any_element(),
)
}
_ => None,
}
}
pub fn render_sign_in_button(&mut self, _: &mut ViewContext<Self>) -> Button {
let client = self.client.clone();
Button::new("sign_in", "Sign in").on_click(move |_, cx| {
let client = client.clone();
cx.spawn(move |mut cx| async move {
client
.authenticate_and_connect(true, &cx)
.await
.notify_async_err(&mut cx);
})
.detach();
})
}
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
if let Some(user) = self.user_store.read(cx).current_user() {
popover_menu("user-menu")
.menu(|cx| {
ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Theme", theme_selector::Toggle.boxed_clone())
.separator()
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
.action("Sign Out", client::SignOut.boxed_clone())
})
})
.trigger(
ButtonLike::new("user-menu")
.child(
h_stack()
.gap_0p5()
.child(Avatar::new(user.avatar_uri.clone()))
.child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
)
.anchor(gpui::AnchorCorner::TopRight)
} else {
popover_menu("user-menu")
.menu(|cx| {
ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Theme", theme_selector::Toggle.boxed_clone())
.separator()
.action("Share Feedback", feedback::GiveFeedback.boxed_clone())
})
})
.trigger(
ButtonLike::new("user-menu")
.child(
h_stack()
.gap_0p5()
.child(IconElement::new(Icon::ChevronDown).color(Color::Muted)),
)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)),
)
}
}
}

View File

@@ -0,0 +1,167 @@
pub mod channel_view;
pub mod chat_panel;
pub mod collab_panel;
mod collab_titlebar_item;
mod face_pile;
pub mod notification_panel;
pub mod notifications;
mod panel_settings;
use std::{rc::Rc, sync::Arc};
use call::{report_call_event_for_room, ActiveCall, Room};
pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
use gpui::{
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
WindowKind, WindowOptions,
};
pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
};
use settings::Settings;
use util::ResultExt;
use workspace::AppState;
actions!(
collab,
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
);
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
ChatPanelSettings::register(cx);
NotificationPanelSettings::register(cx);
vcs_menu::init(cx);
collab_titlebar_item::init(cx);
collab_panel::init(cx);
channel_view::init(cx);
chat_panel::init(cx);
notification_panel::init(cx);
notifications::init(&app_state, cx);
// cx.add_global_action(toggle_screen_sharing);
// cx.add_global_action(toggle_mute);
// cx.add_global_action(toggle_deafen);
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
let toggle_screen_sharing = room.update(cx, |room, cx| {
if room.is_screen_sharing() {
report_call_event_for_room(
"disable screen share",
room.id(),
room.channel_id(),
&client,
cx,
);
Task::ready(room.unshare_screen(cx))
} else {
report_call_event_for_room(
"enable screen share",
room.id(),
room.channel_id(),
&client,
cx,
);
room.share_screen(cx)
}
});
toggle_screen_sharing.detach_and_log_err(cx);
}
}
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
room.update(cx, |room, cx| {
let operation = if room.is_muted(cx) {
"enable microphone"
} else {
"disable microphone"
};
report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
room.toggle_mute(cx)
})
.map(|task| task.detach_and_log_err(cx))
.log_err();
}
}
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
room.update(cx, Room::toggle_deafen)
.map(|task| task.detach_and_log_err(cx))
.log_err();
}
}
fn notification_window_options(
screen: Rc<dyn PlatformDisplay>,
window_size: Size<Pixels>,
) -> WindowOptions {
let notification_margin_width = GlobalPixels::from(16.);
let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
let screen_bounds = screen.bounds();
let size: Size<GlobalPixels> = window_size.into();
// todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument.
let bounds = gpui::Bounds::<GlobalPixels> {
origin: screen_bounds.upper_right()
- point(
size.width + notification_margin_width,
notification_margin_height,
),
size: window_size.into(),
};
WindowOptions {
bounds: WindowBounds::Fixed(bounds),
titlebar: None,
center: false,
focus: false,
show: true,
kind: WindowKind::PopUp,
is_movable: false,
display_id: Some(screen.id()),
}
}
// fn render_avatar<T: 'static>(
// avatar: Option<Arc<ImageData>>,
// avatar_style: &AvatarStyle,
// container: ContainerStyle,
// ) -> AnyElement<T> {
// avatar
// .map(|avatar| {
// Image::from_data(avatar)
// .with_style(avatar_style.image)
// .aligned()
// .contained()
// .with_corner_radius(avatar_style.outer_corner_radius)
// .constrained()
// .with_width(avatar_style.outer_width)
// .with_height(avatar_style.outer_width)
// .into_any()
// })
// .unwrap_or_else(|| {
// Empty::new()
// .constrained()
// .with_width(avatar_style.outer_width)
// .into_any()
// })
// .contained()
// .with_style(container)
// .into_any()
// }
fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
}

View File

@@ -0,0 +1,32 @@
use gpui::{
div, AnyElement, Div, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
};
use smallvec::SmallVec;
#[derive(Default, IntoElement)]
pub struct FacePile {
pub faces: SmallVec<[AnyElement; 2]>,
}
impl RenderOnce for FacePile {
type Rendered = Div;
fn render(self, _: &mut WindowContext) -> Self::Rendered {
let player_count = self.faces.len();
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
.z_index((player_count - ix) as u8)
.when(isnt_last, |div| div.neg_mr_1())
.child(player)
});
div().p_1().flex().items_center().children(player_list)
}
}
impl ParentElement for FacePile {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.faces
}
}

View File

@@ -0,0 +1,716 @@
use crate::{chat_panel::ChatPanel, NotificationPanelSettings};
use anyhow::Result;
use channel::ChannelStore;
use client::{Client, Notification, User, UserStore};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt;
use gpui::{
actions, div, list, px, serde_json, AnyElement, AppContext, AsyncWindowContext, CursorStyle,
DismissEvent, Div, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
IntoElement, ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render, Stateful,
StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext, WeakView,
WindowContext,
};
use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
use project::Fs;
use rpc::proto;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use std::{sync::Arc, time::Duration};
use time::{OffsetDateTime, UtcOffset};
use ui::{h_stack, v_stack, Avatar, Button, Clickable, Icon, IconButton, IconElement, Label};
use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
Workspace,
};
const LOADING_THRESHOLD: usize = 30;
const MARK_AS_READ_DELAY: Duration = Duration::from_secs(1);
const TOAST_DURATION: Duration = Duration::from_secs(5);
const NOTIFICATION_PANEL_KEY: &'static str = "NotificationPanel";
pub struct NotificationPanel {
client: Arc<Client>,
user_store: Model<UserStore>,
channel_store: Model<ChannelStore>,
notification_store: Model<NotificationStore>,
fs: Arc<dyn Fs>,
width: Option<f32>,
active: bool,
notification_list: ListState,
pending_serialization: Task<Option<()>>,
subscriptions: Vec<gpui::Subscription>,
workspace: WeakView<Workspace>,
current_notification_toast: Option<(u64, Task<()>)>,
local_timezone: UtcOffset,
focus_handle: FocusHandle,
mark_as_read_tasks: HashMap<u64, Task<Result<()>>>,
}
#[derive(Serialize, Deserialize)]
struct SerializedNotificationPanel {
width: Option<f32>,
}
#[derive(Debug)]
pub enum Event {
DockPositionChanged,
Focus,
Dismissed,
}
pub struct NotificationPresenter {
pub actor: Option<Arc<client::User>>,
pub text: String,
pub icon: &'static str,
pub needs_response: bool,
pub can_navigate: bool,
}
actions!(notification_panel, [ToggleFocus]);
pub fn init(cx: &mut AppContext) {
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, 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.build_view(|cx: &mut ViewContext<Self>| {
let view = cx.view().clone();
let mut status = client.status();
cx.spawn(|this, mut cx| async move {
while let Some(_) = status.next().await {
if this
.update(&mut cx, |_, cx| {
cx.notify();
})
.is_err()
{
break;
}
}
})
.detach();
let notification_list =
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, 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| {
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
if let Some(task) = this
.notification_store
.update(cx, |store, cx| store.load_more_notifications(false, cx))
{
task.detach();
}
}
},
));
let mut this = Self {
fs,
client,
user_store,
local_timezone: cx.local_timezone(),
channel_store: ChannelStore::global(cx),
notification_store: NotificationStore::global(cx),
notification_list,
pending_serialization: Task::ready(None),
workspace: workspace_handle,
focus_handle: cx.focus_handle(),
current_notification_toast: None,
subscriptions: Vec::new(),
active: false,
mark_as_read_tasks: HashMap::default(),
width: None,
};
let mut old_dock_position = this.position(cx);
this.subscriptions.extend([
cx.observe(&this.notification_store, |_, _, cx| 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: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let serialized_panel = if let Some(panel) = cx
.background_executor()
.spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) })
.await
.log_err()
.flatten()
{
Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?)
} else {
None
};
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;
cx.notify();
});
}
panel
})
})
}
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
self.pending_serialization = cx.background_executor().spawn(
async move {
KEY_VALUE_STORE
.write_kvp(
NOTIFICATION_PANEL_KEY.into(),
serde_json::to_string(&SerializedNotificationPanel { width })?,
)
.await?;
anyhow::Ok(())
}
.log_err(),
);
}
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();
let timestamp = entry.timestamp;
let NotificationPresenter {
actor,
text,
needs_response,
can_navigate,
..
} = self.present_notification(entry, cx)?;
let response = entry.response;
let notification = entry.notification.clone();
if self.active && !entry.is_read {
self.did_render_notification(notification_id, &notification, cx);
}
Some(
div()
.id(ix)
.child(
h_stack()
.children(actor.map(|actor| Avatar::new(actor.avatar_uri.clone())))
.child(
v_stack().child(Label::new(text)).child(
h_stack()
.child(Label::new(format_timestamp(
timestamp,
now,
self.local_timezone,
)))
.children(if let Some(is_accepted) = response {
Some(div().child(Label::new(if is_accepted {
"You accepted"
} else {
"You declined"
})))
} else if needs_response {
Some(
h_stack()
.child(Button::new("decline", "Decline").on_click(
{
let notification = notification.clone();
let view = cx.view().clone();
move |_, cx| {
view.update(cx, |this, cx| {
this.respond_to_notification(
notification.clone(),
false,
cx,
)
});
}
},
))
.child(Button::new("accept", "Accept").on_click({
let notification = notification.clone();
let view = cx.view().clone();
move |_, cx| {
view.update(cx, |this, cx| {
this.respond_to_notification(
notification.clone(),
true,
cx,
)
});
}
})),
)
} else {
None
}),
),
),
)
.when(can_navigate, |el| {
el.cursor(CursorStyle::PointingHand).on_click({
let notification = notification.clone();
cx.listener(move |this, _, cx| {
this.did_click_notification(&notification, cx)
})
})
})
.into_any(),
)
}
fn present_notification(
&self,
entry: &NotificationEntry,
cx: &AppContext,
) -> Option<NotificationPresenter> {
let user_store = self.user_store.read(cx);
let channel_store = self.channel_store.read(cx);
match entry.notification {
Notification::ContactRequest { sender_id } => {
let requester = user_store.get_cached_user(sender_id)?;
Some(NotificationPresenter {
icon: "icons/plus.svg",
text: format!("{} wants to add you as a contact", requester.github_login),
needs_response: user_store.has_incoming_contact_request(requester.id),
actor: Some(requester),
can_navigate: false,
})
}
Notification::ContactRequestAccepted { responder_id } => {
let responder = user_store.get_cached_user(responder_id)?;
Some(NotificationPresenter {
icon: "icons/plus.svg",
text: format!("{} accepted your contact invite", responder.github_login),
needs_response: false,
actor: Some(responder),
can_navigate: false,
})
}
Notification::ChannelInvitation {
ref channel_name,
channel_id,
inviter_id,
} => {
let inviter = user_store.get_cached_user(inviter_id)?;
Some(NotificationPresenter {
icon: "icons/hash.svg",
text: format!(
"{} invited you to join the #{channel_name} channel",
inviter.github_login
),
needs_response: channel_store.has_channel_invitation(channel_id),
actor: Some(inviter),
can_navigate: false,
})
}
Notification::ChannelMessageMention {
sender_id,
channel_id,
message_id,
} => {
let sender = user_store.get_cached_user(sender_id)?;
let channel = channel_store.channel_for_id(channel_id)?;
let message = self
.notification_store
.read(cx)
.channel_message_for_id(message_id)?;
Some(NotificationPresenter {
icon: "icons/conversations.svg",
text: format!(
"{} mentioned you in #{}:\n{}",
sender.github_login, channel.name, message.body,
),
needs_response: false,
actor: Some(sender),
can_navigate: true,
})
}
}
}
fn did_render_notification(
&mut self,
notification_id: u64,
notification: &Notification,
cx: &mut ViewContext<Self>,
) {
let should_mark_as_read = match notification {
Notification::ContactRequestAccepted { .. } => true,
Notification::ContactRequest { .. }
| Notification::ChannelInvitation { .. }
| Notification::ChannelMessageMention { .. } => false,
};
if should_mark_as_read {
self.mark_as_read_tasks
.entry(notification_id)
.or_insert_with(|| {
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(MARK_AS_READ_DELAY).await;
client
.request(proto::MarkNotificationRead { notification_id })
.await?;
this.update(&mut cx, |this, _| {
this.mark_as_read_tasks.remove(&notification_id);
})?;
Ok(())
})
});
}
}
fn did_click_notification(&mut self, notification: &Notification, cx: &mut ViewContext<Self>) {
if let Notification::ChannelMessageMention {
message_id,
channel_id,
..
} = notification.clone()
{
if let Some(workspace) = self.workspace.upgrade() {
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.select_channel(channel_id, Some(message_id), cx)
.detach_and_log_err(cx);
});
}
});
});
}
}
}
fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
if let Notification::ChannelMessageMention { channel_id, .. } = &notification {
if let Some(workspace) = self.workspace.upgrade() {
return if let Some(panel) = workspace.read(cx).panel::<ChatPanel>(cx) {
let panel = panel.read(cx);
panel.is_scrolled_to_bottom()
&& panel
.active_chat()
.map_or(false, |chat| chat.read(cx).channel_id == *channel_id)
} else {
false
};
}
}
false
}
fn render_sign_in_prompt(&self) -> AnyElement {
Button::new(
"sign_in_prompt_button",
"Sign in to view your notifications",
)
.on_click({
let client = self.client.clone();
move |_, cx| {
let client = client.clone();
cx.spawn(move |cx| async move {
client.authenticate_and_connect(true, &cx).log_err().await;
})
.detach()
}
})
.into_any_element()
}
fn render_empty_state(&self) -> AnyElement {
Label::new("You have no notifications").into_any_element()
}
fn on_notification_event(
&mut self,
_: Model<NotificationStore>,
event: &NotificationEvent,
cx: &mut ViewContext<Self>,
) {
match event {
NotificationEvent::NewNotification { entry } => self.add_toast(entry, cx),
NotificationEvent::NotificationRemoved { entry }
| NotificationEvent::NotificationRead { entry } => self.remove_toast(entry.id, cx),
NotificationEvent::NotificationsUpdated {
old_range,
new_count,
} => {
self.notification_list.splice(old_range.clone(), *new_count);
cx.notify();
}
}
}
fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
if self.is_showing_notification(&entry.notification, cx) {
return;
}
let Some(NotificationPresenter { actor, text, .. }) = self.present_notification(entry, cx)
else {
return;
};
let notification_id = entry.id;
self.current_notification_toast = Some((
notification_id,
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();
}),
));
self.workspace
.update(cx, |workspace, cx| {
workspace.dismiss_notification::<NotificationToast>(0, cx);
workspace.show_notification(0, cx, |cx| {
let workspace = cx.view().downgrade();
cx.build_view(|_| NotificationToast {
notification_id,
actor,
text,
workspace,
})
})
})
.ok();
}
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();
self.workspace
.update(cx, |workspace, cx| {
workspace.dismiss_notification::<NotificationToast>(0, cx)
})
.ok();
}
}
}
fn respond_to_notification(
&mut self,
notification: Notification,
response: bool,
cx: &mut ViewContext<Self>,
) {
self.notification_store.update(cx, |store, cx| {
store.respond_to_notification(notification, response, cx);
});
}
}
impl Render for NotificationPanel {
type Element = AnyElement;
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement {
if self.client.user_id().is_none() {
self.render_sign_in_prompt()
} else if self.notification_list.item_count() == 0 {
self.render_empty_state()
} else {
v_stack()
.bg(gpui::red())
.child(
h_stack()
.child(Label::new("Notifications"))
.child(IconElement::new(Icon::Envelope)),
)
.child(list(self.notification_list.clone()).size_full())
.size_full()
.into_any_element()
}
}
}
impl FocusableView for NotificationPanel {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl EventEmitter<Event> for NotificationPanel {}
impl EventEmitter<PanelEvent> for NotificationPanel {}
impl Panel for NotificationPanel {
fn persistent_name() -> &'static str {
"NotificationPanel"
}
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
NotificationPanelSettings::get_global(cx).dock
}
fn position_is_valid(&self, position: DockPosition) -> bool {
matches!(position, DockPosition::Left | DockPosition::Right)
}
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
settings::update_settings_file::<NotificationPanelSettings>(
self.fs.clone(),
cx,
move |settings| settings.dock = Some(position),
);
}
fn size(&self, cx: &gpui::WindowContext) -> f32 {
self.width
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
}
fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
self.width = size;
self.serialize(cx);
cx.notify();
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
self.active = active;
if self.notification_store.read(cx).notification_count() == 0 {
cx.emit(Event::Dismissed);
}
}
fn icon(&self, cx: &gpui::WindowContext) -> Option<Icon> {
(NotificationPanelSettings::get_global(cx).button
&& self.notification_store.read(cx).notification_count() > 0)
.then(|| Icon::Bell)
}
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
let count = self.notification_store.read(cx).unread_notification_count();
if count == 0 {
None
} else {
Some(count.to_string())
}
}
fn toggle_action(&self) -> Box<dyn gpui::Action> {
Box::new(ToggleFocus)
}
}
pub struct NotificationToast {
notification_id: u64,
actor: Option<Arc<User>>,
text: String,
workspace: WeakView<Workspace>,
}
pub enum ToastEvent {
Dismiss,
}
impl NotificationToast {
fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
let workspace = self.workspace.clone();
let notification_id = self.notification_id;
cx.window_context().defer(move |cx| {
workspace
.update(cx, |workspace, 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, cx);
}
});
}
})
.ok();
})
}
}
impl Render for NotificationToast {
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let user = self.actor.clone();
h_stack()
.id("notification_panel_toast")
.children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
.child(Label::new(self.text.clone()))
.child(
IconButton::new("close", Icon::Close)
.on_click(cx.listener(|_, _, cx| cx.emit(ToastEvent::Dismiss))),
)
.on_click(cx.listener(|this, _, cx| {
this.focus_notification_panel(cx);
cx.emit(ToastEvent::Dismiss);
}))
}
}
impl EventEmitter<ToastEvent> for NotificationToast {}
impl EventEmitter<DismissEvent> for NotificationToast {}
fn format_timestamp(
mut timestamp: OffsetDateTime,
mut now: OffsetDateTime,
local_timezone: UtcOffset,
) -> String {
timestamp = timestamp.to_offset(local_timezone);
now = now.to_offset(local_timezone);
let today = now.date();
let date = timestamp.date();
if date == today {
let difference = now - timestamp;
if difference >= Duration::from_secs(3600) {
format!("{}h", difference.whole_seconds() / 3600)
} else if difference >= Duration::from_secs(60) {
format!("{}m", difference.whole_seconds() / 60)
} else {
"just now".to_string()
}
} else if date.next_day() == Some(today) {
format!("yesterday")
} else {
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
}
}

View File

@@ -0,0 +1,11 @@
use gpui::AppContext;
use std::sync::Arc;
use workspace::AppState;
pub mod incoming_call_notification;
pub mod project_shared_notification;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
incoming_call_notification::init(app_state, cx);
project_shared_notification::init(app_state, cx);
}

View File

@@ -0,0 +1,147 @@
use crate::notification_window_options;
use call::{ActiveCall, IncomingCall};
use futures::StreamExt;
use gpui::{
div, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce, Styled, ViewContext,
VisualContext as _, WindowHandle,
};
use std::sync::{Arc, Weak};
use ui::prelude::*;
use ui::{h_stack, v_stack, Avatar, Button, Label};
use util::ResultExt;
use workspace::AppState;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
let app_state = Arc::downgrade(app_state);
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
cx.spawn(|mut cx| async move {
let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
while let Some(incoming_call) = incoming_call.next().await {
for window in notification_windows.drain(..) {
window
.update(&mut cx, |_, cx| {
// todo!()
cx.remove_window();
})
.log_err();
}
if let Some(incoming_call) = incoming_call {
let unique_screens = cx.update(|cx| cx.displays()).unwrap();
let window_size = gpui::Size {
width: px(380.),
height: px(64.),
};
for screen in unique_screens {
let options = notification_window_options(screen, window_size);
let window = cx
.open_window(options, |cx| {
cx.build_view(|_| {
IncomingCallNotification::new(
incoming_call.clone(),
app_state.clone(),
)
})
})
.unwrap();
notification_windows.push(window);
}
}
}
})
.detach();
}
#[derive(Clone, PartialEq)]
struct RespondToCall {
accept: bool,
}
struct IncomingCallNotificationState {
call: IncomingCall,
app_state: Weak<AppState>,
}
pub struct IncomingCallNotification {
state: Arc<IncomingCallNotificationState>,
}
impl IncomingCallNotificationState {
pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
Self { call, app_state }
}
fn respond(&self, accept: bool, cx: &mut AppContext) {
let active_call = ActiveCall::global(cx);
if accept {
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
let caller_user_id = self.call.calling_user.id;
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
let app_state = self.app_state.clone();
let cx: &mut AppContext = cx;
cx.spawn(|cx| async move {
join.await?;
if let Some(project_id) = initial_project_id {
cx.update(|cx| {
if let Some(app_state) = app_state.upgrade() {
workspace::join_remote_project(
project_id,
caller_user_id,
app_state,
cx,
)
.detach_and_log_err(cx);
}
})
.log_err();
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
} else {
active_call.update(cx, |active_call, cx| {
active_call.decline_incoming(cx).log_err();
});
}
}
}
impl IncomingCallNotification {
pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
Self {
state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
}
}
fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element {
h_stack()
.child(Avatar::new(self.state.call.calling_user.avatar_uri.clone()))
.child(
v_stack()
.child(Label::new(format!(
"{} is sharing a project in Zed",
self.state.call.calling_user.github_login
)))
.child(self.render_buttons(cx)),
)
}
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
h_stack()
.child(Button::new("accept", "Accept").render(cx).on_click({
let state = self.state.clone();
move |_, cx| state.respond(true, cx)
}))
.child(Button::new("decline", "Decline").render(cx).on_click({
let state = self.state.clone();
move |_, cx| state.respond(false, cx)
}))
}
}
impl Render for IncomingCallNotification {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div().bg(red()).flex_none().child(self.render_caller(cx))
}
}

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